From d7cff500572682293c188bc47f42bd52e5c60f7b Mon Sep 17 00:00:00 2001 From: John Mercier Date: Fri, 16 Apr 2021 11:15:22 -0400 Subject: [PATCH] Adding support for generic events --- README.md | 5 + build.gradle | 2 +- .../xcbparser/ClientMessageDataUnion.groovy | 5 + .../x11protocol/xcbparser/JavaEvent.groovy | 80 ++++++++-- .../xcbparser/JavaTypeListProperty.groovy | 7 +- .../x11protocol/xcbparser/XResult.groovy | 51 +++++-- .../x11protocol/xcbparser/XTypeEvent.groovy | 20 ++- .../xcbparser/JavaGenericEventSpec.groovy | 143 ++++++++++++++++++ .../xcbparser/XTypeEventSpec.groovy | 8 +- .../x11client/ProtocolPluginService.java | 106 +++++++++++++ .../moaxcp/x11client/XProtocolService.java | 100 +++++------- .../x11client/protocol/XGenericEvent.java | 9 ++ .../x11client/protocol/XProtocolPlugin.java | 5 +- 13 files changed, 445 insertions(+), 96 deletions(-) create mode 100644 buildSrc/src/test/groovy/com/github/moaxcp/x11protocol/xcbparser/JavaGenericEventSpec.groovy create mode 100644 src/main/java/com/github/moaxcp/x11client/ProtocolPluginService.java create mode 100644 src/main/java/com/github/moaxcp/x11client/protocol/XGenericEvent.java diff --git a/README.md b/README.md index 2a0d5771..d9a05e24 100644 --- a/README.md +++ b/README.md @@ -428,6 +428,11 @@ and will likely move into a new project. # versions +## 0.7.0-SNAPSHOT + +* Adding support for generic events +* Added ProtocolPluginService to handle plugins for the XProtocolService + ## 0.6.0 * Implementing `sync()` method based on XSync but without a discard parameter. diff --git a/build.gradle b/build.gradle index ac83ce75..af0c8b9e 100644 --- a/build.gradle +++ b/build.gradle @@ -67,7 +67,7 @@ x11Protocol { outputSrc = file('build/generated/xproto/java/') outputResources = file('build/generated/xproto/resources') xcbXmls = file('src/main/xcbXmls') - exclude = ['present', 'xinput', 'xkb', 'dri3', 'glx'] + exclude = ['xinput', 'xkb', 'dri3', 'glx'] keysymHeader = file('src/main/keysym/keysymdef.h') } diff --git a/buildSrc/src/main/groovy/com/github/moaxcp/x11protocol/xcbparser/ClientMessageDataUnion.groovy b/buildSrc/src/main/groovy/com/github/moaxcp/x11protocol/xcbparser/ClientMessageDataUnion.groovy index 9eb9e83a..a4ba2b08 100644 --- a/buildSrc/src/main/groovy/com/github/moaxcp/x11protocol/xcbparser/ClientMessageDataUnion.groovy +++ b/buildSrc/src/main/groovy/com/github/moaxcp/x11protocol/xcbparser/ClientMessageDataUnion.groovy @@ -59,4 +59,9 @@ class ClientMessageDataUnion extends JavaUnion { MethodSpec getWriteMethod() { return null } + + @Override + Optional getFixedSize() { + return Optional.of(20) + } } diff --git a/buildSrc/src/main/groovy/com/github/moaxcp/x11protocol/xcbparser/JavaEvent.groovy b/buildSrc/src/main/groovy/com/github/moaxcp/x11protocol/xcbparser/JavaEvent.groovy index feb3dcdd..780bc74e 100644 --- a/buildSrc/src/main/groovy/com/github/moaxcp/x11protocol/xcbparser/JavaEvent.groovy +++ b/buildSrc/src/main/groovy/com/github/moaxcp/x11protocol/xcbparser/JavaEvent.groovy @@ -8,12 +8,17 @@ import static com.github.moaxcp.x11protocol.generator.Conventions.getEventTypeNa class JavaEvent extends JavaObjectType { int number + boolean genericEvent + int genericEventNumber static JavaEvent javaEvent(XTypeEvent event) { String simpleName = getEventJavaName(event.name) - + ClassName superType = ClassName.get(event.basePackage, 'XEvent') + if(event.genericEvent) { + superType = ClassName.get(event.basePackage, 'XGenericEvent') + } JavaEvent javaEvent = new JavaEvent( - superTypes: event.superTypes + ClassName.get(event.basePackage, 'XEvent'), + superTypes: event.superTypes + superType, basePackage: event.basePackage, javaPackage: event.javaPackage, simpleName:simpleName, @@ -27,6 +32,20 @@ class JavaEvent extends JavaObjectType { if(javaEvent.fixedSize && javaEvent.fixedSize.get() < 32) { javaEvent.protocol.add(new JavaPad(bytes: 32 - javaEvent.fixedSize.get())) } + + if(event.genericEvent) { + javaEvent.genericEvent = true + javaEvent.genericEventNumber = event.genericEventNumber + JavaProperty l = javaEvent.getJavaProperty('length') + l.writeValueExpression = CodeBlock.of('getLength() - 32') + if(!(javaEvent.protocol[1] instanceof JavaReadParameter)) { + throw new IllegalStateException("First field must be a JavaReadParameter") + } + javaEvent.getJavaProperty('extension').readParam = true + javaEvent.getJavaProperty('sequenceNumber').readParam = true + javaEvent.getJavaProperty('length').readParam = true + javaEvent.getJavaProperty('eventType').readParam = true + } return javaEvent } @@ -51,30 +70,59 @@ class JavaEvent extends JavaObjectType { @Override void addReadParameters(MethodSpec.Builder methodBuilder) { - super.addReadParameters(methodBuilder) methodBuilder.addParameter(ParameterSpec.builder(TypeName.BOOLEAN, 'sentEvent').build()) + super.addReadParameters(methodBuilder) } @Override - void addWriteStatements(MethodSpec.Builder methodBuilder) { - super.addWriteStatements(methodBuilder) - if(fixedSize && fixedSize.get() >= 32) { - return + void addReadStatements(MethodSpec.Builder methodBuilder) { + if(lastListNoLength) { + if(genericEvent) { + methodBuilder.addStatement('int javaStart = 10') + } else { + methodBuilder.addStatement('int javaStart = 1') + } + protocol.eachWithIndex { it, i -> + if(!it.readProtocol + || (it instanceof JavaProperty && it.bitcaseInfo)) { + return + } + methodBuilder.addCode(it.declareAndReadCode) + if(i != protocol.size() - 1) { + methodBuilder.addStatement('javaStart += $L', it.getSizeExpression()) + } + } + } else { + super.addReadStatements(methodBuilder) + } + if(fixedSize && fixedSize.get() < 32) { + methodBuilder.addStatement('in.readPad($L)', 32 - fixedSize.get()) } - methodBuilder.beginControlFlow('if(getSize() < 32)') - .addStatement('out.writePad(32 - getSize())') - .endControlFlow() } @Override void addBuilderStatement(MethodSpec.Builder methodBuilder, CodeBlock... fields) { - CodeBlock sentEvent = CodeBlock.builder().addStatement('javaBuilder.$L($L)', 'sentEvent', 'sentEvent').build() - super.addBuilderStatement(methodBuilder, sentEvent) - if(fixedSize && fixedSize.get() >= 32) { + CodeBlock.Builder startBuilder = CodeBlock.builder().addStatement('javaBuilder.$L($L)', 'sentEvent', 'sentEvent') + super.addBuilderStatement(methodBuilder, startBuilder.build()) + if(!fixedSize) { + methodBuilder.beginControlFlow('if(javaBuilder.getSize() < 32)') + methodBuilder.addStatement('in.readPad(32 - javaBuilder.getSize())') + methodBuilder.endControlFlow() + return + } + + if(fixedSize && fixedSize.get() % 4 == 0) { + return + } + methodBuilder.addStatement('in.readPadAlign(javaBuilder.getSize())') + } + + @Override + void addWriteStatements(MethodSpec.Builder methodBuilder) { + super.addWriteStatements(methodBuilder) + if(fixedSize && fixedSize.get() % 4 == 0) { return } - methodBuilder.beginControlFlow('if(javaBuilder.getSize() < 32)') - .addStatement('in.readPad(32 - javaBuilder.getSize())') - .endControlFlow() + methodBuilder.addStatement('out.writePadAlign(getSize())') } } diff --git a/buildSrc/src/main/groovy/com/github/moaxcp/x11protocol/xcbparser/JavaTypeListProperty.groovy b/buildSrc/src/main/groovy/com/github/moaxcp/x11protocol/xcbparser/JavaTypeListProperty.groovy index 208f1b57..d8b1d252 100644 --- a/buildSrc/src/main/groovy/com/github/moaxcp/x11protocol/xcbparser/JavaTypeListProperty.groovy +++ b/buildSrc/src/main/groovy/com/github/moaxcp/x11protocol/xcbparser/JavaTypeListProperty.groovy @@ -40,9 +40,14 @@ class JavaTypeListProperty extends JavaListProperty { @Override CodeBlock getDeclareAndReadCode() { if(lengthExpression instanceof EmptyExpression) { + JavaProperty lengthProperty = javaType.getJavaProperty('length') + CodeBlock lengthExpression = CodeBlock.of('$L', lengthProperty.name) + if(lengthProperty.typeName == TypeName.SHORT) { + lengthExpression = CodeBlock.of('Short.toUnsignedInt($L)', lengthProperty.name) + } return CodeBlock.builder() .addStatement('$1T $2L = new $3T<>(length - javaStart)', typeName, name, ArrayList.class) - .beginControlFlow('while(javaStart < Short.toUnsignedInt(length) * 4)') + .beginControlFlow('while(javaStart < $L * 4)', lengthExpression) .addStatement('$1T baseObject = $1T.read$2L(in)', baseTypeName, baseTypeName.simpleName()) .addStatement('$L.add(baseObject)', name) .addStatement('javaStart += baseObject.getSize()') diff --git a/buildSrc/src/main/groovy/com/github/moaxcp/x11protocol/xcbparser/XResult.groovy b/buildSrc/src/main/groovy/com/github/moaxcp/x11protocol/xcbparser/XResult.groovy index 7672c622..e919a89d 100644 --- a/buildSrc/src/main/groovy/com/github/moaxcp/x11protocol/xcbparser/XResult.groovy +++ b/buildSrc/src/main/groovy/com/github/moaxcp/x11protocol/xcbparser/XResult.groovy @@ -57,11 +57,17 @@ class XResult { .build()) builder.addField( - FieldSpec.builder(byte.class, 'majorOpcode', Modifier.PRIVATE) + FieldSpec.builder(byte.class, 'majorVersion', Modifier.PRIVATE) .addAnnotation(Getter.class) - .addAnnotation(Setter.class) + .initializer('$L', majorVersion) .build()) + builder.addField( + FieldSpec.builder(byte.class, 'minorVersion', Modifier.PRIVATE) + .addAnnotation(Getter.class) + .initializer('$L', minorVersion) + .build()) + builder.addField( FieldSpec.builder(byte.class, 'firstEvent', Modifier.PRIVATE) .addAnnotation(Getter.class) @@ -96,9 +102,11 @@ class XResult { .addParameter(byte.class, 'number') for(XTypeEvent event : events.values()) { - supportedEvent.beginControlFlow('if(number + firstEvent == $L)', event.number) - supportedEvent.addStatement('return true') - supportedEvent.endControlFlow() + if(event.number != 35) { + supportedEvent.beginControlFlow('if(number + firstEvent == $L)', event.number) + supportedEvent.addStatement('return true') + supportedEvent.endControlFlow() + } } supportedEvent.addStatement('return false') @@ -129,9 +137,11 @@ class XResult { .addException(IOException.class) for(XTypeEvent event : events.values()) { - readEvent.beginControlFlow('if(number + firstEvent == $L)', event.number) - readEvent.addStatement('return $T.read$L(sentEvent, in)', event.javaType.className, event.javaType.className.simpleName()) - readEvent.endControlFlow() + if(event.number != 35) { + readEvent.beginControlFlow('if(number + firstEvent == $L)', event.number) + readEvent.addStatement('return $T.read$L(sentEvent, in)', event.javaType.className, event.javaType.className.simpleName()) + readEvent.endControlFlow() + } } readEvent.addStatement('throw new $T("number " + number + " is not supported")', IllegalArgumentException.class) @@ -154,10 +164,33 @@ class XResult { builder.addMethod(readError.build()) + MethodSpec.Builder readGenericEvent = MethodSpec.methodBuilder('readGenericEvent') + .addModifiers(Modifier.PUBLIC) + .addAnnotation(Override.class) + .returns(ClassName.get(basePackage, 'XGenericEvent')) + .addParameter(boolean.class, 'sentEvent') + .addParameter(byte.class, 'extension') + .addParameter(short.class, 'sequenceNumber') + .addParameter(int.class, 'length') + .addParameter(short.class, 'eventType') + .addParameter(ClassName.get(basePackage, 'X11Input'), 'in') + .addException(IOException.class) + + for(XTypeEvent event : events.values()) { + if(event.number == 35) { + readGenericEvent.beginControlFlow('if(eventType == $L)', event.genericEventNumber) + readError.addStatement('return $T.read$L(sentEvent, extension, sequenceNumber, length, eventType, in)', event.javaType.className, event.javaType.className.simpleName()) + readGenericEvent.endControlFlow() + } + } + readGenericEvent.addStatement('throw new $T("eventType " + eventType + " is not supported")', IllegalArgumentException.class) + + builder.addMethod(readGenericEvent.build()) + return builder.build() } - public ClassName getPluginClassName() { + ClassName getPluginClassName() { ClassName.get(javaPackage, getJavaName(header.capitalize() + 'Plugin')) } diff --git a/buildSrc/src/main/groovy/com/github/moaxcp/x11protocol/xcbparser/XTypeEvent.groovy b/buildSrc/src/main/groovy/com/github/moaxcp/x11protocol/xcbparser/XTypeEvent.groovy index a336c1d3..3fadaad9 100644 --- a/buildSrc/src/main/groovy/com/github/moaxcp/x11protocol/xcbparser/XTypeEvent.groovy +++ b/buildSrc/src/main/groovy/com/github/moaxcp/x11protocol/xcbparser/XTypeEvent.groovy @@ -6,18 +6,32 @@ import static com.github.moaxcp.x11protocol.xcbparser.JavaEvent.javaEvent class XTypeEvent extends XTypeObject { int number + boolean genericEvent + int genericEventNumber XTypeEvent(Map map) { super(map) number = map.number ?: 0 + genericEvent = map.genericEvent ?: false } static XTypeEvent xTypeEvent(XResult result, Node node) { int number = Integer.valueOf((String) node.attributes().get('number')) - XTypeEvent event = new XTypeEvent(result: result, number: number, name: node.attributes().get('name'), basePackage: result.basePackage, javaPackage: result.javaPackage) + XTypeEvent event = new XTypeEvent(result: result, number: number, name: node.attributes().get('name'), genericEvent: node.attributes().get('xge'), basePackage: result.basePackage, javaPackage: result.javaPackage) event.addUnits(result, node) - event.protocol.add(0, new XUnitField(result: result, name: 'NUMBER', type: 'CARD8', constantValue: number)) - event.protocol.add(2, new XUnitField(result: result, name:'sequence_number', type: 'CARD16')) + if(event.genericEvent) { + event.number = 35 + event.genericEventNumber = number + event.protocol.add(0, new XUnitField(result: result, name: 'NUMBER', type: 'CARD8', constantValue: 35)) + event.protocol.add(1, new XUnitField(result: result, name: 'extension', type: 'CARD8')) + event.protocol.add(2, new XUnitField(result: result, name: 'sequence_number', type: 'CARD16')) + event.protocol.add(3, new XUnitField(result: result, name: 'length', type: 'CARD32', localOnly: true)) + event.protocol.add(4, new XUnitField(result: result, name: 'event_type', type: 'CARD16', constantValue: number)) + + } else { + event.protocol.add(0, new XUnitField(result: result, name: 'NUMBER', type: 'CARD8', constantValue: number)) + event.protocol.add(2, new XUnitField(result: result, name: 'sequence_number', type: 'CARD16')) + } return event } diff --git a/buildSrc/src/test/groovy/com/github/moaxcp/x11protocol/xcbparser/JavaGenericEventSpec.groovy b/buildSrc/src/test/groovy/com/github/moaxcp/x11protocol/xcbparser/JavaGenericEventSpec.groovy new file mode 100644 index 00000000..425b43ac --- /dev/null +++ b/buildSrc/src/test/groovy/com/github/moaxcp/x11protocol/xcbparser/JavaGenericEventSpec.groovy @@ -0,0 +1,143 @@ +package com.github.moaxcp.x11protocol.xcbparser + +import com.github.moaxcp.x11protocol.XmlSpec + +class JavaGenericEventSpec extends XmlSpec { + def configureNotify() { + given: + xmlBuilder.xcb() { + xidtype(name: 'EVENT') + xidtype(name: 'WINDOW') + event(name: 'ConfigureNotify', number: '0', xge: 'true') { + pad(bytes: '2') + field(type: 'EVENT', name: 'event') + field(type: 'WINDOW', name: 'window') + field(type: 'INT16', name: 'x') + field(type: 'INT16', name: 'y') + field(type: 'CARD16', name: 'width') + field(type: 'CARD16', name: 'height') + field(type: 'INT16', name: 'off_x') + field(type: 'INT16', name: 'off_y') + field(type: 'CARD16', name: 'pixmap_width') + field(type: 'CARD16', name: 'pixmap_height') + field(type: 'CARD32', name: 'pixmap_flags') + } + } + + when: + addChildNodes() + XTypeEvent event = result.resolveXType('ConfigureNotify') + JavaEvent javaEvent = event.javaType + + then: + javaEvent.typeSpecs[0].toString() == '''\ + @lombok.Value + @lombok.Builder + public class ConfigureNotifyEvent implements com.github.moaxcp.x11client.protocol.XGenericEvent { + public static final byte NUMBER = 35; + + private boolean sentEvent; + + private byte extension; + + private short sequenceNumber; + + private short eventType; + + private int event; + + private int window; + + private short x; + + private short y; + + private short width; + + private short height; + + private short offX; + + private short offY; + + private short pixmapWidth; + + private short pixmapHeight; + + private int pixmapFlags; + + @java.lang.Override + public byte getResponseCode() { + return NUMBER; + } + + public static com.github.moaxcp.x11client.protocol.xproto.ConfigureNotifyEvent readConfigureNotifyEvent( + boolean sentEvent, byte extension, short sequenceNumber, int length, short eventType, + com.github.moaxcp.x11client.protocol.X11Input in) throws java.io.IOException { + in.readPad(2); + int event = in.readCard32(); + int window = in.readCard32(); + short x = in.readInt16(); + short y = in.readInt16(); + short width = in.readCard16(); + short height = in.readCard16(); + short offX = in.readInt16(); + short offY = in.readInt16(); + short pixmapWidth = in.readCard16(); + short pixmapHeight = in.readCard16(); + int pixmapFlags = in.readCard32(); + com.github.moaxcp.x11client.protocol.xproto.ConfigureNotifyEvent.ConfigureNotifyEventBuilder javaBuilder = com.github.moaxcp.x11client.protocol.xproto.ConfigureNotifyEvent.builder(); + javaBuilder.extension(extension); + javaBuilder.sequenceNumber(sequenceNumber); + javaBuilder.eventType(eventType); + javaBuilder.event(event); + javaBuilder.window(window); + javaBuilder.x(x); + javaBuilder.y(y); + javaBuilder.width(width); + javaBuilder.height(height); + javaBuilder.offX(offX); + javaBuilder.offY(offY); + javaBuilder.pixmapWidth(pixmapWidth); + javaBuilder.pixmapHeight(pixmapHeight); + javaBuilder.pixmapFlags(pixmapFlags); + + javaBuilder.sentEvent(sentEvent); + return javaBuilder.build(); + } + + @java.lang.Override + public void write(com.github.moaxcp.x11client.protocol.X11Output out) throws java.io.IOException { + out.writeCard8(sentEvent ? 0b10000000 & NUMBER : NUMBER); + out.writeCard8(extension); + out.writeCard16(sequenceNumber); + out.writeCard32(getLength() - 32); + out.writeCard16(eventType); + out.writePad(2); + out.writeCard32(event); + out.writeCard32(window); + out.writeInt16(x); + out.writeInt16(y); + out.writeCard16(width); + out.writeCard16(height); + out.writeInt16(offX); + out.writeInt16(offY); + out.writeCard16(pixmapWidth); + out.writeCard16(pixmapHeight); + out.writeCard32(pixmapFlags); + } + + @java.lang.Override + public int getSize() { + return 40; + } + + public static class ConfigureNotifyEventBuilder { + public int getSize() { + return 40; + } + } + } + '''.stripIndent() + } +} diff --git a/buildSrc/src/test/groovy/com/github/moaxcp/x11protocol/xcbparser/XTypeEventSpec.groovy b/buildSrc/src/test/groovy/com/github/moaxcp/x11protocol/xcbparser/XTypeEventSpec.groovy index 694736c1..667af642 100644 --- a/buildSrc/src/test/groovy/com/github/moaxcp/x11protocol/xcbparser/XTypeEventSpec.groovy +++ b/buildSrc/src/test/groovy/com/github/moaxcp/x11protocol/xcbparser/XTypeEventSpec.groovy @@ -20,10 +20,10 @@ class XTypeEventSpec extends XmlSpec { then: event.name == 'CompleteNotify' - event.number == 1 - event.protocol[1].align == 8 - event.protocol[3].name == 'kind' - event.protocol[4].name == 'serial' + event.number == 35 + event.protocol[5].align == 8 + event.protocol[6].name == 'kind' + event.protocol[7].name == 'serial' } } diff --git a/src/main/java/com/github/moaxcp/x11client/ProtocolPluginService.java b/src/main/java/com/github/moaxcp/x11client/ProtocolPluginService.java new file mode 100644 index 00000000..0bfcfc4e --- /dev/null +++ b/src/main/java/com/github/moaxcp/x11client/ProtocolPluginService.java @@ -0,0 +1,106 @@ +package com.github.moaxcp.x11client; + +import com.github.moaxcp.x11client.protocol.XProtocolPlugin; +import com.github.moaxcp.x11client.protocol.XRequest; +import com.github.moaxcp.x11client.protocol.xproto.XprotoPlugin; + +import java.util.*; + +import static java.util.stream.Collectors.toList; + +class ProtocolPluginService { + private final ServiceLoader loader = ServiceLoader.load(XProtocolPlugin.class); + private final List activatedPlugins = new ArrayList<>(); + + ProtocolPluginService() { + for(XProtocolPlugin plugin : loader) { + if(plugin instanceof XprotoPlugin) { + activatedPlugins.add(plugin); + break; + } + } + } + + List listLoadedPlugins() { + List result = new ArrayList<>(); + for(XProtocolPlugin plugin : loader) { + result.add(plugin.getName()); + } + return Collections.unmodifiableList(result); + } + + private Optional loadedPlugin(String name) { + for(XProtocolPlugin plugin : loader) { + if(plugin.getName().equals(name)) { + return Optional.of(plugin); + } + } + return Optional.empty(); + } + + List listActivatedPlugins() { + return activatedPlugins.stream() + .map(XProtocolPlugin::getName) + .collect(toList()); + } + + boolean activatePlugin(String name, byte majorOpcode, byte firstEvent, byte firstError) { + Optional find = getActivatedPlugin(name); + if(find.isPresent()) { + throw new IllegalStateException("Plugin \"" + name + " \" is already activated."); + } + + Optional loaded = loadedPlugin(name); + if(loaded.isPresent()) { + XProtocolPlugin plugin = loaded.get(); + if(plugin.getMajorVersion() != majorOpcode) { + return false; //client must match server version + } + plugin.setFirstEvent(firstEvent); + plugin.setFirstError(firstError); + activatedPlugins.add(plugin); + return true; + } + return false; + } + + private Optional getActivatedPlugin(String name) { + return activatedPlugins.stream() + .filter(p -> p.getName().equals(name)) + .findFirst(); + } + + boolean activatedPlugin(String name) { + return getActivatedPlugin(name).isPresent(); + } + + byte majorVersionForRequest(XRequest request) { + return activatedPlugins.stream() + .filter(p -> p.supportedRequest(request)) + .findFirst() + .map(XProtocolPlugin::getMajorVersion) + .orElseThrow(() -> new UnsupportedOperationException("Plugin missing or not activated for request. Could not find majorOpcode for request: " + request)); + } + + Optional activePluginForMajorOpcode(byte majorOpcode) { + return activatedPlugins.stream() + .filter(p -> p.getMajorVersion() == majorOpcode) + .findFirst(); + } + + Optional activePluginForError(byte code) { + return activatedPlugins.stream() + .filter(p -> p.supportedError(code)) + .findFirst(); + } + + boolean genericEventNumber(byte number) { + return number == 35; //see xproto.xml + } + + Optional activePluginForEvent(byte number) { + return activatedPlugins.stream() + .filter(p -> p.supportedEvent(number)) + .findFirst(); + } +} diff --git a/src/main/java/com/github/moaxcp/x11client/XProtocolService.java b/src/main/java/com/github/moaxcp/x11client/XProtocolService.java index 532056f1..61ec37ad 100644 --- a/src/main/java/com/github/moaxcp/x11client/XProtocolService.java +++ b/src/main/java/com/github/moaxcp/x11client/XProtocolService.java @@ -5,14 +5,14 @@ import com.github.moaxcp.x11client.protocol.xproto.QueryExtension; import com.github.moaxcp.x11client.protocol.xproto.QueryExtensionReply; import com.github.moaxcp.x11client.protocol.xproto.Setup; -import com.github.moaxcp.x11client.protocol.xproto.XprotoPlugin; import lombok.Getter; import java.io.IOException; -import java.util.*; +import java.util.LinkedList; +import java.util.Optional; +import java.util.Queue; class XProtocolService { - private final ServiceLoader loader = ServiceLoader.load(XProtocolPlugin.class); private final X11Input in; private final X11Output out; @Getter @@ -23,53 +23,41 @@ class XProtocolService { private long maximumRequestLength; private final Queue requests = new LinkedList<>(); private final Queue events = new LinkedList<>(); - private final List activatedPlugins = new ArrayList<>(); + private final ProtocolPluginService pluginService; XProtocolService(Setup setup, X11Input in, X11Output out) { this.in = in; this.out = out; this.setup = setup; maximumRequestLength = setup.getMaximumRequestLength(); - for(XProtocolPlugin plugin : loader) { - if(plugin instanceof XprotoPlugin) { - activatedPlugins.add(plugin); - continue; - } - String name = plugin.getName(); - QueryExtension request = QueryExtension.builder() - .name(Utilities.toByteList(name)) - .build(); - QueryExtensionReply reply = send(request); - if(reply.isPresent()) { - plugin.setMajorOpcode(reply.getMajorOpcode()); - plugin.setFirstEvent(reply.getFirstEvent()); - plugin.setFirstError(reply.getFirstError()); - activatedPlugins.add(plugin); - } + pluginService = new ProtocolPluginService(); + for(String name : pluginService.listLoadedPlugins()) { + activatePlugin(name); } - if(loadedPlugin("BIG-REQUESTS")) { + if(pluginService.activatedPlugin("BIG-REQUESTS")) { maximumRequestLength = Integer.toUnsignedLong(send(Enable.builder().build()) .getMaximumRequestLength()); } } - public boolean loadedPlugin(String name) { - for(XProtocolPlugin plugin : loader) { - if(plugin.getName().equals(name)) { - return true; - } + public boolean activatePlugin(String name) { + QueryExtension request = QueryExtension.builder() + .name(Utilities.toByteList(name)) + .build(); + QueryExtensionReply reply = send(request); + if(reply.isPresent()) { + return pluginService.activatePlugin(name, reply.getMajorOpcode(), reply.getFirstEvent(), reply.getFirstError()); } return false; } + public boolean loadedPlugin(String name) { + return pluginService.listLoadedPlugins().contains(name); + } + public boolean activatedPlugin(String name) { - for(XProtocolPlugin plugin : activatedPlugins) { - if(plugin.getName().equals(name)) { - return true; - } - } - return false; + return pluginService.listActivatedPlugins().contains(name); } public T send(TwoWayRequest request) { @@ -83,20 +71,10 @@ public void send(OneWayRequest request) { } private void actuallySend(XRequest request) { - boolean sent = false; - for(XProtocolPlugin plugin : activatedPlugins) { - if(plugin.supportedRequest(request)) { - try { - request.write(plugin.getMajorOpcode(), out); - } catch(IOException e) { - throw new X11ClientException("exception when writing with plugin \"" + plugin.getName() + "\"", e); - } - sent = true; - break; - } - } - if(!sent) { - throw new UnsupportedOperationException(String.format("could not find plugin for request \"%s\"", request)); + try { + request.write(pluginService.majorVersionForRequest(request), out); + } catch(IOException e) { + throw new X11ClientException("exception writing request \"" + request + "\"", e); } nextSequenceNumber++; } @@ -122,26 +100,28 @@ private T readReply(XReplyFunction function) { private XError readError() throws IOException { byte code = in.readCard8(); - for(XProtocolPlugin reader : activatedPlugins) { - if(reader.supportedError(code)) { - return reader.readError(code, in); - } - } - throw new IllegalStateException(XProtocolPlugin.class.getSimpleName() + " not found for error code " + code); + XProtocolPlugin plugin = pluginService.activePluginForError(code).orElseThrow(() -> new IllegalStateException(XProtocolPlugin.class.getSimpleName() + " not found for error code " + code)); + return plugin.readError(code, in); } private XEvent readEvent(byte responseCode) throws IOException { boolean sentEvent = responseCode < 0; - byte number = responseCode; - if(sentEvent) { - number = (byte) (responseCode ^ (byte) 0b10000000); - } - for(XProtocolPlugin reader : activatedPlugins) { - if(reader.supportedEvent(number)) { - return reader.readEvent(number, sentEvent, in); + byte number = !sentEvent ? responseCode : (byte) (responseCode ^ (byte) 0b10000000); + if(pluginService.genericEventNumber(number)) { + byte extension = in.readCard8(); + Optional pluginExtension = pluginService.activePluginForMajorOpcode(extension); + if(!pluginExtension.isPresent()) { + throw new IllegalStateException("Could not find plugin for generic event with major-opcode of " + extension); } + XProtocolPlugin plugin = pluginExtension.get(); + + short sequenceNumber = in.readCard16(); + int length = in.readCard32(); + short eventType = in.readCard16(); + return plugin.readGenericEvent(sentEvent, extension, sequenceNumber, length, eventType, in); } - throw new IllegalStateException(XProtocolPlugin.class.getSimpleName() + " not found for number " + number); + XProtocolPlugin plugin = pluginService.activePluginForEvent(number).orElseThrow(() -> new IllegalStateException(XProtocolPlugin.class.getSimpleName() + " not found for number " + number)); + return plugin.readEvent(number, sentEvent, in); } public XEvent getNextEvent() { diff --git a/src/main/java/com/github/moaxcp/x11client/protocol/XGenericEvent.java b/src/main/java/com/github/moaxcp/x11client/protocol/XGenericEvent.java new file mode 100644 index 00000000..5bb6bcfd --- /dev/null +++ b/src/main/java/com/github/moaxcp/x11client/protocol/XGenericEvent.java @@ -0,0 +1,9 @@ +package com.github.moaxcp.x11client.protocol; + +public interface XGenericEvent extends XEvent { + byte getExtension(); + default int getLength() { + return ((getSize() + 4 - getSize() % 4) / 4); + } + short getEventType(); +} diff --git a/src/main/java/com/github/moaxcp/x11client/protocol/XProtocolPlugin.java b/src/main/java/com/github/moaxcp/x11client/protocol/XProtocolPlugin.java index 2a4070a0..9cf1304d 100644 --- a/src/main/java/com/github/moaxcp/x11client/protocol/XProtocolPlugin.java +++ b/src/main/java/com/github/moaxcp/x11client/protocol/XProtocolPlugin.java @@ -4,8 +4,8 @@ public interface XProtocolPlugin { String getName(); - byte getMajorOpcode(); - void setMajorOpcode(byte majorOpcode); + byte getMajorVersion(); + byte getMinorVersion(); byte getFirstEvent(); void setFirstEvent(byte firstEvent); byte getFirstError(); @@ -15,4 +15,5 @@ public interface XProtocolPlugin { boolean supportedError(byte code); XEvent readEvent(byte number, boolean sentEvent, X11Input in) throws IOException; XError readError(byte code, X11Input in) throws IOException; + XGenericEvent readGenericEvent(boolean sentEvent, byte extension, short sequenceNumber, int length, short eventType, X11Input in) throws IOException; }