From a59f6fe0103599e9d806683086f8386db2ebfe52 Mon Sep 17 00:00:00 2001 From: John Mercier Date: Wed, 30 Dec 2020 17:13:06 -0500 Subject: [PATCH] Adding defaultGC and updating readme and ITs. --- README.md | 141 +++++++++++++++++- .../github/moaxcp/x11client/X11ClientIT.java | 62 +++++++- .../github/moaxcp/x11client/X11Client.java | 16 ++ 3 files changed, 209 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 2197d8e1..8e245a9f 100644 --- a/README.md +++ b/README.md @@ -4,12 +4,12 @@ x11-client enables java and other jvm languages to talk directly to a x11 server without binding to a C library. The client is similar to X11lib for C but uses objects to represent the protocol resulting in a simplified client. It supports the core protocol and the following extensions: bigreq, composite, -damage, dpms, dri2, ge, record, render, res, screensaver, shape, sync, xc_misc, -xevie, xf86dri, xf86vidmode, xfixes, xinerama, xprint, xselinux, xtest. The -client is similar to X11lib and follows the same pattern of queuing one-way -requests before sending them to the server. +damage, dpms, dri2, ge, randr, record, render, res, screensaver, shape, shm, +sync, xc_misc, xevie, xf86dri, xf86vidmode, xfixes, xinerama, xprint, xselinux, +xtest, xv, xvmc. The client is similar to X11lib and follows the same pattern +of queuing one-way requests before sending them to the server. -![Java CI with Gradle](https://github.com/moaxcp/x11-client/workflows/Java%20CI%20with%20Gradle/badge.svg?branch=master) +[![Java CI with Gradle](https://github.com/moaxcp/x11-client/workflows/Java%20CI%20with%20Gradle/badge.svg?branch=master)](https://github.com/moaxcp/x11-client/actions?query=workflow%3A%22Java+CI+with+Gradle%22) [![maven central](https://img.shields.io/maven-central/v/com.github.moaxcp.x11/x11-client)](https://search.maven.org/artifact/com.github.moaxcp.x11/x11-client) [![javadoc](https://javadoc.io/badge2/com.github.moaxcp.x11/x11-client/javadoc.svg)](https://javadoc.io/doc/com.github.moaxcp.x11/x11-client) [![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=com.github.moaxcp.x11%3Ax11-client&metric=alert_status)](https://sonarcloud.io/dashboard?id=com.github.moaxcp.x11%3Ax11-client) @@ -145,6 +145,9 @@ These examples are conversions of a X11lib example written in C. ## Hello World +This is an example of a simple window. In this example none of the helper +methods are used. Raw requests are built and sent directly to the server. + ``` try(X11Client x11Client = X11Client.connect()) { CreateWindow window = CreateWindow.builder() @@ -191,7 +194,6 @@ try(X11Client x11Client = X11Client.connect()) { x11Client.send(ImageText8.builder() .drawable(window.getWid()) .gc(gc.getCid()) - .stringLen((byte) "Hello World!".length()) .string(stringToByteList("Hello World!")) .x((short) 10) .y((short) 50) @@ -203,6 +205,116 @@ try(X11Client x11Client = X11Client.connect()) { } ``` +The next example is the same window but uses helper methods in the client. + +``` +try(X11Client client = X11Client.connect()) { + int wid = client.createSimpleWindow((short) 10, (short) 10, (short) 600, (short) 480, EventMask.EXPOSURE, EventMask.KEY_PRESS); + client.storeName(wid, "Hello World!"); + int deleteAtom = client.getAtom("WM_DELETE_WINDOW"); + client.setWMProtocols(wid, deleteAtom); + client.mapWindow(wid); + int gc = client.createGC(0, wid); + while(true) { + XEvent event = client.getNextEvent(); + if(event instanceof ExposeEvent) { + client.fillRectangle(wid, gc, (short) 20, (short) 20, (short) 10, (short) 10); + client.drawString(wid, gc, (short) 10, (short) 50, "Hello World!"); + } else if(event instanceof KeyPressEvent) { + break; + } else if(event instanceof ClientMessageEvent) { + ClientMessageEvent clientMessage = (ClientMessageEvent) event; + if(clientMessage.getFormat() == 32) { + ClientMessageData32 data = (ClientMessageData32) clientMessage.getData(); + if(data.getData32().get(0) == deleteAtom) { + break; + } + } + } + } +} +``` + +## TinyWM + +[TinyWM](http://incise.org/tinywm.html) is a famous small window manager +written in around 50 lines of code. This example is the implementation in java. + +``` +try(X11Client client = X11Client.connect(new DisplayName(":1"))) { + int wid = client.createSimpleWindow(10, 10, 200, 200); + client.mapWindow(wid); + client.send(GrabKey.builder() + .key((byte) client.keySymToKeyCode(KeySym.getByName("F1").get().getValue())) + .modifiersEnable(ModMask.ONE) + .grabWindow(client.getRoot(0)) + .ownerEvents(true) + .keyboardMode(GrabMode.ASYNC) + .pointerMode(GrabMode.ASYNC) + .build()); + client.send(GrabButton.builder() + .button(ButtonIndex.ONE) + .modifiersEnable(ModMask.ONE) + .grabWindow(client.getRoot(0)) + .ownerEvents(true) + .eventMaskEnable(BUTTON_PRESS, BUTTON_RELEASE, POINTER_MOTION) + .keyboardMode(GrabMode.ASYNC) + .pointerMode(GrabMode.ASYNC) + .build()); + client.send(GrabButton.builder() + .button(ButtonIndex.THREE) + .modifiersEnable(ModMask.ONE) + .grabWindow(client.getRoot(0)) + .ownerEvents(true) + .eventMaskEnable(BUTTON_PRESS, BUTTON_RELEASE, POINTER_MOTION) + .keyboardMode(GrabMode.ASYNC) + .pointerMode(GrabMode.ASYNC) + .build()); + + GetGeometryReply geometry = null; + ButtonPressEvent start = null; + + while(true) { + XEvent event = client.getNextEvent(); + if(event instanceof KeyPressEvent) { + KeyPressEvent keyPress = (KeyPressEvent) event; + int child = keyPress.getChild(); + if(child != Window.NONE.getValue()) { + client.raiseWindow(child); + } + } else if(event instanceof ButtonPressEvent) { + ButtonPressEvent buttonPress = (ButtonPressEvent) event; + int child = buttonPress.getChild(); + if(child != Window.NONE.getValue()) { + geometry = client.send(GetGeometry.builder() + .drawable(child) + .build()); + start = buttonPress; + } + } else if(event instanceof MotionNotifyEvent) { + MotionNotifyEvent motionNotify = (MotionNotifyEvent) event; + int child = motionNotify.getChild(); + if(child != Window.NONE.getValue()) { + int xdiff = motionNotify.getRootX() - start.getRootX(); + int ydiff = motionNotify.getRootY() - start.getRootY(); + client.send(ConfigureWindow.builder() + .window(child) + .x(geometry.getX() + (start.getDetail() == ButtonIndex.ONE.getValue() ? xdiff : 0)) + .y(geometry.getY() + (start.getDetail() == ButtonIndex.ONE.getValue() ? ydiff : 0)) + .width(Math.max(1, geometry.getWidth() + (start.getDetail() == ButtonIndex.THREE.getValue() ? xdiff : 0))) + .height(Math.max(1, geometry.getHeight() + (start.getDetail() == ButtonIndex.THREE.getValue() ? ydiff : 0))) + .build()); + } + } else if(event instanceof ButtonReleaseEvent) { + start = null; + } + } +} +``` + +The java version is a bit longer due to the builder pattern being a little more +verbose. + # Design ## Request Prossesing @@ -320,15 +432,30 @@ and will likely move into a new project. Removing length properties from protocol objects where it is simply the list size. The length still needs to be set for properties involving complex -expressions. +expressions. This results in not having to set the length of lists on most +objects. For example drawing a string no longer requires the size. + +``` +client.send(ImageText8.builder() + .drawable(window.getWid()) + .gc(gc.getCid()) + .string(stringToByteList("Hello World!")) + .x((short) 10) + .y((short) 50) + .build()); +``` Adding exclude to x11protocol plugin. Fixing issues with objects missing padding for the first field. This enables the sync extension to work. +Added support for file descriptors. They are simply treated as an `int`. + Added extensions shm, sync xrandr, xv, and xvmc +Added defaultGC cache for root windows and method to automatically create them. + ## 0.3.0 Adding TinyWM example. diff --git a/src/integrationTest/java/com/github/moaxcp/x11client/X11ClientIT.java b/src/integrationTest/java/com/github/moaxcp/x11client/X11ClientIT.java index d0100129..e8aa96c6 100644 --- a/src/integrationTest/java/com/github/moaxcp/x11client/X11ClientIT.java +++ b/src/integrationTest/java/com/github/moaxcp/x11client/X11ClientIT.java @@ -15,7 +15,7 @@ public class X11ClientIT { @Test - void simpleHelloWorld() throws IOException { + void simpleHelloWorldMouse() throws IOException { try(X11Client client = X11Client.connect()) { CreateWindow window = CreateWindow.builder() .depth(client.getDepth(0)) @@ -121,6 +121,64 @@ void simpleHelloWorld() throws IOException { } } + @Test + void simpleHelloWorld() throws IOException { + try(X11Client x11Client = X11Client.connect()) { + CreateWindow window = CreateWindow.builder() + .depth(x11Client.getDepth(0)) + .wid(x11Client.nextResourceId()) + .parent(x11Client.getRoot(0)) + .x((short) 10) + .y((short) 10) + .width((short) 600) + .height((short) 480) + .borderWidth((short) 5) + .clazz(WindowClass.COPY_FROM_PARENT) + .visual(x11Client.getVisualId(0)) + .backgroundPixel(x11Client.getWhitePixel(0)) + .borderPixel(x11Client.getBlackPixel(0)) + .eventMaskEnable(EventMask.EXPOSURE, EventMask.KEY_PRESS) + .build(); + x11Client.send(window); + x11Client.send(MapWindow.builder() + .window(window.getWid()) + .build()); + CreateGC gc = CreateGC.builder() + .cid(x11Client.nextResourceId()) + .drawable(window.getWid()) + .background(x11Client.getWhitePixel(0)) + .foreground(x11Client.getBlackPixel(0)) + .build(); + x11Client.send(gc); + while(true) { + XEvent event = x11Client.getNextEvent(); + if(event instanceof ExposeEvent) { + List rectangles = new ArrayList<>(); + rectangles.add(Rectangle.builder() + .x((short) 20) + .y((short) 20) + .width((short) 10) + .height((short) 10) + .build()); + x11Client.send(PolyFillRectangle.builder() + .drawable(window.getWid()) + .gc(gc.getCid()) + .rectangles(rectangles) + .build()); + x11Client.send(ImageText8.builder() + .drawable(window.getWid()) + .gc(gc.getCid()) + .string(stringToByteList("Hello World!")) + .x((short) 10) + .y((short) 50) + .build()); + } else if(event instanceof KeyPressEvent) { + break; + } + } + } + } + @Test void clientTestXFunctions() throws IOException { try(X11Client client = X11Client.connect()) { @@ -145,8 +203,6 @@ void clientTestXFunctions() throws IOException { break; } } - } else { - throw new IllegalStateException(event.toString()); } } } diff --git a/src/main/java/com/github/moaxcp/x11client/X11Client.java b/src/main/java/com/github/moaxcp/x11client/X11Client.java index f72f8ad6..ba4e165b 100644 --- a/src/main/java/com/github/moaxcp/x11client/X11Client.java +++ b/src/main/java/com/github/moaxcp/x11client/X11Client.java @@ -5,6 +5,8 @@ import java.io.IOException; import java.nio.ByteBuffer; import java.util.Collections; +import java.util.HashMap; +import java.util.Map; import lombok.NonNull; import static com.github.moaxcp.x11client.protocol.Utilities.byteArrayToList; @@ -18,6 +20,7 @@ public class X11Client implements AutoCloseable { private final XProtocolService protocolService; private final ResourceIdService resourceIdService; private final AtomService atomService; + private final Map defaultGCs = new HashMap<>(); /** * Creates a client for the given displayName and authority. @@ -119,6 +122,7 @@ public void flush() { */ @Override public void close() throws IOException { + defaultGCs.values().stream().forEach(i -> send(FreeGC.builder().gc(i).build())); connection.close(); } @@ -186,6 +190,7 @@ public void storeName(int wid, String name) { .property(Atom.WM_NAME.getValue()) .type(Atom.STRING.getValue()) .format((byte) 8) + .dataLen(name.length()) .data(stringToByteList(name)) .build()); } @@ -198,6 +203,7 @@ public void setWMProtocols(int wid, int atom) { .type(Atom.ATOM.getValue()) .format((byte) 32) .mode(PropMode.REPLACE) + .dataLen(1) .data(byteArrayToList(ByteBuffer.allocate(4).putInt(atom).array())) .build()); } @@ -219,6 +225,16 @@ public int createGC(int screen, int wid) { return gc; } + public int defaultGC(int screen) { + int root = getRoot(screen); + if(defaultGCs.containsKey(root)) { + return defaultGCs.get(root); + } + int gc = createGC(screen, root); + defaultGCs.put(root, gc); + return gc; + } + /** * see https://github.com/mirror/libX11/blob/master/src/Text.c * @param drawable