Skip to content

Commit f78c04e

Browse files
committed
feat(full-screen): add mousetrap record poc
1 parent 3dfaeb8 commit f78c04e

File tree

4 files changed

+258
-47
lines changed

4 files changed

+258
-47
lines changed

Extensions/full-screen/dist/fullScreen.js

Lines changed: 50 additions & 47 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Extensions/full-screen/src/app.tsx

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import { Config, Settings } from "./types/fullscreen";
2121
import WebAPI from "./services/web-api";
2222
import showWhatsNew from "./services/whats-new";
2323
import { getHtmlContent } from "./services/html-creator";
24+
import { initMoustrapRecord } from "./services/mousetrap-record";
2425

2526
import SeekableProgressBar from "./ui/components/ProgressBar/ProgressBar";
2627
import SeekableVolumeBar from "./ui/components/VolumeBar/VolumeBar";
@@ -47,6 +48,7 @@ async function main() {
4748

4849
// Start from here
4950
showWhatsNew();
51+
initMoustrapRecord(Spicetify.Mousetrap);
5052

5153
if (CFM.getGlobal("activationTypes") !== "btns") {
5254
if (CFM.getGlobal("keyActivation") !== "def") Spicetify.Mousetrap.bind("t", openwithTV);
@@ -259,6 +261,12 @@ async function main() {
259261
}
260262
}
261263

264+
function recordSequence() {
265+
Spicetify.Mousetrap.record(function (sequence) {
266+
// sequence is an array like ['ctrl+k', 'c']
267+
console.log("You pressed: " + sequence.join(" "));
268+
});
269+
}
262270
// Set the timeout to show upnext or hide when song ends
263271
let upnextTimer: NodeJS.Timeout,
264272
upNextShown = false;
Lines changed: 199 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,199 @@
1+
/**
2+
* This extension allows you to record a sequence using Mousetrap.
3+
*
4+
* @author Dan Tao <[email protected]>
5+
*/
6+
export function initMoustrapRecord(Mousetrap) {
7+
/**
8+
* the sequence currently being recorded
9+
*
10+
* @type {Array}
11+
*/
12+
var _recordedSequence = [],
13+
/**
14+
* a callback to invoke after recording a sequence
15+
*
16+
* @type {Function|null}
17+
*/
18+
_recordedSequenceCallback = null,
19+
/**
20+
* a list of all of the keys currently held down
21+
*
22+
* @type {Array}
23+
*/
24+
_currentRecordedKeys = [],
25+
/**
26+
* temporary state where we remember if we've already captured a
27+
* character key in the current combo
28+
*
29+
* @type {boolean}
30+
*/
31+
_recordedCharacterKey = false,
32+
/**
33+
* a handle for the timer of the current recording
34+
*
35+
* @type {null|number}
36+
*/
37+
_recordTimer = null,
38+
/**
39+
* the original handleKey method to override when Mousetrap.record() is
40+
* called
41+
*
42+
* @type {Function}
43+
*/
44+
_origHandleKey = Mousetrap.prototype.handleKey;
45+
46+
/**
47+
* handles a character key event
48+
*
49+
* @param {string} character
50+
* @param {Array} modifiers
51+
* @param {Event} e
52+
* @returns void
53+
*/
54+
function _handleKey(character, modifiers, e) {
55+
var self = this;
56+
57+
if (!self.recording) {
58+
_origHandleKey.apply(self, arguments);
59+
return;
60+
}
61+
62+
// remember this character if we're currently recording a sequence
63+
if (e.type == "keydown") {
64+
if (character.length === 1 && _recordedCharacterKey) {
65+
_recordCurrentCombo();
66+
}
67+
68+
for (i = 0; i < modifiers.length; ++i) {
69+
_recordKey(modifiers[i]);
70+
}
71+
_recordKey(character);
72+
73+
// once a key is released, all keys that were held down at the time
74+
// count as a keypress
75+
} else if (e.type == "keyup" && _currentRecordedKeys.length > 0) {
76+
_recordCurrentCombo();
77+
}
78+
}
79+
80+
/**
81+
* marks a character key as held down while recording a sequence
82+
*
83+
* @param {string} key
84+
* @returns void
85+
*/
86+
function _recordKey(key) {
87+
var i;
88+
89+
// one-off implementation of Array.indexOf, since IE6-9 don't support it
90+
for (i = 0; i < _currentRecordedKeys.length; ++i) {
91+
if (_currentRecordedKeys[i] === key) {
92+
return;
93+
}
94+
}
95+
96+
_currentRecordedKeys.push(key);
97+
98+
if (key.length === 1) {
99+
_recordedCharacterKey = true;
100+
}
101+
}
102+
103+
/**
104+
* marks whatever key combination that's been recorded so far as finished
105+
* and gets ready for the next combo
106+
*
107+
* @returns void
108+
*/
109+
function _recordCurrentCombo() {
110+
_recordedSequence.push(_currentRecordedKeys);
111+
_currentRecordedKeys = [];
112+
_recordedCharacterKey = false;
113+
_restartRecordTimer();
114+
}
115+
116+
/**
117+
* ensures each combo in a sequence is in a predictable order and formats
118+
* key combos to be '+'-delimited
119+
*
120+
* modifies the sequence in-place
121+
*
122+
* @param {Array} sequence
123+
* @returns void
124+
*/
125+
function _normalizeSequence(sequence) {
126+
var i;
127+
128+
for (i = 0; i < sequence.length; ++i) {
129+
sequence[i].sort(function (x, y) {
130+
// modifier keys always come first, in alphabetical order
131+
if (x.length > 1 && y.length === 1) {
132+
return -1;
133+
} else if (x.length === 1 && y.length > 1) {
134+
return 1;
135+
}
136+
137+
// character keys come next (list should contain no duplicates,
138+
// so no need for equality check)
139+
return x > y ? 1 : -1;
140+
});
141+
142+
sequence[i] = sequence[i].join("+");
143+
}
144+
}
145+
146+
/**
147+
* finishes the current recording, passes the recorded sequence to the stored
148+
* callback, and sets Mousetrap.handleKey back to its original function
149+
*
150+
* @returns void
151+
*/
152+
function _finishRecording() {
153+
if (_recordedSequenceCallback) {
154+
_normalizeSequence(_recordedSequence);
155+
_recordedSequenceCallback(_recordedSequence);
156+
}
157+
158+
// reset all recorded state
159+
_recordedSequence = [];
160+
_recordedSequenceCallback = null;
161+
_currentRecordedKeys = [];
162+
}
163+
164+
/**
165+
* called to set a 1 second timeout on the current recording
166+
*
167+
* this is so after each key press in the sequence the recording will wait for
168+
* 1 more second before executing the callback
169+
*
170+
* @returns void
171+
*/
172+
function _restartRecordTimer() {
173+
clearTimeout(_recordTimer);
174+
_recordTimer = setTimeout(_finishRecording, 1000);
175+
}
176+
177+
/**
178+
* records the next sequence and passes it to a callback once it's
179+
* completed
180+
*
181+
* @param {Function} callback
182+
* @returns void
183+
*/
184+
Mousetrap.prototype.record = function (callback) {
185+
var self = this;
186+
self.recording = true;
187+
_recordedSequenceCallback = function () {
188+
self.recording = false;
189+
callback.apply(self, arguments);
190+
};
191+
};
192+
193+
Mousetrap.prototype.handleKey = function () {
194+
var self = this;
195+
_handleKey.apply(self, arguments);
196+
};
197+
198+
Mousetrap.init();
199+
}

shared/types/spicetify.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -782,6 +782,7 @@ declare namespace Spicetify {
782782
* so new extension should use this library instead.
783783
*/
784784
const Mousetrap: {
785+
record(arg0: (sequence: any) => void): unknown;
785786
bind: (
786787
keys: string | string[],
787788
callback: (event: KeyboardEvent, combo: string) => void,

0 commit comments

Comments
 (0)