Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 9 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,9 @@
/.idea
/DistributionTool.exe
.DS_Store
.idea/**
/DistributionTool*
.vscode/**
package.json
package-lock.json
.history
__exclude
.editorconfig
3 changes: 3 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[submodule "warudo.streamdeck.sdPlugin/libs"]
path = warudo.streamdeck.sdPlugin/libs
url = git@github.com:elgatosf/streamdeck-javascript-sdk.git
Binary file modified Release/warudo.streamdeck.streamDeckPlugin
Binary file not shown.
31 changes: 18 additions & 13 deletions warudo.streamdeck.sdPlugin/en.json
Original file line number Diff line number Diff line change
@@ -1,15 +1,20 @@
{
"Description": "Use this to create your own plugin",
"Name": "Stream Deck Template",
"Category": "Templates",
"com.elgato.template.action": {
"Name": "Action",
"Tooltip": "This is the only action in this plugin"
},
"Localization": {
"More info": "More info",
"Message": "Message",
"Click Me": "Click Me",
"Button": "Button"
}
"Description": "Integration with Warudo, a 3D livestreaming software for VTubers.",
"Name": "Warudo",
"Category": "Warudo",
"warudo.trigger": {
"Name": "Trigger",
"Tooltip": "Invoke a trigger in Warudo"
},
"warudo.toggle": {
"Name": "Toggle",
"Tooltip": "Activate a toggle in Warudo"
},
"warudo.message": {
"Name": "Message",
"Tooltip": "Send a message to Warudo"
},
"Localization": {
"Receiver name": "Receiver name"
}
}
33 changes: 18 additions & 15 deletions warudo.streamdeck.sdPlugin/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -2,25 +2,28 @@
<html>

<head>
<title>warudo.streamdeck</title>
<meta charset="utf-8" />
<title>warudo.streamdeck</title>
<meta charset="utf-8"/>
<style>
canvas {
background-color: transparent;
}
</style>
</head>

<body>
<!-- libs -->
<script src="libs/js/utils.js"></script>
<script src="libs/js/constants.js"></script>
<script src="libs/js/events.js"></script>
<script src="libs/js/api.js"></script>
<script src="libs/js/stream-deck.js"></script>
<script src="libs/js/action.js"></script>
<script src="libs/js/timers.js"></script>

<!-- app -->
<script src="js/index.js"></script>

<!-- actions -->
<!-- Stream Deck Libs -->
<script src="libs/js/constants.js"></script>
<script src="libs/js/prototypes.js"></script>
<script src="libs/js/timers.js"></script>
<script src="libs/js/utils.js"></script>
<script src="libs/js/events.js"></script>
<script src="libs/js/api.js"></script>
<script src="libs/js/stream-deck.js"></script>
<script src="libs/js/action.js"></script>

<!-- Plugin Source -->
<script src="index.js"></script>
</body>

</html>
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
/// <reference path="libs/js/stream-deck.js" />
/// <reference path="libs/js/action.js" />
/// <reference path="libs/js/utils.js" />

/* GLOBALS */

Expand All @@ -26,14 +25,8 @@ let messages = {}; // context -> message
let toggleStates = {}; // receiver name -> state

const warudoEndpoint = 'ws://localhost:19069';
// Always try to keep the websocket connection open
let ws = null;
// Always try to keep the websocket connection open
setInterval(() => {
if (ws.readyState !== WebSocket.OPEN && ws.readyState !== WebSocket.CONNECTING) {
setupWebsocket();
}
}, 1000);
let _tryconnect = undefined;

const setupWebsocket = () => {
toggleStates = {};
Expand All @@ -44,7 +37,9 @@ const setupWebsocket = () => {
}
ws.onclose = function() {
console.log('Disconnected from Warudo');
setupWebsocket();
if (_tryconnect) {
try_connect();
}
}
ws.onmessage = function(msg) {
console.log('Message from Warudo', msg);
Expand All @@ -67,7 +62,25 @@ const setupWebsocket = () => {
}
}
}
setupWebsocket();

const try_connect = () => {
_tryconnect = window.setTimeout(() => {
setupWebsocket();
}, 1000);
}

const cancel_try_connect = () => {
window.clearTimeout(_tryconnect);
_tryconnect = undefined;
}

const connecting = () => {
return (ws) && (ws.readyState == WebSocket.CONNECTING);
}

const connected = () => {
return (!connecting()) && (ws && (ws.readyState === WebSocket.OPEN));
}

