initial commit
This commit is contained in:
commit
3958100391
5
.gitignore
vendored
Normal file
5
.gitignore
vendored
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
/.settings
|
||||||
|
/bin
|
||||||
|
/.classpath
|
||||||
|
/TEST
|
||||||
|
/target/
|
23
.project
Normal file
23
.project
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<projectDescription>
|
||||||
|
<name>DesktopStreamDeck</name>
|
||||||
|
<comment></comment>
|
||||||
|
<projects>
|
||||||
|
</projects>
|
||||||
|
<buildSpec>
|
||||||
|
<buildCommand>
|
||||||
|
<name>org.eclipse.jdt.core.javabuilder</name>
|
||||||
|
<arguments>
|
||||||
|
</arguments>
|
||||||
|
</buildCommand>
|
||||||
|
<buildCommand>
|
||||||
|
<name>org.eclipse.m2e.core.maven2Builder</name>
|
||||||
|
<arguments>
|
||||||
|
</arguments>
|
||||||
|
</buildCommand>
|
||||||
|
</buildSpec>
|
||||||
|
<natures>
|
||||||
|
<nature>org.eclipse.m2e.core.maven2Nature</nature>
|
||||||
|
<nature>org.eclipse.jdt.core.javanature</nature>
|
||||||
|
</natures>
|
||||||
|
</projectDescription>
|
63
pom.xml
Normal file
63
pom.xml
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
<groupId>me.mrletsplay</groupId>
|
||||||
|
<artifactId>StreamDeckDesktop</artifactId>
|
||||||
|
<version>1.0-SNAPSHOT</version>
|
||||||
|
<build>
|
||||||
|
<sourceDirectory>src/main/java</sourceDirectory>
|
||||||
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<artifactId>maven-compiler-plugin</artifactId>
|
||||||
|
<version>3.8.1</version>
|
||||||
|
<configuration>
|
||||||
|
<release>17</release>
|
||||||
|
</configuration>
|
||||||
|
</plugin>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-jar-plugin</artifactId>
|
||||||
|
<version>3.2.0</version>
|
||||||
|
<configuration>
|
||||||
|
<archive>
|
||||||
|
<manifest>
|
||||||
|
<mainClass>me.mrletsplay.streamdeck.StreamDeck</mainClass>
|
||||||
|
</manifest>
|
||||||
|
</archive>
|
||||||
|
</configuration>
|
||||||
|
</plugin>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-shade-plugin</artifactId>
|
||||||
|
<version>2.4.3</version>
|
||||||
|
<executions>
|
||||||
|
<execution>
|
||||||
|
<phase>package</phase>
|
||||||
|
<goals>
|
||||||
|
<goal>shade</goal>
|
||||||
|
</goals>
|
||||||
|
</execution>
|
||||||
|
</executions>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
</build>
|
||||||
|
<repositories>
|
||||||
|
<repository>
|
||||||
|
<id>Graphite-Official</id>
|
||||||
|
<url>https://maven.graphite-official.com/releases</url>
|
||||||
|
</repository>
|
||||||
|
</repositories>
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>me.mrletsplay</groupId>
|
||||||
|
<artifactId>SimpleHTTPServer</artifactId>
|
||||||
|
<version>2.0-SNAPSHOT</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>me.mrletsplay</groupId>
|
||||||
|
<artifactId>MrCore</artifactId>
|
||||||
|
<version>4.4</version>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
</project>
|
86
src/main/java/me/mrletsplay/streamdeck/Soundboard.java
Normal file
86
src/main/java/me/mrletsplay/streamdeck/Soundboard.java
Normal file
@ -0,0 +1,86 @@
|
|||||||
|
package me.mrletsplay.streamdeck;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.concurrent.ExecutorService;
|
||||||
|
import java.util.concurrent.Executors;
|
||||||
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
|
|
||||||
|
import javax.sound.sampled.AudioInputStream;
|
||||||
|
import javax.sound.sampled.AudioSystem;
|
||||||
|
import javax.sound.sampled.Clip;
|
||||||
|
import javax.sound.sampled.FloatControl;
|
||||||
|
import javax.sound.sampled.LineEvent;
|
||||||
|
import javax.sound.sampled.LineUnavailableException;
|
||||||
|
import javax.sound.sampled.UnsupportedAudioFileException;
|
||||||
|
|
||||||
|
import me.mrletsplay.streamdeck.exception.InitializationException;
|
||||||
|
import me.mrletsplay.streamdeck.exception.SoundboardException;
|
||||||
|
|
||||||
|
public class Soundboard {
|
||||||
|
|
||||||
|
private static ExecutorService executor;
|
||||||
|
|
||||||
|
public static void initialize() throws InitializationException {
|
||||||
|
executor = Executors.newCachedThreadPool();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void finish() {
|
||||||
|
executor.shutdown();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void play(String path) throws SoundboardException {
|
||||||
|
AudioInputStream s;
|
||||||
|
try {
|
||||||
|
s = AudioSystem.getAudioInputStream(new File("TEST/test.wav"));
|
||||||
|
} catch (UnsupportedAudioFileException | IOException e) {
|
||||||
|
throw new SoundboardException(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
executor.submit(() -> {
|
||||||
|
try(Clip clip = AudioSystem.getClip()) {
|
||||||
|
Object wait = new Object();
|
||||||
|
AtomicBoolean playing = new AtomicBoolean(true);
|
||||||
|
clip.addLineListener(event -> {
|
||||||
|
if(event.getType() == LineEvent.Type.STOP || event.getType() == LineEvent.Type.CLOSE) {
|
||||||
|
playing.set(false);
|
||||||
|
wait.notify();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
clip.open(s);
|
||||||
|
clip.setFramePosition(0);
|
||||||
|
|
||||||
|
// FloatControl gain = (FloatControl) clip.getControl(FloatControl.Type.MASTER_GAIN);
|
||||||
|
// gain.setValue(20f * (float) Math.log10(0.2));
|
||||||
|
|
||||||
|
clip.start();
|
||||||
|
|
||||||
|
while(playing.get()) { wait.wait(); };
|
||||||
|
} catch (LineUnavailableException | IOException | InterruptedException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void test() throws IOException, UnsupportedAudioFileException, LineUnavailableException {
|
||||||
|
AudioInputStream s = AudioSystem.getAudioInputStream(new File("TEST/test.wav"));
|
||||||
|
try(Clip c = AudioSystem.getClip()) {
|
||||||
|
AtomicBoolean e = new AtomicBoolean(true);
|
||||||
|
c.addLineListener(event -> {
|
||||||
|
if(event.getType() == LineEvent.Type.STOP) e.set(false);
|
||||||
|
});
|
||||||
|
c.open(s);
|
||||||
|
|
||||||
|
c.setFramePosition(0);
|
||||||
|
FloatControl gain = (FloatControl) c.getControl(FloatControl.Type.MASTER_GAIN);
|
||||||
|
gain.setValue(20f * (float) Math.log10(0.2));
|
||||||
|
c.start();
|
||||||
|
|
||||||
|
c.loop(10);
|
||||||
|
|
||||||
|
while(e.get());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
37
src/main/java/me/mrletsplay/streamdeck/StreamDeck.java
Normal file
37
src/main/java/me/mrletsplay/streamdeck/StreamDeck.java
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
package me.mrletsplay.streamdeck;
|
||||||
|
|
||||||
|
import me.mrletsplay.simplehttpserver.http.server.HttpServer;
|
||||||
|
import me.mrletsplay.streamdeck.action.ActionSerializer;
|
||||||
|
import me.mrletsplay.streamdeck.api.StreamDeckAPI;
|
||||||
|
import me.mrletsplay.streamdeck.deck.Deck;
|
||||||
|
import me.mrletsplay.streamdeck.exception.InitializationException;
|
||||||
|
|
||||||
|
public class StreamDeck {
|
||||||
|
|
||||||
|
private static Deck deck;
|
||||||
|
|
||||||
|
public static void main(String[] args) throws InitializationException {
|
||||||
|
deck = new Deck(20);
|
||||||
|
|
||||||
|
ActionSerializer.initialize();
|
||||||
|
Soundboard.initialize();
|
||||||
|
|
||||||
|
HttpServer server = new HttpServer(HttpServer.newConfigurationBuilder()
|
||||||
|
.hostBindAll()
|
||||||
|
.port(5734)
|
||||||
|
.create());
|
||||||
|
|
||||||
|
new StreamDeckAPI().register(server.getDocumentProvider());
|
||||||
|
|
||||||
|
server.start();
|
||||||
|
|
||||||
|
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
|
||||||
|
Soundboard.finish();
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Deck getDeck() {
|
||||||
|
return deck;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
14
src/main/java/me/mrletsplay/streamdeck/action/Action.java
Normal file
14
src/main/java/me/mrletsplay/streamdeck/action/Action.java
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
package me.mrletsplay.streamdeck.action;
|
||||||
|
|
||||||
|
import java.lang.annotation.ElementType;
|
||||||
|
import java.lang.annotation.Retention;
|
||||||
|
import java.lang.annotation.RetentionPolicy;
|
||||||
|
import java.lang.annotation.Target;
|
||||||
|
|
||||||
|
@Target(ElementType.TYPE)
|
||||||
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
|
public @interface Action {
|
||||||
|
|
||||||
|
String value();
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,14 @@
|
|||||||
|
package me.mrletsplay.streamdeck.action;
|
||||||
|
|
||||||
|
import java.lang.annotation.ElementType;
|
||||||
|
import java.lang.annotation.Retention;
|
||||||
|
import java.lang.annotation.RetentionPolicy;
|
||||||
|
import java.lang.annotation.Target;
|
||||||
|
|
||||||
|
@Target(ElementType.FIELD)
|
||||||
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
|
public @interface ActionParameter {
|
||||||
|
|
||||||
|
String name() default "";
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,25 @@
|
|||||||
|
package me.mrletsplay.streamdeck.action;
|
||||||
|
|
||||||
|
public class ActionSerializationException extends Exception {
|
||||||
|
|
||||||
|
private static final long serialVersionUID = 5454427202803805714L;
|
||||||
|
|
||||||
|
public ActionSerializationException() {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
public ActionSerializationException(String message, Throwable cause) {
|
||||||
|
super(message, cause);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ActionSerializationException(String message) {
|
||||||
|
super(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ActionSerializationException(Throwable cause) {
|
||||||
|
super(cause);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,112 @@
|
|||||||
|
package me.mrletsplay.streamdeck.action;
|
||||||
|
|
||||||
|
import java.lang.reflect.Constructor;
|
||||||
|
import java.lang.reflect.Field;
|
||||||
|
import java.lang.reflect.InvocationTargetException;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import me.mrletsplay.mrcore.json.JSONObject;
|
||||||
|
import me.mrletsplay.mrcore.json.JSONType;
|
||||||
|
import me.mrletsplay.simplehttpserver.http.validation.JsonObjectValidator;
|
||||||
|
import me.mrletsplay.streamdeck.action.impl.PlaySoundAction;
|
||||||
|
import me.mrletsplay.streamdeck.action.impl.PressKeyAction;
|
||||||
|
import me.mrletsplay.streamdeck.exception.InitializationException;
|
||||||
|
|
||||||
|
public class ActionSerializer {
|
||||||
|
|
||||||
|
public static final JsonObjectValidator
|
||||||
|
ACTION_VALIDATOR = new JsonObjectValidator()
|
||||||
|
.require("type", JSONType.STRING)
|
||||||
|
.require("data", JSONType.OBJECT);
|
||||||
|
|
||||||
|
private static final List<Class<? extends DeckAction>> ACTIONS = List.of(
|
||||||
|
PlaySoundAction.class,
|
||||||
|
PressKeyAction.class
|
||||||
|
);
|
||||||
|
|
||||||
|
private static Map<String, Class<? extends DeckAction>> nameToAction;
|
||||||
|
|
||||||
|
public static void initialize() throws InitializationException {
|
||||||
|
Map<String, Class<? extends DeckAction>> nta = new LinkedHashMap<>();
|
||||||
|
for(var actionClass : ACTIONS) {
|
||||||
|
Action ac = actionClass.getAnnotation(Action.class);
|
||||||
|
if(ac == null) throw new InitializationException("Class " + actionClass.getName() + " is missing the @Action annotation");
|
||||||
|
|
||||||
|
nta.put(ac.value(), actionClass);
|
||||||
|
}
|
||||||
|
|
||||||
|
nameToAction = Collections.unmodifiableMap(nta);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static JSONObject serializeAction(DeckAction action) throws ActionSerializationException {
|
||||||
|
if(nameToAction == null) throw new ActionSerializationException("ActionSerializer is not initialized");
|
||||||
|
|
||||||
|
Class<? extends DeckAction> actionClass = action.getClass();
|
||||||
|
if(!ACTIONS.contains(actionClass)) throw new ActionSerializationException("Class " + actionClass.getName() + " is not a registered action class");
|
||||||
|
|
||||||
|
Action ac = actionClass.getAnnotation(Action.class);
|
||||||
|
|
||||||
|
JSONObject object = new JSONObject();
|
||||||
|
object.put("type", ac.value());
|
||||||
|
|
||||||
|
JSONObject data = new JSONObject();
|
||||||
|
for(Field f : action.getClass().getDeclaredFields()) {
|
||||||
|
ActionParameter parameter = f.getAnnotation(ActionParameter.class);
|
||||||
|
if(parameter == null) continue;
|
||||||
|
|
||||||
|
String name = !parameter.name().isEmpty() ? parameter.name() : f.getName();
|
||||||
|
try {
|
||||||
|
f.setAccessible(true);
|
||||||
|
data.put(name, f.get(action));
|
||||||
|
} catch (IllegalArgumentException | IllegalAccessException e) {
|
||||||
|
throw new ActionSerializationException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
action.serialize(data);
|
||||||
|
|
||||||
|
return object;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static DeckAction deserializeAction(JSONObject action) throws ActionSerializationException {
|
||||||
|
if(nameToAction == null) throw new ActionSerializationException("ActionSerializer is not initialized");
|
||||||
|
|
||||||
|
String actionType = action.optString("type").orElseThrow(() -> new ActionSerializationException("Missing action type"));
|
||||||
|
Class<? extends DeckAction> actionClass = nameToAction.get(actionType);
|
||||||
|
if(actionClass == null) throw new ActionSerializationException("Action doesn't exist");
|
||||||
|
|
||||||
|
JSONObject data = action.optJSONObject("data").orElseThrow(() -> new ActionSerializationException("Missing data"));
|
||||||
|
|
||||||
|
try {
|
||||||
|
Constructor<? extends DeckAction> constr = actionClass.getDeclaredConstructor();
|
||||||
|
constr.setAccessible(true);
|
||||||
|
DeckAction a = constr.newInstance();
|
||||||
|
|
||||||
|
for(Field f : action.getClass().getDeclaredFields()) {
|
||||||
|
ActionParameter parameter = f.getAnnotation(ActionParameter.class);
|
||||||
|
if(parameter == null) continue;
|
||||||
|
|
||||||
|
String name = !parameter.name().isEmpty() ? parameter.name() : f.getName();
|
||||||
|
try {
|
||||||
|
f.setAccessible(true);
|
||||||
|
|
||||||
|
// TODO: allow JSONSerializable
|
||||||
|
f.set(a, JSONType.castJSONValueTo(data.get(name), f.getType()));
|
||||||
|
} catch (IllegalArgumentException | IllegalAccessException e) {
|
||||||
|
throw new ActionSerializationException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
a.deserialize(data);
|
||||||
|
} catch (InstantiationException | IllegalAccessException | IllegalArgumentException | InvocationTargetException | NoSuchMethodException | SecurityException e) {
|
||||||
|
throw new ActionSerializationException(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,40 @@
|
|||||||
|
package me.mrletsplay.streamdeck.action;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import me.mrletsplay.mrcore.json.JSONObject;
|
||||||
|
|
||||||
|
public interface DeckAction {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Useful if parameters can't be directly mapped to JSON types.<br>
|
||||||
|
* By default, this does nothing but may be implemented if needed
|
||||||
|
* @param data The action data
|
||||||
|
* @throws ActionSerializationException If the action can't be deserialized
|
||||||
|
*/
|
||||||
|
public default void deserialize(JSONObject data) throws ActionSerializationException {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Must be implemented to correspond to {@link #deserialize(JSONObject)}.<br>
|
||||||
|
* By default, this does nothing but may be implemented if needed
|
||||||
|
* @param data The action data
|
||||||
|
* @throws ActionSerializationException If the action can't be serialized
|
||||||
|
*/
|
||||||
|
public default void serialize(JSONObject data) throws ActionSerializationException {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validates the action and returns a list of errors, or an empty list if there are no errors.<br>
|
||||||
|
* By default, this does nothing but may be implemented to add further validation
|
||||||
|
* @return A list of errors, or an empty list if there are no errors
|
||||||
|
*/
|
||||||
|
public default List<String> validate() {
|
||||||
|
return Collections.emptyList();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Runs the action
|
||||||
|
*/
|
||||||
|
public void run();
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,18 @@
|
|||||||
|
package me.mrletsplay.streamdeck.action.impl;
|
||||||
|
|
||||||
|
import me.mrletsplay.streamdeck.action.Action;
|
||||||
|
import me.mrletsplay.streamdeck.action.ActionParameter;
|
||||||
|
import me.mrletsplay.streamdeck.action.DeckAction;
|
||||||
|
|
||||||
|
@Action("play_sound")
|
||||||
|
public final class PlaySoundAction implements DeckAction {
|
||||||
|
|
||||||
|
@ActionParameter
|
||||||
|
private String soundPath;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,33 @@
|
|||||||
|
package me.mrletsplay.streamdeck.action.impl;
|
||||||
|
|
||||||
|
import java.awt.AWTException;
|
||||||
|
import java.awt.Robot;
|
||||||
|
|
||||||
|
import me.mrletsplay.streamdeck.action.Action;
|
||||||
|
import me.mrletsplay.streamdeck.action.ActionParameter;
|
||||||
|
import me.mrletsplay.streamdeck.action.DeckAction;
|
||||||
|
|
||||||
|
@Action("press_key")
|
||||||
|
public final class PressKeyAction implements DeckAction {
|
||||||
|
|
||||||
|
private static Robot robot;
|
||||||
|
|
||||||
|
static {
|
||||||
|
try {
|
||||||
|
robot = new Robot();
|
||||||
|
} catch (AWTException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@ActionParameter
|
||||||
|
private int key;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
robot.keyPress(key);
|
||||||
|
robot.delay(100);
|
||||||
|
robot.keyRelease(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
149
src/main/java/me/mrletsplay/streamdeck/api/StreamDeckAPI.java
Normal file
149
src/main/java/me/mrletsplay/streamdeck/api/StreamDeckAPI.java
Normal file
@ -0,0 +1,149 @@
|
|||||||
|
package me.mrletsplay.streamdeck.api;
|
||||||
|
|
||||||
|
import java.io.ByteArrayInputStream;
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.Base64;
|
||||||
|
|
||||||
|
import javax.imageio.ImageIO;
|
||||||
|
|
||||||
|
import me.mrletsplay.mrcore.json.JSONArray;
|
||||||
|
import me.mrletsplay.mrcore.json.JSONObject;
|
||||||
|
import me.mrletsplay.mrcore.json.JSONType;
|
||||||
|
import me.mrletsplay.simplehttpserver.http.HttpRequestMethod;
|
||||||
|
import me.mrletsplay.simplehttpserver.http.HttpStatusCodes;
|
||||||
|
import me.mrletsplay.simplehttpserver.http.endpoint.Endpoint;
|
||||||
|
import me.mrletsplay.simplehttpserver.http.endpoint.EndpointCollection;
|
||||||
|
import me.mrletsplay.simplehttpserver.http.header.DefaultClientContentTypes;
|
||||||
|
import me.mrletsplay.simplehttpserver.http.request.HttpRequestContext;
|
||||||
|
import me.mrletsplay.simplehttpserver.http.response.JsonResponse;
|
||||||
|
import me.mrletsplay.simplehttpserver.http.validation.JsonArrayValidator;
|
||||||
|
import me.mrletsplay.simplehttpserver.http.validation.JsonObjectValidator;
|
||||||
|
import me.mrletsplay.simplehttpserver.http.validation.result.ValidationResult;
|
||||||
|
import me.mrletsplay.streamdeck.StreamDeck;
|
||||||
|
import me.mrletsplay.streamdeck.action.ActionSerializationException;
|
||||||
|
import me.mrletsplay.streamdeck.action.ActionSerializer;
|
||||||
|
import me.mrletsplay.streamdeck.action.DeckAction;
|
||||||
|
import me.mrletsplay.streamdeck.deck.DeckButton;
|
||||||
|
|
||||||
|
public class StreamDeckAPI implements EndpointCollection {
|
||||||
|
|
||||||
|
private static final JsonObjectValidator
|
||||||
|
BUTTON_VALIDATOR = new JsonObjectValidator()
|
||||||
|
.require("id", JSONType.INTEGER)
|
||||||
|
.optional("icon", JSONType.STRING)
|
||||||
|
.optionalObject("action", ActionSerializer.ACTION_VALIDATOR),
|
||||||
|
STATE_VALIDATOR = new JsonObjectValidator()
|
||||||
|
.requireArray("buttons", new JsonArrayValidator()
|
||||||
|
.requireElementObjects(BUTTON_VALIDATOR));
|
||||||
|
|
||||||
|
private static JsonResponse error(String errorMessage) {
|
||||||
|
JSONObject error = new JSONObject();
|
||||||
|
error.put("error", errorMessage);
|
||||||
|
return new JsonResponse(error);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Endpoint(method = HttpRequestMethod.GET, path = "/actions")
|
||||||
|
public void getActions() {
|
||||||
|
// TODO
|
||||||
|
}
|
||||||
|
|
||||||
|
@Endpoint(method = HttpRequestMethod.GET, path = "/state")
|
||||||
|
public void getState() {
|
||||||
|
HttpRequestContext ctx = HttpRequestContext.getCurrentContext();
|
||||||
|
JSONObject state = new JSONObject();
|
||||||
|
|
||||||
|
JSONArray buttons = new JSONArray();
|
||||||
|
for(DeckButton button : StreamDeck.getDeck().getButtons()) {
|
||||||
|
JSONObject buttonObj = new JSONObject();
|
||||||
|
buttonObj.put("id", button.getId());
|
||||||
|
|
||||||
|
if(button.getIcon() == null) {
|
||||||
|
buttonObj.put("icon", null);
|
||||||
|
}else {
|
||||||
|
try {
|
||||||
|
ByteArrayOutputStream bOut = new ByteArrayOutputStream();
|
||||||
|
ImageIO.write(button.getIcon(), "PNG", bOut);
|
||||||
|
}catch(IOException e) {
|
||||||
|
ctx.respond(HttpStatusCodes.INTERNAL_SERVER_ERROR_500, error("Failed to serialize icon: " + e.getMessage()));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(button.getAction() == null) {
|
||||||
|
buttonObj.put("action", null);
|
||||||
|
}else {
|
||||||
|
try {
|
||||||
|
buttonObj.put("action", ActionSerializer.serializeAction(button.getAction()));
|
||||||
|
}catch(ActionSerializationException e) {
|
||||||
|
ctx.respond(HttpStatusCodes.INTERNAL_SERVER_ERROR_500, error("Failed to serialize action " + e.getMessage()));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
buttons.add(buttonObj);
|
||||||
|
}
|
||||||
|
|
||||||
|
state.put("buttons", buttons);
|
||||||
|
|
||||||
|
ctx.respond(HttpStatusCodes.OK_200, new JsonResponse(buttons));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Endpoint(method = HttpRequestMethod.PATCH, path = "/state")
|
||||||
|
public void updateState() {
|
||||||
|
HttpRequestContext ctx = HttpRequestContext.getCurrentContext();
|
||||||
|
JSONObject content;
|
||||||
|
if((content = ctx.expectContent(DefaultClientContentTypes.JSON_OBJECT)) == null) {
|
||||||
|
ctx.respond(HttpStatusCodes.BAD_REQUEST_400, error("Invalid content"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ValidationResult result = STATE_VALIDATOR.validate(content);
|
||||||
|
if(!result.isOk()) {
|
||||||
|
ctx.respond(HttpStatusCodes.BAD_REQUEST_400, result.asJsonResponse());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for(Object o : content.getJSONArray("buttons")) {
|
||||||
|
JSONObject buttonObj = (JSONObject) o;
|
||||||
|
int id = buttonObj.getInt("id");
|
||||||
|
DeckButton button = StreamDeck.getDeck().getButton(id);
|
||||||
|
|
||||||
|
if(buttonObj.has("icon")) {
|
||||||
|
String icon = buttonObj.getString("icon");
|
||||||
|
if(icon == null) {
|
||||||
|
button.setIcon(null);
|
||||||
|
}else {
|
||||||
|
try {
|
||||||
|
button.setIcon(ImageIO.read(new ByteArrayInputStream(Base64.getDecoder().decode(icon))));
|
||||||
|
} catch (IOException | IllegalArgumentException e) {
|
||||||
|
ctx.respond(HttpStatusCodes.BAD_REQUEST_400, error("Invalid icon"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(buttonObj.has("action")) {
|
||||||
|
JSONObject action = buttonObj.getJSONObject("action");
|
||||||
|
if(action == null) {
|
||||||
|
button.setAction(null);
|
||||||
|
}else {
|
||||||
|
DeckAction newAction;
|
||||||
|
try {
|
||||||
|
newAction = ActionSerializer.deserializeAction(action);
|
||||||
|
} catch (ActionSerializationException e) {
|
||||||
|
ctx.respond(HttpStatusCodes.BAD_REQUEST_400, error("Invalid action: " + e.getMessage()));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
button.setAction(newAction);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getBasePath() {
|
||||||
|
return "/api";
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
26
src/main/java/me/mrletsplay/streamdeck/deck/Deck.java
Normal file
26
src/main/java/me/mrletsplay/streamdeck/deck/Deck.java
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
package me.mrletsplay.streamdeck.deck;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class Deck {
|
||||||
|
|
||||||
|
private List<DeckButton> buttons;
|
||||||
|
|
||||||
|
public Deck(int buttonCount) {
|
||||||
|
List<DeckButton> buttons = new ArrayList<>(buttonCount);
|
||||||
|
for(int i = 0; i < buttonCount; i++) buttons.add(new DeckButton(i));
|
||||||
|
this.buttons = Collections.unmodifiableList(buttons);
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<DeckButton> getButtons() {
|
||||||
|
return buttons;
|
||||||
|
}
|
||||||
|
|
||||||
|
public DeckButton getButton(int id) {
|
||||||
|
if(id < 0 || id >= buttons.size()) return null;
|
||||||
|
return buttons.get(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
37
src/main/java/me/mrletsplay/streamdeck/deck/DeckButton.java
Normal file
37
src/main/java/me/mrletsplay/streamdeck/deck/DeckButton.java
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
package me.mrletsplay.streamdeck.deck;
|
||||||
|
|
||||||
|
import java.awt.image.BufferedImage;
|
||||||
|
|
||||||
|
import me.mrletsplay.streamdeck.action.DeckAction;
|
||||||
|
|
||||||
|
public class DeckButton {
|
||||||
|
|
||||||
|
private int id;
|
||||||
|
private BufferedImage icon;
|
||||||
|
private DeckAction action;
|
||||||
|
|
||||||
|
public DeckButton(int id) {
|
||||||
|
this.id = id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getId() {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setIcon(BufferedImage icon) {
|
||||||
|
this.icon = icon;
|
||||||
|
}
|
||||||
|
|
||||||
|
public BufferedImage getIcon() {
|
||||||
|
return icon;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAction(DeckAction action) {
|
||||||
|
this.action = action;
|
||||||
|
}
|
||||||
|
|
||||||
|
public DeckAction getAction() {
|
||||||
|
return action;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,23 @@
|
|||||||
|
package me.mrletsplay.streamdeck.exception;
|
||||||
|
|
||||||
|
public class InitializationException extends Exception {
|
||||||
|
|
||||||
|
private static final long serialVersionUID = 8812199845001173928L;
|
||||||
|
|
||||||
|
public InitializationException() {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
public InitializationException(String message, Throwable cause) {
|
||||||
|
super(message, cause);
|
||||||
|
}
|
||||||
|
|
||||||
|
public InitializationException(String message) {
|
||||||
|
super(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
public InitializationException(Throwable cause) {
|
||||||
|
super(cause);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,23 @@
|
|||||||
|
package me.mrletsplay.streamdeck.exception;
|
||||||
|
|
||||||
|
public class SoundboardException extends Exception {
|
||||||
|
|
||||||
|
private static final long serialVersionUID = 8790973950212930162L;
|
||||||
|
|
||||||
|
public SoundboardException() {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
public SoundboardException(String message, Throwable cause) {
|
||||||
|
super(message, cause);
|
||||||
|
}
|
||||||
|
|
||||||
|
public SoundboardException(String message) {
|
||||||
|
super(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
public SoundboardException(Throwable cause) {
|
||||||
|
super(cause);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user