-
Notifications
You must be signed in to change notification settings - Fork 24
Expand file tree
/
Copy pathStreamdeckInterface.cpp
More file actions
319 lines (279 loc) · 13.2 KB
/
StreamdeckInterface.cpp
File metadata and controls
319 lines (279 loc) · 13.2 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
//==============================================================================
/**
@file StreamdeckInterface.cpp
@brief Based off of Elgato's CPU plugin example
@copyright (c) 2018, Corsair Memory, Inc.
This source code is licensed under the MIT-style license found in the LICENSE file.
**/
//==============================================================================
#include "StreamdeckInterface.h"
#include <atomic>
#include "ElgatoSD/EPLJSONUtils.h"
#include "ElgatoSD/ESDConnectionManager.h"
#include "SimulatorInterface/SimulatorInterfaceParameters.h"
#include "StreamdeckContext/BackwardsCompatibilityHandler.h"
#include "Utilities/JsonReader.h"
#include "Utilities/LuaReader.h"
#include <nlohmann/json.hpp>
using json = nlohmann::json;
class CallBackTimer
{
public:
CallBackTimer() : _execute(false) {}
~CallBackTimer()
{
if (_execute.load(std::memory_order_acquire)) {
stop();
};
}
void stop()
{
_execute.store(false, std::memory_order_release);
if (_thd.joinable())
_thd.join();
}
void start(int interval, std::function<void(void)> func)
{
if (_execute.load(std::memory_order_acquire)) {
stop();
};
_execute.store(true, std::memory_order_release);
_thd = std::thread([this, interval, func]() {
while (_execute.load(std::memory_order_acquire)) {
func();
std::this_thread::sleep_for(std::chrono::milliseconds(interval));
}
});
}
bool is_running() const noexcept { return (_execute.load(std::memory_order_acquire) && _thd.joinable()); }
private:
std::atomic<bool> _execute;
std::thread _thd;
};
StreamdeckInterface::StreamdeckInterface()
{
mTimer = new CallBackTimer();
mTimer->start(10, [this]() { this->UpdateFromGameState(); });
}
StreamdeckInterface::~StreamdeckInterface()
{
if (mTimer != nullptr) {
mTimer->stop();
delete mTimer;
mTimer = nullptr;
}
}
SimulatorConnectionSettings StreamdeckInterface::get_connection_settings(const json &global_settings)
{
const std::string ip_address_request = EPLJSONUtils::GetStringByName(global_settings, "ip_address");
const std::string listener_port_request = EPLJSONUtils::GetStringByName(global_settings, "listener_port");
const std::string send_port_request = EPLJSONUtils::GetStringByName(global_settings, "send_port");
const std::string multicast_address_request = ""; // TODO: add multicast option to javascript UI.
const bool user_settings_valid =
(!ip_address_request.empty() && !listener_port_request.empty() && !send_port_request.empty());
SimulatorConnectionSettings connection_settings;
if (user_settings_valid) {
connection_settings.rx_port = listener_port_request;
connection_settings.tx_port = send_port_request;
connection_settings.ip_address = ip_address_request;
connection_settings.multicast_address = multicast_address_request;
} else {
connection_settings.rx_port = DEFAULT_LISTENER_PORT;
connection_settings.tx_port = DEFAULT_SEND_PORT;
connection_settings.ip_address = DEFAULT_IP_ADDRESS;
connection_settings.multicast_address = DEFAULT_MULTICAST_ADDRESS;
}
return connection_settings;
}
void StreamdeckInterface::DidReceiveGlobalSettings(const json &inPayload)
{
json settings;
EPLJSONUtils::GetObjectByName(inPayload, "settings", settings);
// SimulatorConnectionSettings connection_settings = get_connection_settings(settings);
// TODO: read these in from global settings, always connect to both for now.
std::unordered_map<Protocol, SimulatorConnectionSettings> connection_settings;
connection_settings[Protocol::DCS_BIOS] = {"5010", "7778", "127.0.0.1", "239.255.50.10"};
connection_settings[Protocol::DCS_ExportScript] = {"1725", "26027", "127.0.0.1", ""};
// Create first instance of Simulator Interface only done under DidReceiveGlobalSettings to allow for any stored
// settings to be used before binding socket to default port values.
for (const auto &protocol : connection_settings) {
if (!simConnectionManager_.is_connected_with_settings(protocol.first, protocol.second)) {
try {
simConnectionManager_.connect_to_protocol(protocol.first, protocol.second);
mConnectionManager->LogMessage("[Plugin] Successfully connected to Simulator Interface UDP port");
} catch (const std::exception &e) {
mConnectionManager->LogMessage("[Plugin] Caught Exception While Opening Connection: " +
std::string(e.what()));
}
}
}
}
void StreamdeckInterface::UpdateFromGameState()
{
//
// Warning: UpdateFromGameState() is running in the timer thread
//
// Update the Simulator game state in memory, then update each Streamdeck button context.
simConnectionManager_.update_all();
if (mConnectionManager != nullptr) {
mVisibleContextsMutex.lock();
for (auto &context : mVisibleContexts) {
const auto protocol = context.second.protocol();
if (simConnectionManager_.is_connected(protocol)) {
context.second.updateContextState(simConnectionManager_.get_interface(protocol), mConnectionManager);
}
}
mVisibleContextsMutex.unlock();
}
}
void StreamdeckInterface::KeyDownForAction(const std::string &inAction,
const std::string &inContext,
const json &inPayload,
const std::string &inDeviceID)
{
const auto payload = backwardsCompatibilityHandler(inPayload);
mVisibleContextsMutex.lock();
const auto protocol = mVisibleContexts[inContext].protocol();
if (simConnectionManager_.is_connected(protocol)) {
mVisibleContexts[inContext].handleButtonPressedEvent(
simConnectionManager_.get_interface(protocol), mConnectionManager, payload);
}
mVisibleContextsMutex.unlock();
}
void StreamdeckInterface::KeyUpForAction(const std::string &inAction,
const std::string &inContext,
const json &inPayload,
const std::string &inDeviceID)
{
const auto payload = backwardsCompatibilityHandler(inPayload);
mVisibleContextsMutex.lock();
const auto protocol = mVisibleContexts[inContext].protocol();
if (simConnectionManager_.is_connected(protocol)) {
mVisibleContexts[inContext].handleButtonReleasedEvent(
simConnectionManager_.get_interface(protocol), mConnectionManager, payload);
}
mVisibleContextsMutex.unlock();
}
void StreamdeckInterface::WillAppearForAction(const std::string &inAction,
const std::string &inContext,
const json &inPayload,
const std::string &inDeviceID)
{
const auto settings = inPayload["settings"];
auto newContext = StreamdeckContext(inAction, inContext, settings);
if (newContext.is_valid()) {
mVisibleContextsMutex.lock();
// Remember the context and make sure state is synchronized with plugin.
mVisibleContexts[inContext] = std::move(newContext);
mVisibleContexts[inContext].forceSendState(mConnectionManager);
mVisibleContextsMutex.unlock();
} else {
mConnectionManager->LogMessage("[Plugin] Unable to handle button of type: " + inAction +
" context: " + inContext + " with Settings: " + settings.dump());
}
}
void StreamdeckInterface::WillDisappearForAction(const std::string &inAction,
const std::string &inContext,
const json &inPayload,
const std::string &inDeviceID)
{
// Remove the context.
mVisibleContextsMutex.lock();
mVisibleContexts.erase(inContext);
mVisibleContextsMutex.unlock();
}
void StreamdeckInterface::DeviceDidConnect(const std::string &inDeviceID, const json &inDeviceInfo)
{
// Request global settings from Streamdeck.
if (mConnectionManager != nullptr) {
mConnectionManager->GetGlobalSettings();
}
}
void StreamdeckInterface::DeviceDidDisconnect(const std::string &inDeviceID)
{
// Nothing to do.
}
void StreamdeckInterface::SendToPlugin(const std::string &inAction,
const std::string &inContext,
const json &inPayload,
const std::string &inDeviceID)
{
const std::string event = EPLJSONUtils::GetStringByName(inPayload, "event");
if (event == "SettingsUpdate") {
// Update settings for the specified context -- triggered by Property Inspector detecting a change.
mVisibleContextsMutex.lock();
if (mVisibleContexts.count(inContext) > 0) {
mVisibleContexts[inContext].updateContextSettings(inPayload["settings"]);
}
mVisibleContextsMutex.unlock();
}
if (event == "RequestDcsStateUpdate") {
const auto protocol =
(inAction == "com.ctytler.dcs.dcs-bios") ? Protocol::DCS_BIOS : Protocol::DCS_ExportScript;
if (!simConnectionManager_.is_connected(protocol)) {
mConnectionManager->SendToPropertyInspector(inAction,
inContext,
json({{"event", "DebugDcsGameState"},
{"current_game_state", ""},
{"error", "SimulatorInterface not connected"}}));
} else {
const json current_simulator_state =
simConnectionManager_.get_interface(protocol)->get_current_state_as_json();
mConnectionManager->SendToPropertyInspector(
inAction,
inContext,
json({{"event", "DebugDcsGameState"}, {"current_game_state", current_simulator_state}}));
}
}
if (event == "RequestInstalledModules") {
const std::string dcs_install_path = EPLJSONUtils::GetStringByName(inPayload, "dcs_install_path");
const std::string modules_subdir = "/mods/aircraft/";
const json installed_modules_and_result = get_installed_modules(dcs_install_path, modules_subdir);
const std::string result = EPLJSONUtils::GetStringByName(installed_modules_and_result, "result");
if (result != "success") {
mConnectionManager->LogMessage("[DCS-ExportScript:IdLookupWindow] Get Installed Modules Failure: " +
result);
}
mConnectionManager->SendToPropertyInspector(
inAction,
inContext,
json({{"event", "InstalledModules"},
{"installed_modules", installed_modules_and_result["installed_modules"]}}));
}
if (event == "RequestIdLookup") {
const std::string dcs_install_path = EPLJSONUtils::GetStringByName(inPayload, "dcs_install_path");
const std::string module = EPLJSONUtils::GetStringByName(inPayload, "module");
json clickabledata_and_result = get_clickabledata(dcs_install_path, module, "bin/extract_clickabledata.lua");
const std::string lua_result = EPLJSONUtils::GetStringByName(clickabledata_and_result, "result");
if (lua_result != "success") {
mConnectionManager->LogMessage("[DCS-ExportScript:IdLookupWindow] " + module +
" Clickabledata Result: " + lua_result);
}
mConnectionManager->SendToPropertyInspector(
inAction,
inContext,
json({{"event", "Clickabledata"}, {"clickabledata", clickabledata_and_result["clickabledata_items"]}}));
}
if (event == "requestModuleList") {
const std::string path = EPLJSONUtils::GetStringByName(inPayload, "path");
const auto maybe_module_list = get_module_list(path);
if (maybe_module_list) {
mConnectionManager->SendToPropertyInspector(
inAction, inContext, json({{"event", "ModuleList"}, {"moduleList", maybe_module_list.value()}}));
mConnectionManager->LogMessage("[Plugin] Successfully found json modules at: " + path);
} else {
mConnectionManager->LogMessage("[Plugin] Get list of json modules failed at: " + path);
}
}
if (event == "requestControlReferenceJson") {
const std::string filename = EPLJSONUtils::GetStringByName(inPayload, "filename");
const auto maybe_json = read_json_file(filename);
if (maybe_json) {
mConnectionManager->SendToPropertyInspector(
inAction, inContext, json({{"event", "JsonFile"}, {"jsonFile", maybe_json.value()}}));
mConnectionManager->LogMessage("[Plugin] Successfully read in json file from: " + filename);
} else {
mConnectionManager->LogMessage("[Plugin] Unable to read in json file from: " + filename);
}
}
}