const updateToggleStates = () => {
// Find all toggle actions and update their state
Expand Down Expand Up @@ -129,15 +142,26 @@ actions.forEach(action => {
action.onKeyDown(async jsn => {
console.log('onKeyDown', jsn.context);
getSettingsAndRun(jsn.context, () => {
const data = {
receiverName: receiverNames[jsn.context],
};
if (action === messageAction) {
data.message = messages[jsn.context] || '';
try {
const data = {
receiverName: receiverNames[jsn.context],
};
if (action === messageAction) {
data.message = messages[jsn.context] || '';
}

if (!connected()) {
$SD.showAlert(jsn.context);
return;
}

const payload = { action: actionType, data }
console.log('Sending payload', payload);
ws.send(JSON.stringify(payload));
$SD.showOk(jsn.context);
} catch (e) {
$SD.showAlert(jsn.context);
}
const payload = { action: actionType, data }
console.log('Sending payload', payload)
ws.send(JSON.stringify(payload));
});
});

Expand Down Expand Up @@ -179,119 +203,15 @@ actions.forEach(action => {

});

/* STREAMDECK RELATED */

// In this example, we're monitoring a couple of apps
// that we've added to the manifest.json file
// under the "monitoredApps" key
// if one of the monitored apps is launched or terminated,
// we'll update the key images and send the running apps list
// to the property inspector

$SD.onApplicationDidLaunch((jsn) => {
const {event, payload} = jsn;
console.log('onApplicationDidLaunch', jsn, event, payload);
// our monitored app settings (in manifest.json) are case-sensitive
// so we need to capitalize the app name to match
const app = Utils.capitalize(Utils.getApplicationName(payload));
// there should be a corresponding image in the images folder
const img = `images/${payload.application}.png`;
// try to load it
Utils.loadImagePromise(img).then(results => {
MCONTEXTS.forEach(c => updateKeyImages(c, img));
});
// add the monitored app to our running apps list
if(!MPLUGINDATA.runningApps.includes(app)) {MPLUGINDATA.runningApps.push(app);};
// send the running apps list to the property inspector
MCONTEXTS.forEach(updateRunningApps);
console.log('Warudo is open, attempting to connect...', jsn, event, payload);
if (!connected()) {
try_connect();
}
});

$SD.onApplicationDidTerminate(({context,payload}) => {
console.log('onApplicationDidTerminate', payload, payload.application);
// our monitored app settings (in manifest.json) are case-sensitive
// so we need to capitalize the app name to match
const app = Utils.capitalize(Utils.getApplicationName(payload));
// remove the terminated app from our running apps list
MPLUGINDATA.runningApps = MPLUGINDATA.runningApps.filter(item => item !== app);
// there should be a corresponding image in the images folder
const img = `images/${payload.application}.png`;
// overlay our terminated image on top of the terminated app image
const arrImages = [img, 'images/terminated.png'];
// try to load them
Utils.loadImages(arrImages).then(images => {
// if successfully loaded, merge them
Utils.mergeImages(images).then(b64 => {
// and update the key images
MCONTEXTS.forEach(c => updateKeyImages(c, b64));
setTimeout(() => {
// after 1.5 seconds, reset the key images
MCONTEXTS.forEach(c => updateKeyImages(c, `images/default.png`));
}, 1500);
});
});
// update the running apps list in the property inspector
MCONTEXTS.forEach(updateRunningApps);
console.log('Warudo has quit, stopping any attempts at connecting...', payload, payload.application);
cancel_try_connect();
});

/** HELPERS */

const updateRunningApps = (context) => {
console.log('updateRunningApps', MPLUGINDATA.runningApps);
// $SD.sendToPropertyInspector(context, {runningApps: MPLUGINDATA.runningApps});
sendToPropertyInspector(context, {runningApps: MPLUGINDATA.runningApps});
};

const updateKeyImages = (context, url) => {
// console.log('updateKeyImages', context);
$SD.setImage(context, url);
};


/** UTILITIES USED IN THIS DEMO */

Utils.loadImagePromise = url =>
new Promise(resolve => {
const img = new Image();
img.onload = () => resolve({url, img, status: 'ok'});
img.onerror = () => resolve({url, img, status: 'error'});
img.src = url;
});

Utils.loadImages = arrayOfUrls => Promise.all(arrayOfUrls.map(Utils.loadImagePromise));

Utils.capitalize = str => {
return str.charAt(0).toUpperCase() + str.slice(1);
};

Utils.getApplicationName = (payload) => {
const isMac = $SD.appInfo.application.platform === 'mac';
if(payload && payload.application) {
return isMac ? payload.application.split('.').pop() : payload.application.split('.')[0];
}
return '';
};

Utils.mergeImages = (images = [], options = {width: 144, height: 144, format: 'image/png', quality: 1}, inCanvas) => new Promise(resolve => {
const canvas = inCanvas && inCanvas instanceof HTMLCanvasElement
? inCanvas
: document.createElement('canvas');

const ctx = canvas.getContext('2d');
ctx.globalCompositeOperation = 'source-over';

resolve(Promise.all(images).then(images => {
canvas.width = options.width || 144;
canvas.height = options.height || 144;

// Draw images to canvas
images.forEach(image => {
ctx.globalAlpha = image.opacity ? image.opacity : 1;
return ctx.drawImage(image.img, image.x || 0, image.y || 0);
});

// Resolve all other data URIs sync
return canvas.toDataURL(options.format, options.quality);
}));
});


Loading