@crosscopy/clipboard 0.1.0-beta

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,96 @@
1
+ # General Clipboard Listener
2
+
3
+ > A Cross-Platform clipboard listener that listens for both text and image (screenshots).
4
+
5
+ npm package: https://www.npmjs.com/package/general-clipboard-listener
6
+
7
+ ## Installation
8
+
9
+ `npm i @crosscopy/clipboard`
10
+
11
+ ## Usage
12
+
13
+ It has a very easy to use event-based system.
14
+
15
+ See [demo.ts](./demo.ts) for a demo.
16
+
17
+ Run `ts-node demo.ts`.
18
+
19
+ ```ts
20
+ import clipboardEventListener from "./@crosscopy/clipboard";
21
+
22
+ console.log(clipboard.readTextSync());
23
+ console.log(await clipboard.readText());
24
+ const imgBuf = clipboard.readImageSync();
25
+ // console.log(imgBuf.toString("base64"));
26
+ // console.log(clipboard.readImageBase64Sync());
27
+ // await clipboard.writeImage(base64img); // add fake image to clipboard
28
+ clipboard.writeImageSync(base64img); // add fake image to clipboard
29
+ console.log(""); // give some time
30
+ console.assert(clipboard.readImageBase64Sync() === base64img);
31
+
32
+ // * test readimage
33
+ clipboard.writeImageSync(base64img);
34
+ console.log();
35
+ console.assert(
36
+ (await clipboard.readImage()).toString("base64") === base64img
37
+ );
38
+
39
+ await clipboard.writeImage(base64img);
40
+ console.log();
41
+ console.assert((await clipboard.readImageBase64()) === base64img);
42
+ clipboard.on("text", (text) => {
43
+ console.log(text);
44
+ });
45
+ clipboard.on("image", (data) => {
46
+ fs.writeFileSync("test.png", data);
47
+ });
48
+ clipboard.listen();
49
+ setTimeout(() => {
50
+ clipboard.close();
51
+ }, 10000);
52
+ ```
53
+
54
+ ### Note
55
+
56
+ This is an important note. If you write some data and read it immediately using the `sync` APIs, you may not be able to get the data.
57
+ It needs a tiny bit of time to process. any code between the two lines should work, such as `console.log()`.
58
+
59
+ Here I use `await sleep(1)` to make it work.
60
+
61
+ ```js
62
+ const sleep = (ms: number) => new Promise((r) => setTimeout(r, ms));
63
+
64
+ cb.writeTextSync("abc");
65
+ await sleep(1);
66
+ const text = cb.readTextSync();
67
+ ```
68
+
69
+ If this package becomes popular some day and people requires a fix, I will fix it. For now, this implementation is good enough for CrossCopy.
70
+
71
+ I never need to write and read immediately after unless during testing.
72
+
73
+ ## Explanation
74
+
75
+ It's achieved using child process + `stdout`.
76
+
77
+ I took a golang version of the listener from [golang-design/clipboard](https://github.com/golang-design/clipboard).
78
+
79
+ When a change is detected, `IMAGE_CHANGED` or `TEXT_CHANGED` is printed to `stdout`.
80
+
81
+ This library runs compiled go-version clipboard listener using child process and listen to the `stdout`.
82
+
83
+ If it sees the keywords in child process's `stdout`, an event will be emitted.
84
+
85
+ Run `npm run demo` to see a demo for 10 sec. Once started, copy some text and screenshot and check the terminal.
86
+
87
+
88
+ ## Supported Platforms
89
+
90
+ Format: `process.platform/process.arch`
91
+
92
+ The process here is from nodejs.
93
+
94
+ Supported platforms can be found in `go-clipboard-monitor`.
95
+
96
+ If your nodejs gives different platform or arch, it may not work.
package/dist/demo.d.ts ADDED
@@ -0,0 +1 @@
1
+
package/dist/demo.js ADDED
@@ -0,0 +1,94 @@
1
+ 'use strict';
2
+
3
+ var fs = require('fs');
4
+ var path = require('path');
5
+ var events = require('events');
6
+ var child_process = require('child_process');
7
+
8
+ function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
9
+
10
+ var fs__default = /*#__PURE__*/_interopDefaultLegacy(fs);
11
+ var path__default = /*#__PURE__*/_interopDefaultLegacy(path);
12
+
13
+ var __getOwnPropNames = Object.getOwnPropertyNames;
14
+ var __esm = (fn, res) => function __init() {
15
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
16
+ };
17
+ var __commonJS = (cb, mod) => function __require() {
18
+ return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
19
+ };
20
+ var ClipboardEventListener, clipboard_default;
21
+ var init_clipboard = __esm({
22
+ "index.ts"() {
23
+ ClipboardEventListener = class extends events.EventEmitter {
24
+ constructor() {
25
+ super();
26
+ this.child = void 0;
27
+ }
28
+ startListening() {
29
+ var _a, _b;
30
+ const { platform, arch } = process;
31
+ const pathArr = [__dirname];
32
+ if (path__default["default"].basename(__dirname) === "dist") {
33
+ pathArr.push("..");
34
+ }
35
+ let exeFilename = `go-clipboard-${platform}-${arch}`;
36
+ if (platform === "win32") {
37
+ exeFilename += ".exe";
38
+ }
39
+ pathArr.push(...["go-clipboard", "binaries", exeFilename]);
40
+ const exePath = path__default["default"].join(...pathArr);
41
+ console.log(exePath);
42
+ if (!fs__default["default"].existsSync(exePath)) {
43
+ throw new Error(
44
+ `Executable (${exeFilename}) not found, your platform is ${platform} ${arch} and may not be supported.`
45
+ );
46
+ }
47
+ this.child = child_process.execFile(exePath);
48
+ (_a = this.child.stdout) == null ? void 0 : _a.on("data", (data) => {
49
+ const dataStr = data.toString();
50
+ if (dataStr.trim() === "TEXT_CHANGED") {
51
+ this.emit("text");
52
+ }
53
+ if (dataStr.trim() === "IMAGE_CHANGED") {
54
+ this.emit("image");
55
+ }
56
+ });
57
+ (_b = this.child.stderr) == null ? void 0 : _b.on("data", (data) => {
58
+ this.emit("open", data.toString());
59
+ });
60
+ }
61
+ stopListening() {
62
+ var _a;
63
+ this.emit("close");
64
+ const res = (_a = this.child) == null ? void 0 : _a.kill();
65
+ return res;
66
+ }
67
+ };
68
+ clipboard_default = new ClipboardEventListener();
69
+ }
70
+ });
71
+
72
+ // demo.ts
73
+ var require_demo = __commonJS({
74
+ "demo.ts"() {
75
+ init_clipboard();
76
+ clipboard_default.on("text", () => {
77
+ console.log("Clipboard Text Updated");
78
+ });
79
+ clipboard_default.on("image", () => {
80
+ console.log("Clipboard Image Updated");
81
+ });
82
+ clipboard_default.on("open", (data) => {
83
+ console.log(data);
84
+ });
85
+ clipboard_default.startListening();
86
+ setTimeout(() => {
87
+ clipboard_default.stopListening();
88
+ }, 1e4);
89
+ }
90
+ });
91
+ var demo = require_demo();
92
+
93
+ module.exports = demo;
94
+ //# sourceMappingURL=demo.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../index.ts","../demo.ts"],"names":[],"mappings":";;;;;;;;;AAAA,OAAO,QAAQ;AACf,OAAO,UAAU;AACjB,SAAS,oBAAoB;AAC7B,SAAgB,gBAA8D;AAH9E,IAKM,wBAkDC;AAvDP;AAAA;AAAA;AAKA,IAAM,yBAAN,cAAqC,aAAa;AAAA,MAEhD,cAAc;AACZ,cAAM;AACN,aAAK,QAAQ;AAAA,MACf;AAAA,MAEA,iBAAiB;AAZnB;AAaI,cAAM,EAAE,UAAU,KAAK,IAAI;AAC3B,cAAM,UAAU,CAAC,SAAS;AAE1B,YAAI,KAAK,SAAS,SAAS,MAAM,QAAQ;AACvC,kBAAQ,KAAK,IAAI;AAAA,QACnB;AACA,YAAI,cAAc,gBAAgB,YAAY;AAC9C,YAAI,aAAa,SAAS;AACxB,yBAAe;AAAA,QACjB;AACA,gBAAQ,KAAK,GAAG,CAAC,gBAAgB,YAAY,WAAW,CAAC;AACzD,cAAM,UAAU,KAAK,KAAK,GAAG,OAAO;AACpC,gBAAQ,IAAI,OAAO;AAEnB,YAAI,CAAC,GAAG,WAAW,OAAO,GAAG;AAC3B,gBAAM,IAAI;AAAA,YACR,eAAe,4CAA4C,YAAY;AAAA,UACzE;AAAA,QACF;AACA,aAAK,QAAQ,SAAS,OAAO;AAC7B,mBAAK,MAAM,WAAX,mBAAmB,GAAG,QAAQ,CAAC,SAAiB;AAC9C,gBAAM,UAAU,KAAK,SAAS;AAC9B,cAAI,QAAQ,KAAK,MAAM,gBAAgB;AACrC,iBAAK,KAAK,MAAM;AAAA,UAClB;AACA,cAAI,QAAQ,KAAK,MAAM,iBAAiB;AACtC,iBAAK,KAAK,OAAO;AAAA,UACnB;AAAA,QACF;AAEA,mBAAK,MAAM,WAAX,mBAAmB,GAAG,QAAQ,CAAC,SAAiB;AAC9C,eAAK,KAAK,QAAQ,KAAK,SAAS,CAAC;AAAA,QACnC;AAAA,MACF;AAAA,MAEA,gBAAgB;AAhDlB;AAiDI,aAAK,KAAK,OAAO;AACjB,cAAM,OAAM,UAAK,UAAL,mBAAY;AACxB,eAAO;AAAA,MACT;AAAA,IACF;AAEA,IAAO,oBAAQ,IAAI,uBAAuB;AAAA;AAAA;;;ACvD1C;AAAA;AAAA;AAEA,sBAAuB,GAAG,QAAQ,MAAM;AACtC,cAAQ,IAAI,wBAAwB;AAAA,IACtC,CAAC;AAED,sBAAuB,GAAG,SAAS,MAAM;AACvC,cAAQ,IAAI,yBAAyB;AAAA,IACvC,CAAC;AAED,sBAAuB,GAAG,QAAQ,CAAC,SAAS;AAC1C,cAAQ,IAAI,IAAI;AAAA,IAClB,CAAC;AAED,sBAAuB,eAAe;AAEtC,eAAW,MAAM;AACf,wBAAuB,cAAc;AAAA,IACvC,GAAG,GAAK;AAAA;AAAA","sourcesContent":["import fs from 'node:fs';\nimport path from 'node:path';\nimport { EventEmitter } from 'events';\nimport { spawn, execFile, ChildProcessWithoutNullStreams, ChildProcess } from 'node:child_process';\n\nclass ClipboardEventListener extends EventEmitter {\n child: ChildProcess | undefined;\n constructor() {\n super();\n this.child = undefined;\n }\n\n startListening() {\n const { platform, arch } = process;\n const pathArr = [__dirname];\n\n if (path.basename(__dirname) === 'dist') {\n pathArr.push('..');\n }\n let exeFilename = `go-clipboard-${platform}-${arch}`;\n if (platform === 'win32') {\n exeFilename += '.exe';\n }\n pathArr.push(...['go-clipboard', 'binaries', exeFilename]);\n const exePath = path.join(...pathArr);\n console.log(exePath);\n \n if (!fs.existsSync(exePath)) {\n throw new Error(\n `Executable (${exeFilename}) not found, your platform is ${platform} ${arch} and may not be supported.`\n );\n }\n this.child = execFile(exePath);\n this.child.stdout?.on('data', (data: Buffer) => {\n const dataStr = data.toString();\n if (dataStr.trim() === 'TEXT_CHANGED') {\n this.emit('text');\n }\n if (dataStr.trim() === 'IMAGE_CHANGED') {\n this.emit('image');\n }\n });\n\n this.child.stderr?.on('data', (data: Buffer) => {\n this.emit('open', data.toString());\n });\n }\n\n stopListening() {\n this.emit('close');\n const res = this.child?.kill();\n return res;\n }\n}\n\nexport default new ClipboardEventListener();\n","import clipboardEventListener from \"./index\";\n\nclipboardEventListener.on(\"text\", () => {\n console.log(\"Clipboard Text Updated\");\n});\n\nclipboardEventListener.on(\"image\", () => {\n console.log(\"Clipboard Image Updated\");\n});\n\nclipboardEventListener.on(\"open\", (data) => {\n console.log(data);\n});\n\nclipboardEventListener.startListening();\n\nsetTimeout(() => {\n clipboardEventListener.stopListening();\n}, 10000);\n"]}
package/dist/demo.mjs ADDED
@@ -0,0 +1,87 @@
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+ import { EventEmitter } from 'events';
4
+ import { execFile } from 'child_process';
5
+
6
+ var __getOwnPropNames = Object.getOwnPropertyNames;
7
+ var __esm = (fn, res) => function __init() {
8
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
9
+ };
10
+ var __commonJS = (cb, mod) => function __require() {
11
+ return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
12
+ };
13
+ var ClipboardEventListener, clipboard_default;
14
+ var init_clipboard = __esm({
15
+ "index.ts"() {
16
+ ClipboardEventListener = class extends EventEmitter {
17
+ constructor() {
18
+ super();
19
+ this.child = void 0;
20
+ }
21
+ startListening() {
22
+ var _a, _b;
23
+ const { platform, arch } = process;
24
+ const pathArr = [__dirname];
25
+ if (path.basename(__dirname) === "dist") {
26
+ pathArr.push("..");
27
+ }
28
+ let exeFilename = `go-clipboard-${platform}-${arch}`;
29
+ if (platform === "win32") {
30
+ exeFilename += ".exe";
31
+ }
32
+ pathArr.push(...["go-clipboard", "binaries", exeFilename]);
33
+ const exePath = path.join(...pathArr);
34
+ console.log(exePath);
35
+ if (!fs.existsSync(exePath)) {
36
+ throw new Error(
37
+ `Executable (${exeFilename}) not found, your platform is ${platform} ${arch} and may not be supported.`
38
+ );
39
+ }
40
+ this.child = execFile(exePath);
41
+ (_a = this.child.stdout) == null ? void 0 : _a.on("data", (data) => {
42
+ const dataStr = data.toString();
43
+ if (dataStr.trim() === "TEXT_CHANGED") {
44
+ this.emit("text");
45
+ }
46
+ if (dataStr.trim() === "IMAGE_CHANGED") {
47
+ this.emit("image");
48
+ }
49
+ });
50
+ (_b = this.child.stderr) == null ? void 0 : _b.on("data", (data) => {
51
+ this.emit("open", data.toString());
52
+ });
53
+ }
54
+ stopListening() {
55
+ var _a;
56
+ this.emit("close");
57
+ const res = (_a = this.child) == null ? void 0 : _a.kill();
58
+ return res;
59
+ }
60
+ };
61
+ clipboard_default = new ClipboardEventListener();
62
+ }
63
+ });
64
+
65
+ // demo.ts
66
+ var require_demo = __commonJS({
67
+ "demo.ts"() {
68
+ init_clipboard();
69
+ clipboard_default.on("text", () => {
70
+ console.log("Clipboard Text Updated");
71
+ });
72
+ clipboard_default.on("image", () => {
73
+ console.log("Clipboard Image Updated");
74
+ });
75
+ clipboard_default.on("open", (data) => {
76
+ console.log(data);
77
+ });
78
+ clipboard_default.startListening();
79
+ setTimeout(() => {
80
+ clipboard_default.stopListening();
81
+ }, 1e4);
82
+ }
83
+ });
84
+ var demo = require_demo();
85
+
86
+ export { demo as default };
87
+ //# sourceMappingURL=demo.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../index.ts","../demo.ts"],"names":[],"mappings":";;;;;;;;;AAAA,OAAO,QAAQ;AACf,OAAO,UAAU;AACjB,SAAS,oBAAoB;AAC7B,SAAgB,gBAA8D;AAH9E,IAKM,wBAkDC;AAvDP;AAAA;AAAA;AAKA,IAAM,yBAAN,cAAqC,aAAa;AAAA,MAEhD,cAAc;AACZ,cAAM;AACN,aAAK,QAAQ;AAAA,MACf;AAAA,MAEA,iBAAiB;AAZnB;AAaI,cAAM,EAAE,UAAU,KAAK,IAAI;AAC3B,cAAM,UAAU,CAAC,SAAS;AAE1B,YAAI,KAAK,SAAS,SAAS,MAAM,QAAQ;AACvC,kBAAQ,KAAK,IAAI;AAAA,QACnB;AACA,YAAI,cAAc,gBAAgB,YAAY;AAC9C,YAAI,aAAa,SAAS;AACxB,yBAAe;AAAA,QACjB;AACA,gBAAQ,KAAK,GAAG,CAAC,gBAAgB,YAAY,WAAW,CAAC;AACzD,cAAM,UAAU,KAAK,KAAK,GAAG,OAAO;AACpC,gBAAQ,IAAI,OAAO;AAEnB,YAAI,CAAC,GAAG,WAAW,OAAO,GAAG;AAC3B,gBAAM,IAAI;AAAA,YACR,eAAe,4CAA4C,YAAY;AAAA,UACzE;AAAA,QACF;AACA,aAAK,QAAQ,SAAS,OAAO;AAC7B,mBAAK,MAAM,WAAX,mBAAmB,GAAG,QAAQ,CAAC,SAAiB;AAC9C,gBAAM,UAAU,KAAK,SAAS;AAC9B,cAAI,QAAQ,KAAK,MAAM,gBAAgB;AACrC,iBAAK,KAAK,MAAM;AAAA,UAClB;AACA,cAAI,QAAQ,KAAK,MAAM,iBAAiB;AACtC,iBAAK,KAAK,OAAO;AAAA,UACnB;AAAA,QACF;AAEA,mBAAK,MAAM,WAAX,mBAAmB,GAAG,QAAQ,CAAC,SAAiB;AAC9C,eAAK,KAAK,QAAQ,KAAK,SAAS,CAAC;AAAA,QACnC;AAAA,MACF;AAAA,MAEA,gBAAgB;AAhDlB;AAiDI,aAAK,KAAK,OAAO;AACjB,cAAM,OAAM,UAAK,UAAL,mBAAY;AACxB,eAAO;AAAA,MACT;AAAA,IACF;AAEA,IAAO,oBAAQ,IAAI,uBAAuB;AAAA;AAAA;;;ACvD1C;AAAA;AAAA;AAEA,sBAAuB,GAAG,QAAQ,MAAM;AACtC,cAAQ,IAAI,wBAAwB;AAAA,IACtC,CAAC;AAED,sBAAuB,GAAG,SAAS,MAAM;AACvC,cAAQ,IAAI,yBAAyB;AAAA,IACvC,CAAC;AAED,sBAAuB,GAAG,QAAQ,CAAC,SAAS;AAC1C,cAAQ,IAAI,IAAI;AAAA,IAClB,CAAC;AAED,sBAAuB,eAAe;AAEtC,eAAW,MAAM;AACf,wBAAuB,cAAc;AAAA,IACvC,GAAG,GAAK;AAAA;AAAA","sourcesContent":["import fs from 'node:fs';\nimport path from 'node:path';\nimport { EventEmitter } from 'events';\nimport { spawn, execFile, ChildProcessWithoutNullStreams, ChildProcess } from 'node:child_process';\n\nclass ClipboardEventListener extends EventEmitter {\n child: ChildProcess | undefined;\n constructor() {\n super();\n this.child = undefined;\n }\n\n startListening() {\n const { platform, arch } = process;\n const pathArr = [__dirname];\n\n if (path.basename(__dirname) === 'dist') {\n pathArr.push('..');\n }\n let exeFilename = `go-clipboard-${platform}-${arch}`;\n if (platform === 'win32') {\n exeFilename += '.exe';\n }\n pathArr.push(...['go-clipboard', 'binaries', exeFilename]);\n const exePath = path.join(...pathArr);\n console.log(exePath);\n \n if (!fs.existsSync(exePath)) {\n throw new Error(\n `Executable (${exeFilename}) not found, your platform is ${platform} ${arch} and may not be supported.`\n );\n }\n this.child = execFile(exePath);\n this.child.stdout?.on('data', (data: Buffer) => {\n const dataStr = data.toString();\n if (dataStr.trim() === 'TEXT_CHANGED') {\n this.emit('text');\n }\n if (dataStr.trim() === 'IMAGE_CHANGED') {\n this.emit('image');\n }\n });\n\n this.child.stderr?.on('data', (data: Buffer) => {\n this.emit('open', data.toString());\n });\n }\n\n stopListening() {\n this.emit('close');\n const res = this.child?.kill();\n return res;\n }\n}\n\nexport default new ClipboardEventListener();\n","import clipboardEventListener from \"./index\";\n\nclipboardEventListener.on(\"text\", () => {\n console.log(\"Clipboard Text Updated\");\n});\n\nclipboardEventListener.on(\"image\", () => {\n console.log(\"Clipboard Image Updated\");\n});\n\nclipboardEventListener.on(\"open\", (data) => {\n console.log(data);\n});\n\nclipboardEventListener.startListening();\n\nsetTimeout(() => {\n clipboardEventListener.stopListening();\n}, 10000);\n"]}
@@ -0,0 +1,12 @@
1
+ import { EventEmitter } from 'events';
2
+ import { ChildProcess } from 'node:child_process';
3
+
4
+ declare class ClipboardEventListener extends EventEmitter {
5
+ child: ChildProcess | undefined;
6
+ constructor();
7
+ startListening(): void;
8
+ stopListening(): boolean | undefined;
9
+ }
10
+ declare const _default: ClipboardEventListener;
11
+
12
+ export { _default as default };
package/dist/index.js ADDED
@@ -0,0 +1,62 @@
1
+ 'use strict';
2
+
3
+ var fs = require('fs');
4
+ var path = require('path');
5
+ var events = require('events');
6
+ var child_process = require('child_process');
7
+
8
+ function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
9
+
10
+ var fs__default = /*#__PURE__*/_interopDefaultLegacy(fs);
11
+ var path__default = /*#__PURE__*/_interopDefaultLegacy(path);
12
+
13
+ // index.ts
14
+ var ClipboardEventListener = class extends events.EventEmitter {
15
+ constructor() {
16
+ super();
17
+ this.child = void 0;
18
+ }
19
+ startListening() {
20
+ var _a, _b;
21
+ const { platform, arch } = process;
22
+ const pathArr = [__dirname];
23
+ if (path__default["default"].basename(__dirname) === "dist") {
24
+ pathArr.push("..");
25
+ }
26
+ let exeFilename = `go-clipboard-${platform}-${arch}`;
27
+ if (platform === "win32") {
28
+ exeFilename += ".exe";
29
+ }
30
+ pathArr.push(...["go-clipboard", "binaries", exeFilename]);
31
+ const exePath = path__default["default"].join(...pathArr);
32
+ console.log(exePath);
33
+ if (!fs__default["default"].existsSync(exePath)) {
34
+ throw new Error(
35
+ `Executable (${exeFilename}) not found, your platform is ${platform} ${arch} and may not be supported.`
36
+ );
37
+ }
38
+ this.child = child_process.execFile(exePath);
39
+ (_a = this.child.stdout) == null ? void 0 : _a.on("data", (data) => {
40
+ const dataStr = data.toString();
41
+ if (dataStr.trim() === "TEXT_CHANGED") {
42
+ this.emit("text");
43
+ }
44
+ if (dataStr.trim() === "IMAGE_CHANGED") {
45
+ this.emit("image");
46
+ }
47
+ });
48
+ (_b = this.child.stderr) == null ? void 0 : _b.on("data", (data) => {
49
+ this.emit("open", data.toString());
50
+ });
51
+ }
52
+ stopListening() {
53
+ var _a;
54
+ this.emit("close");
55
+ const res = (_a = this.child) == null ? void 0 : _a.kill();
56
+ return res;
57
+ }
58
+ };
59
+ var clipboard_default = new ClipboardEventListener();
60
+
61
+ module.exports = clipboard_default;
62
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../index.ts"],"names":[],"mappings":";AAAA,OAAO,QAAQ;AACf,OAAO,UAAU;AACjB,SAAS,oBAAoB;AAC7B,SAAgB,gBAA8D;AAE9E,IAAM,yBAAN,cAAqC,aAAa;AAAA,EAEhD,cAAc;AACZ,UAAM;AACN,SAAK,QAAQ;AAAA,EACf;AAAA,EAEA,iBAAiB;AAZnB;AAaI,UAAM,EAAE,UAAU,KAAK,IAAI;AAC3B,UAAM,UAAU,CAAC,SAAS;AAE1B,QAAI,KAAK,SAAS,SAAS,MAAM,QAAQ;AACvC,cAAQ,KAAK,IAAI;AAAA,IACnB;AACA,QAAI,cAAc,gBAAgB,YAAY;AAC9C,QAAI,aAAa,SAAS;AACxB,qBAAe;AAAA,IACjB;AACA,YAAQ,KAAK,GAAG,CAAC,gBAAgB,YAAY,WAAW,CAAC;AACzD,UAAM,UAAU,KAAK,KAAK,GAAG,OAAO;AACpC,YAAQ,IAAI,OAAO;AAEnB,QAAI,CAAC,GAAG,WAAW,OAAO,GAAG;AAC3B,YAAM,IAAI;AAAA,QACR,eAAe,4CAA4C,YAAY;AAAA,MACzE;AAAA,IACF;AACA,SAAK,QAAQ,SAAS,OAAO;AAC7B,eAAK,MAAM,WAAX,mBAAmB,GAAG,QAAQ,CAAC,SAAiB;AAC9C,YAAM,UAAU,KAAK,SAAS;AAC9B,UAAI,QAAQ,KAAK,MAAM,gBAAgB;AACrC,aAAK,KAAK,MAAM;AAAA,MAClB;AACA,UAAI,QAAQ,KAAK,MAAM,iBAAiB;AACtC,aAAK,KAAK,OAAO;AAAA,MACnB;AAAA,IACF;AAEA,eAAK,MAAM,WAAX,mBAAmB,GAAG,QAAQ,CAAC,SAAiB;AAC9C,WAAK,KAAK,QAAQ,KAAK,SAAS,CAAC;AAAA,IACnC;AAAA,EACF;AAAA,EAEA,gBAAgB;AAhDlB;AAiDI,SAAK,KAAK,OAAO;AACjB,UAAM,OAAM,UAAK,UAAL,mBAAY;AACxB,WAAO;AAAA,EACT;AACF;AAEA,IAAO,oBAAQ,IAAI,uBAAuB","sourcesContent":["import fs from 'node:fs';\nimport path from 'node:path';\nimport { EventEmitter } from 'events';\nimport { spawn, execFile, ChildProcessWithoutNullStreams, ChildProcess } from 'node:child_process';\n\nclass ClipboardEventListener extends EventEmitter {\n child: ChildProcess | undefined;\n constructor() {\n super();\n this.child = undefined;\n }\n\n startListening() {\n const { platform, arch } = process;\n const pathArr = [__dirname];\n\n if (path.basename(__dirname) === 'dist') {\n pathArr.push('..');\n }\n let exeFilename = `go-clipboard-${platform}-${arch}`;\n if (platform === 'win32') {\n exeFilename += '.exe';\n }\n pathArr.push(...['go-clipboard', 'binaries', exeFilename]);\n const exePath = path.join(...pathArr);\n console.log(exePath);\n \n if (!fs.existsSync(exePath)) {\n throw new Error(\n `Executable (${exeFilename}) not found, your platform is ${platform} ${arch} and may not be supported.`\n );\n }\n this.child = execFile(exePath);\n this.child.stdout?.on('data', (data: Buffer) => {\n const dataStr = data.toString();\n if (dataStr.trim() === 'TEXT_CHANGED') {\n this.emit('text');\n }\n if (dataStr.trim() === 'IMAGE_CHANGED') {\n this.emit('image');\n }\n });\n\n this.child.stderr?.on('data', (data: Buffer) => {\n this.emit('open', data.toString());\n });\n }\n\n stopListening() {\n this.emit('close');\n const res = this.child?.kill();\n return res;\n }\n}\n\nexport default new ClipboardEventListener();\n"]}
package/dist/index.mjs ADDED
@@ -0,0 +1,55 @@
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+ import { EventEmitter } from 'events';
4
+ import { execFile } from 'child_process';
5
+
6
+ // index.ts
7
+ var ClipboardEventListener = class extends EventEmitter {
8
+ constructor() {
9
+ super();
10
+ this.child = void 0;
11
+ }
12
+ startListening() {
13
+ var _a, _b;
14
+ const { platform, arch } = process;
15
+ const pathArr = [__dirname];
16
+ if (path.basename(__dirname) === "dist") {
17
+ pathArr.push("..");
18
+ }
19
+ let exeFilename = `go-clipboard-${platform}-${arch}`;
20
+ if (platform === "win32") {
21
+ exeFilename += ".exe";
22
+ }
23
+ pathArr.push(...["go-clipboard", "binaries", exeFilename]);
24
+ const exePath = path.join(...pathArr);
25
+ console.log(exePath);
26
+ if (!fs.existsSync(exePath)) {
27
+ throw new Error(
28
+ `Executable (${exeFilename}) not found, your platform is ${platform} ${arch} and may not be supported.`
29
+ );
30
+ }
31
+ this.child = execFile(exePath);
32
+ (_a = this.child.stdout) == null ? void 0 : _a.on("data", (data) => {
33
+ const dataStr = data.toString();
34
+ if (dataStr.trim() === "TEXT_CHANGED") {
35
+ this.emit("text");
36
+ }
37
+ if (dataStr.trim() === "IMAGE_CHANGED") {
38
+ this.emit("image");
39
+ }
40
+ });
41
+ (_b = this.child.stderr) == null ? void 0 : _b.on("data", (data) => {
42
+ this.emit("open", data.toString());
43
+ });
44
+ }
45
+ stopListening() {
46
+ var _a;
47
+ this.emit("close");
48
+ const res = (_a = this.child) == null ? void 0 : _a.kill();
49
+ return res;
50
+ }
51
+ };
52
+ var clipboard_default = new ClipboardEventListener();
53
+
54
+ export { clipboard_default as default };
55
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../index.ts"],"names":[],"mappings":";AAAA,OAAO,QAAQ;AACf,OAAO,UAAU;AACjB,SAAS,oBAAoB;AAC7B,SAAgB,gBAA8D;AAE9E,IAAM,yBAAN,cAAqC,aAAa;AAAA,EAEhD,cAAc;AACZ,UAAM;AACN,SAAK,QAAQ;AAAA,EACf;AAAA,EAEA,iBAAiB;AAZnB;AAaI,UAAM,EAAE,UAAU,KAAK,IAAI;AAC3B,UAAM,UAAU,CAAC,SAAS;AAE1B,QAAI,KAAK,SAAS,SAAS,MAAM,QAAQ;AACvC,cAAQ,KAAK,IAAI;AAAA,IACnB;AACA,QAAI,cAAc,gBAAgB,YAAY;AAC9C,QAAI,aAAa,SAAS;AACxB,qBAAe;AAAA,IACjB;AACA,YAAQ,KAAK,GAAG,CAAC,gBAAgB,YAAY,WAAW,CAAC;AACzD,UAAM,UAAU,KAAK,KAAK,GAAG,OAAO;AACpC,YAAQ,IAAI,OAAO;AAEnB,QAAI,CAAC,GAAG,WAAW,OAAO,GAAG;AAC3B,YAAM,IAAI;AAAA,QACR,eAAe,4CAA4C,YAAY;AAAA,MACzE;AAAA,IACF;AACA,SAAK,QAAQ,SAAS,OAAO;AAC7B,eAAK,MAAM,WAAX,mBAAmB,GAAG,QAAQ,CAAC,SAAiB;AAC9C,YAAM,UAAU,KAAK,SAAS;AAC9B,UAAI,QAAQ,KAAK,MAAM,gBAAgB;AACrC,aAAK,KAAK,MAAM;AAAA,MAClB;AACA,UAAI,QAAQ,KAAK,MAAM,iBAAiB;AACtC,aAAK,KAAK,OAAO;AAAA,MACnB;AAAA,IACF;AAEA,eAAK,MAAM,WAAX,mBAAmB,GAAG,QAAQ,CAAC,SAAiB;AAC9C,WAAK,KAAK,QAAQ,KAAK,SAAS,CAAC;AAAA,IACnC;AAAA,EACF;AAAA,EAEA,gBAAgB;AAhDlB;AAiDI,SAAK,KAAK,OAAO;AACjB,UAAM,OAAM,UAAK,UAAL,mBAAY;AACxB,WAAO;AAAA,EACT;AACF;AAEA,IAAO,oBAAQ,IAAI,uBAAuB","sourcesContent":["import fs from 'node:fs';\nimport path from 'node:path';\nimport { EventEmitter } from 'events';\nimport { spawn, execFile, ChildProcessWithoutNullStreams, ChildProcess } from 'node:child_process';\n\nclass ClipboardEventListener extends EventEmitter {\n child: ChildProcess | undefined;\n constructor() {\n super();\n this.child = undefined;\n }\n\n startListening() {\n const { platform, arch } = process;\n const pathArr = [__dirname];\n\n if (path.basename(__dirname) === 'dist') {\n pathArr.push('..');\n }\n let exeFilename = `go-clipboard-${platform}-${arch}`;\n if (platform === 'win32') {\n exeFilename += '.exe';\n }\n pathArr.push(...['go-clipboard', 'binaries', exeFilename]);\n const exePath = path.join(...pathArr);\n console.log(exePath);\n \n if (!fs.existsSync(exePath)) {\n throw new Error(\n `Executable (${exeFilename}) not found, your platform is ${platform} ${arch} and may not be supported.`\n );\n }\n this.child = execFile(exePath);\n this.child.stdout?.on('data', (data: Buffer) => {\n const dataStr = data.toString();\n if (dataStr.trim() === 'TEXT_CHANGED') {\n this.emit('text');\n }\n if (dataStr.trim() === 'IMAGE_CHANGED') {\n this.emit('image');\n }\n });\n\n this.child.stderr?.on('data', (data: Buffer) => {\n this.emit('open', data.toString());\n });\n }\n\n stopListening() {\n this.emit('close');\n const res = this.child?.kill();\n return res;\n }\n}\n\nexport default new ClipboardEventListener();\n"]}
@@ -0,0 +1,9 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <module type="WEB_MODULE" version="4">
3
+ <component name="Go" enabled="true" />
4
+ <component name="NewModuleRootManager">
5
+ <content url="file://$MODULE_DIR$" />
6
+ <orderEntry type="inheritedJdk" />
7
+ <orderEntry type="sourceFolder" forTests="false" />
8
+ </component>
9
+ </module>
@@ -0,0 +1,8 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <project version="4">
3
+ <component name="ProjectModuleManager">
4
+ <modules>
5
+ <module fileurl="file://$PROJECT_DIR$/.idea/go-clipboard.iml" filepath="$PROJECT_DIR$/.idea/go-clipboard.iml" />
6
+ </modules>
7
+ </component>
8
+ </project>
@@ -0,0 +1,6 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <project version="4">
3
+ <component name="VcsDirectoryMappings">
4
+ <mapping directory="$PROJECT_DIR$/.." vcs="Git" />
5
+ </component>
6
+ </project>
@@ -0,0 +1,8 @@
1
+ build:
2
+ GOOS=darwin GORACH=arm64 go build -o binaries/go-clipboard-darwin-arm64
3
+ GOOS=darwin GORACH=amd64 go build -o binaries/go-clipboard-darwin-x64
4
+ GOOS=linux GORACH=386 go build -o binaries/go-clipboard-linux-386
5
+ GOOS=linux GORACH=arm go build -o binaries/go-clipboard-linux-arm
6
+ GOOS=linux GORACH=arm64 go build -o binaries/go-clipboard-linux-arm64
7
+ GOOS=linux GORACH=amd64 go build -o binaries/go-clipboard-linux-x64
8
+ GOOS=windows GORACH=amd64 go build -o binaries/go-clipboard-win32-x64.exe
@@ -0,0 +1,11 @@
1
+ # Example for IPC between Golang and Nodejs on the context of Clipboard Update Listening
2
+
3
+ To run this example, within this folder
4
+
5
+ ```bash
6
+ go build client.go && node server
7
+ ```
8
+
9
+ Then copy some text or take a screenshot and see what's printed to stdout and image saved to disk.
10
+
11
+ The screenshot image is updated for every screenshot you take.
@@ -0,0 +1,34 @@
1
+ const { execFile } = require("node:child_process");
2
+ const fs = require("fs");
3
+
4
+ const execPath = "./client";
5
+ // const childWriteText = execFile(execPath, ["WRITE_TEXT"]);
6
+ // childWriteText.stdin.write("huakun zui shuai")
7
+ // childWriteText.stdin.end()
8
+ // childWriteText.stdout.on('data', (d) => {
9
+ // console.log(d);
10
+ // })
11
+
12
+ const childWriteImage = execFile(execPath, ["WRITE_IMAGE"]);
13
+ const base64img =
14
+ "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";
15
+ childWriteImage.stdin.write(base64img);
16
+ childWriteImage.stdin.end();
17
+
18
+ // const childWriteImage = execFile(execPath, ["WRITE_IMAGE"]);
19
+ // const base64img = "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"
20
+ // childWriteImage.stdin.write(base64img)
21
+ // childWriteImage.stdin.end()
22
+
23
+ // const childtext = execFile(execPath, ["READ_TEXT"]);
24
+ // childtext.stdout.on("data", (data) => {
25
+ // console.log(Buffer.from(data, 'base64').toString());
26
+ // });
27
+
28
+ setTimeout(() => {
29
+ const childimg = execFile(execPath, ["READ_IMAGE"]);
30
+ childimg.stdout.on("data", (data) => {
31
+ const imgBuf = Buffer.from(data, "base64");
32
+ fs.writeFileSync("test.png", imgBuf);
33
+ });
34
+ }, 1000);
@@ -0,0 +1,131 @@
1
+ package main
2
+
3
+ import (
4
+ "bufio"
5
+ "context"
6
+ "encoding/base64"
7
+ "fmt"
8
+ "log"
9
+ "net"
10
+ "os"
11
+ "sync"
12
+
13
+ "golang.design/x/clipboard"
14
+ )
15
+
16
+ // All possible ways to call this program
17
+ // 1. client (set up TCP socket client to connect to default port 8090)
18
+ // 2. client 9999 (set up TCP socket client to connect to custom port 9999)
19
+ // 3. client READ_TEXT (Read Text from clipboard and output to stdout)
20
+ // 4. client READ_IMAGE (Read Image from clipboard and output to stdout in base64 encoding, whoever is reading this must decode it into bytes)
21
+ // 5. client WRITE_TEXT (Write Text to clipboard, data passed in through stdin, in base64 format (since there may be '\n' in raw data, base64 makes sure this doesn't exist))
22
+ // 6. client WRITE_IMAGE (Write Image to clipboard, data passed in through stdin, read as string, in base64 format. Have to decode into buffer before saving to clipboard)
23
+ func main() {
24
+ err := clipboard.Init()
25
+ var port = "8090"
26
+ if len(os.Args) == 2 {
27
+ if os.Args[1] == "READ_TEXT" {
28
+ cbText := clipboard.Read(clipboard.FmtText)
29
+ fmt.Print(base64.StdEncoding.EncodeToString(cbText))
30
+ return
31
+ }
32
+
33
+ if os.Args[1] == "READ_IMAGE" {
34
+ cbImage := clipboard.Read(clipboard.FmtImage)
35
+ fmt.Println(base64.StdEncoding.EncodeToString(cbImage))
36
+ return
37
+ }
38
+
39
+ if os.Args[1] == "WRITE_TEXT" {
40
+ reader := bufio.NewReader(os.Stdin)
41
+ text, _ := reader.ReadString('\n')
42
+ clipboard.Write(clipboard.FmtText, []byte(text))
43
+ return
44
+ }
45
+
46
+ // This works with base64 string
47
+ if os.Args[1] == "WRITE_IMAGE" {
48
+ reader := bufio.NewReader(os.Stdin)
49
+ data, _ := reader.ReadString('\n')
50
+ imgBuf, _ := base64.StdEncoding.DecodeString(data)
51
+ clipboard.Write(clipboard.FmtImage, imgBuf)
52
+ return
53
+ }
54
+ // If none of the above are true, then parent process is calling me for setting up a TCP socket connection
55
+ // for bidirectional communication (for clipboard listening)
56
+ // So, the second command line arg is port
57
+ port = os.Args[1]
58
+ }
59
+
60
+ // The rest of the code is for clipboard watching
61
+ // connect TCP socket to server
62
+ address := fmt.Sprintf("localhost:%s", port)
63
+ // setup a long connection, but not used to transfer clipboard data, only for message notifications
64
+ con, err := net.Dial("tcp", address)
65
+ checkErr(err)
66
+ defer con.Close()
67
+
68
+ // since we continuously watch clipboard update, use wait group keep go routines running
69
+ var wg sync.WaitGroup
70
+ // * this is how to write send message to parent process using stdout
71
+ msg := "connection from go"
72
+ _, err = con.Write([]byte(msg))
73
+ checkErr(err)
74
+
75
+ textCh := clipboard.Watch(context.TODO(), clipboard.FmtText)
76
+ imgCh := clipboard.Watch(context.TODO(), clipboard.FmtImage)
77
+
78
+ // the text and image clipboard listeners will send message using separate socket connection
79
+ // because closing socket channel is required for the socket server to know when to wrap up received data
80
+ // large data chunks have to be sent in multiple TCP packages which have a size limit
81
+ // the socket server keeps getting data, accumulating data by concatenating packets received, but when to stop?
82
+ // socket server wraps up the data chunks received upon connection close
83
+ // this is why we use short socket connections to send data
84
+ // con (the long socket connection) is only used for sending notifications instead of data
85
+ wg.Add(1)
86
+ go func() {
87
+ for data := range textCh {
88
+ textCon, err := net.Dial("tcp", address)
89
+ checkErr(err)
90
+ // fmt.Println("TEXT_CHANGED") // write TEXT_CHANGED to stdout for parent process to detect clipboard text update
91
+ // cbText := clipboard.Read(clipboard.FmtText)
92
+ payload := "TEXT_CHANGED:" + base64.StdEncoding.EncodeToString(data)
93
+ textCon.Write([]byte(payload))
94
+ textCon.Close()
95
+ }
96
+
97
+ }()
98
+ wg.Add(1)
99
+ go func() {
100
+ for data := range imgCh {
101
+ imgCon, err := net.Dial("tcp", address)
102
+ checkErr(err)
103
+ fmt.Println("IMAGE_CHANGED") // write TEXT_CHANGED to stdout for parent process to detect clipboard text update
104
+ base64Img := base64.StdEncoding.EncodeToString(data)
105
+ payload := "IMAGE_CHANGED:" + base64Img
106
+ log.Println(len(payload))
107
+ imgCon.Write([]byte(payload))
108
+ imgCon.Close()
109
+ }
110
+ }()
111
+
112
+ // this is used to receive data from socket server / parent process
113
+ // doesn't need to be large, no massive data will be sent
114
+ reply := make([]byte, 1024)
115
+ wg.Add(1)
116
+ go func() {
117
+ for {
118
+ _, err = con.Read(reply)
119
+ checkErr(err)
120
+ data := string(reply)
121
+ fmt.Println(data)
122
+ }
123
+ }()
124
+ wg.Wait()
125
+ }
126
+
127
+ func checkErr(err error) {
128
+ if err != nil {
129
+ log.Fatal(err)
130
+ }
131
+ }
@@ -0,0 +1,48 @@
1
+ const net = require("net");
2
+ const fs = require("fs");
3
+ const path = require("path");
4
+ const { execFile } = require("node:child_process");
5
+
6
+ const server = net.createServer(function (con) {
7
+ // client connected
8
+ let data = "";
9
+
10
+ // * demo of sending message to client,
11
+ // con.write("close");
12
+
13
+ /**
14
+ * Accumulate data by concatenating chunks of received data
15
+ */
16
+ con.on("data", (packet) => {
17
+ data += packet.toString();
18
+ });
19
+
20
+ /**
21
+ * When socket connection closes, wrap up received data
22
+ */
23
+ con.on("close", () => {
24
+ const strData = data.toString();
25
+ const subStr = strData.substring(0, 14);
26
+ if (subStr.includes("TEXT_CHANGED:")) {
27
+ console.log(
28
+ `Text Changed: ${Buffer.from(
29
+ strData.substring(13),
30
+ "base64"
31
+ ).toString()}`
32
+ );
33
+ } else if (subStr.includes("IMAGE_CHANGED:")) {
34
+ const imageBase64 = strData.substring(14).toString("base64");
35
+ console.log(`Image Changed`);
36
+ fs.writeFileSync('test.png', Buffer.from(imageBase64, "base64"));
37
+ }
38
+ });
39
+ });
40
+
41
+ server.listen(8090, function () {
42
+ console.log("server is listening");
43
+ const execPath = path.join(__dirname, "client");
44
+ const child = execFile(execPath, [server.address().port]);
45
+ child.stdout.on("data", (data) => {
46
+ console.log("Received data from client socket stdout:\n" + data);
47
+ });
48
+ });
@@ -0,0 +1,131 @@
1
+ package main
2
+
3
+ import (
4
+ "bufio"
5
+ "context"
6
+ "encoding/base64"
7
+ "fmt"
8
+ "log"
9
+ "net"
10
+ "os"
11
+ "sync"
12
+
13
+ "golang.design/x/clipboard"
14
+ )
15
+
16
+ // All possible ways to call this program
17
+ // 1. client (set up TCP socket client to connect to default port 8090)
18
+ // 2. client 9999 (set up TCP socket client to connect to custom port 9999)
19
+ // 3. client READ_TEXT (Read Text from clipboard and output to stdout)
20
+ // 4. client READ_IMAGE (Read Image from clipboard and output to stdout in base64 encoding, whoever is reading this must decode it into bytes)
21
+ // 5. client WRITE_TEXT (Write Text to clipboard, data passed in through stdin, in base64 format (since there may be '\n' in raw data, base64 makes sure this doesn't exist))
22
+ // 6. client WRITE_IMAGE (Write Image to clipboard, data passed in through stdin, read as string, in base64 format. Have to decode into buffer before saving to clipboard)
23
+ func main() {
24
+ err := clipboard.Init()
25
+ var port = "8090"
26
+ if len(os.Args) == 2 {
27
+ if os.Args[1] == "READ_TEXT" {
28
+ cbText := clipboard.Read(clipboard.FmtText)
29
+ fmt.Print(base64.StdEncoding.EncodeToString(cbText))
30
+ return
31
+ }
32
+
33
+ if os.Args[1] == "READ_IMAGE" {
34
+ cbImage := clipboard.Read(clipboard.FmtImage)
35
+ fmt.Println(base64.StdEncoding.EncodeToString(cbImage))
36
+ return
37
+ }
38
+
39
+ if os.Args[1] == "WRITE_TEXT" {
40
+ reader := bufio.NewReader(os.Stdin)
41
+ text, _ := reader.ReadString('\n')
42
+ clipboard.Write(clipboard.FmtText, []byte(text))
43
+ return
44
+ }
45
+
46
+ // This works with base64 string
47
+ if os.Args[1] == "WRITE_IMAGE" {
48
+ reader := bufio.NewReader(os.Stdin)
49
+ data, _ := reader.ReadString('\n')
50
+ imgBuf, _ := base64.StdEncoding.DecodeString(data)
51
+ clipboard.Write(clipboard.FmtImage, imgBuf)
52
+ return
53
+ }
54
+ // If none of the above are true, then parent process is calling me for setting up a TCP socket connection
55
+ // for bidirectional communication (for clipboard listening)
56
+ // So, the second command line arg is port
57
+ port = os.Args[1]
58
+ }
59
+
60
+ // The rest of the code is for clipboard watching
61
+ // connect TCP socket to server
62
+ address := fmt.Sprintf("localhost:%s", port)
63
+ // setup a long connection, but not used to transfer clipboard data, only for message notifications
64
+ con, err := net.Dial("tcp", address)
65
+ checkErr(err)
66
+ defer con.Close()
67
+
68
+ // since we continuously watch clipboard update, use wait group keep go routines running
69
+ var wg sync.WaitGroup
70
+ // * this is how to write send message to parent process using stdout
71
+ msg := "connection from go"
72
+ _, err = con.Write([]byte(msg))
73
+ checkErr(err)
74
+
75
+ textCh := clipboard.Watch(context.TODO(), clipboard.FmtText)
76
+ imgCh := clipboard.Watch(context.TODO(), clipboard.FmtImage)
77
+
78
+ // the text and image clipboard listeners will send message using separate socket connection
79
+ // because closing socket channel is required for the socket server to know when to wrap up received data
80
+ // large data chunks have to be sent in multiple TCP packages which have a size limit
81
+ // the socket server keeps getting data, accumulating data by concatenating packets received, but when to stop?
82
+ // socket server wraps up the data chunks received upon connection close
83
+ // this is why we use short socket connections to send data
84
+ // con (the long socket connection) is only used for sending notifications instead of data
85
+ wg.Add(1)
86
+ go func() {
87
+ for data := range textCh {
88
+ textCon, err := net.Dial("tcp", address)
89
+ checkErr(err)
90
+ // fmt.Println("TEXT_CHANGED") // write TEXT_CHANGED to stdout for parent process to detect clipboard text update
91
+ // cbText := clipboard.Read(clipboard.FmtText)
92
+ payload := "TEXT_CHANGED:" + base64.StdEncoding.EncodeToString(data)
93
+ textCon.Write([]byte(payload))
94
+ textCon.Close()
95
+ }
96
+
97
+ }()
98
+ wg.Add(1)
99
+ go func() {
100
+ for data := range imgCh {
101
+ imgCon, err := net.Dial("tcp", address)
102
+ checkErr(err)
103
+ fmt.Println("IMAGE_CHANGED") // write TEXT_CHANGED to stdout for parent process to detect clipboard text update
104
+ base64Img := base64.StdEncoding.EncodeToString(data)
105
+ payload := "IMAGE_CHANGED:" + base64Img
106
+ log.Println(len(payload))
107
+ imgCon.Write([]byte(payload))
108
+ imgCon.Close()
109
+ }
110
+ }()
111
+
112
+ // this is used to receive data from socket server / parent process
113
+ // doesn't need to be large, no massive data will be sent
114
+ reply := make([]byte, 1024)
115
+ wg.Add(1)
116
+ go func() {
117
+ for {
118
+ _, err = con.Read(reply)
119
+ checkErr(err)
120
+ data := string(reply)
121
+ fmt.Println(data)
122
+ }
123
+ }()
124
+ wg.Wait()
125
+ }
126
+
127
+ func checkErr(err error) {
128
+ if err != nil {
129
+ log.Fatal(err)
130
+ }
131
+ }
package/package.json ADDED
@@ -0,0 +1,47 @@
1
+ {
2
+ "name": "@crosscopy/clipboard",
3
+ "version": "0.1.0-beta",
4
+ "description": "Cross Platform Clipboard listener that detects both text and image update in clipboard",
5
+ "source": "index.ts",
6
+ "types": "./dist/index.d.ts",
7
+ "main": "./dist/index.js",
8
+ "module": "./dist/index.mjs",
9
+ "type": "commonjs",
10
+ "scripts": {
11
+ "build": "tsup",
12
+ "demo": "ts-node demo.ts",
13
+ "test": "jest ./__tests__"
14
+ },
15
+ "repository": {
16
+ "type": "git",
17
+ "url": "git+https://github.com/HuakunShen/general-clipboard-listener.git"
18
+ },
19
+ "keywords": [
20
+ "clipboard"
21
+ ],
22
+ "author": "Huakun Shen",
23
+ "license": "ISC",
24
+ "bugs": {
25
+ "url": "https://github.com/HuakunShen/general-clipboard-listener/issues"
26
+ },
27
+ "homepage": "https://github.com/HuakunShen/general-clipboard-listener#readme",
28
+ "dependencies": {
29
+ "events": "^3.3.0",
30
+ "execa": "^6.1.0"
31
+ },
32
+ "devDependencies": {
33
+ "@jest/globals": "^29.3.1",
34
+ "@types/jest": "^29.2.4",
35
+ "@types/node": "^18.11.7",
36
+ "jest": "^29.3.1",
37
+ "ts-jest": "^29.0.3",
38
+ "ts-node": "^10.9.1",
39
+ "tsup": "^6.3.0",
40
+ "typescript": "^4.8.4"
41
+ },
42
+ "files": [
43
+ "dist",
44
+ "go-clipboard",
45
+ "README.md"
46
+ ]
47
+ }