moaxcp / x11

An x11 protocol implementation and client for the jvm
MIT License
18 stars 3 forks source link

RECORD plugin can't work #21

Open wenfer opened 7 months ago

wenfer commented 7 months ago

my code

package x11;

import com.github.moaxcp.x11client.X11Client;
import com.github.moaxcp.x11client.protocol.record.*;

import java.io.IOException;
import java.util.List;

public class X11Test {

    public static void main(String[] args) {
        try (X11Client client = X11Client.connect()) {
            int rc = client.nextResourceId();
            Range8 empty = Range8.builder().build();
            ExtRange emptyExtRange = ExtRange.builder()
                    .major(empty)
                    .minor(Range16.builder().build())
                    .build();
            Range range = Range.builder()
                    .deviceEvents(Range8.builder().first((byte) 2).last((byte) 6).build())
                    .coreRequests(empty)
                    .coreReplies(empty)
                    .extRequests(emptyExtRange)
                    .extReplies(emptyExtRange)
                    .deliveredEvents(empty)
                    .clientStarted(false)
                    .errors(empty)
                    .clientDied(false)
                    .build();

            CreateContext createContext = CreateContext.builder()
                    .context(rc)
                    .clientSpecs(List.of(Cs.ALL_CLIENTS.getValue()))
                    .elementHeader((byte) 0)
                    .ranges(List.of(range))
                    .build();
            client.send(createContext);

            EnableContext enableContext = EnableContext
                    .builder()
                    .context(rc)
                    .build();
            EnableContextReply enableContextReply = client.send(enableContext);
            if (enableContextReply.getCategory() == 0) {
                for (Byte datum : enableContextReply.getData()) {
                    System.out.println(datum);
                }
            }
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

}

the code can't work, got this exception

Exception in thread "main" com.github.moaxcp.x11client.X11ErrorException: WindowError(firstErrorOffset=0, sequenceNumber=2, badValue=230686721, minorOpcode=0, majorOpcode=2)
    at com.github.moaxcp.x11client.XProtocolService.readReply(XProtocolService.java:87)
    at com.github.moaxcp.x11client.XProtocolService.send(XProtocolService.java:66)
    at com.github.moaxcp.x11client.X11Client.send(X11Client.java:238)
    at x11.X11Test.main(X11Test.java:51)

is somewhere wrong?

moaxcp commented 7 months ago

I would check the x11 server logs to see if there is anything helpful there. You can also compare the the java classes to the C library. There could be something it does differently before submitting the request.

wenfer commented 7 months ago

/the code is convert from rust(x11rb), that's work in rust,i show you code


fn main() {
    let (conn, screen_num) = x11rb::connect(None).unwrap();
    let rc = conn.generate_id().unwrap();
    let empty = Range8 { first: 0, last: 0 };
    let empty_ext = ExtRange {
        major: empty,
        minor: record::Range16 { first: 0, last: 0 },
    };
    let range = record::Range {
        core_requests: empty,
        core_replies: empty,
        ext_requests: empty_ext,
        ext_replies: empty_ext,
        delivered_events: empty,
        device_events: Range8 {
            // We want notification of core X11 events between key press and motion notify
            first: xproto::KEY_PRESS_EVENT,
            last: xproto::MOTION_NOTIFY_EVENT,
        },
        errors: empty,
        client_started: false,
        client_died: false,
    };
    let _ = conn.record_create_context(rc,
                                       0,
                                       &[CS::ALL_CLIENTS.into()],
                                       &[range]);
    for reply in conn.record_enable_context(rc).unwrap() {
        match reply {
            Ok(reply) => {
                //todo something
            }
            Err(err) => {
                println!("error:{}", err);
            }
        }
    }
  }```
moaxcp commented 7 months ago

The problem is with ProtocolPluginService

      if(plugin.getMajorVersion() != majorOpcode) {
        return false; //client must match server version
      }

I assumed this was a way to make sure the versions between the client and server matched.

What needs to happen is the plugin should send a QueryVersion request and return the major and minor versions so ProtocolPluginService can check they match.

moaxcp commented 7 months ago

I started working on this in the fix-plugins branch. Now I am getting a LengthError.

LengthError(firstErrorOffset=0, sequenceNumber=34, badValue=0, minorOpcode=0, majorOpcode=-109)
com.github.moaxcp.x11client.X11ErrorException: LengthError(firstErrorOffset=0, sequenceNumber=34, badValue=0, minorOpcode=0, majorOpcode=-109)
    at app//com.github.moaxcp.x11client.XProtocolService.readReply(XProtocolService.java:87)
    at app//com.github.moaxcp.x11client.XProtocolService.send(XProtocolService.java:66)
    at app//com.github.moaxcp.x11client.X11Client.send(X11Client.java:238)
    at app//com.github.moaxcp.x11client.RecordIT.test(RecordIT.java:43)
moaxcp commented 7 months ago

I added a sync after the CreateContext request and that is the request causing the failure. From there I logged the request. Can you tell if the length is correct?

CreateContext(context=83886081, elementHeader=0, clientSpecs=[3], ranges=[Range(coreRequests=Range8(first=0, last=0), coreReplies=Range8(first=0, last=0), extRequests=ExtRange(major=Range8(first=0, last=0), minor=Range16(first=0, last=0)), extReplies=ExtRange(major=Range8(first=0, last=0), minor=Range16(first=0, last=0)), deliveredEvents=Range8(first=0, last=0), deviceEvents=Range8(first=2, last=6), errors=Range8(first=0, last=0), clientStarted=false, clientDied=false)]), size: 48, length: 12

Or maybe the write method is generated wrong?


  @Override
  public void write(byte offset, X11Output out) throws IOException {
    out.writeCard8((byte)(Byte.toUnsignedInt(OPCODE) + Byte.toUnsignedInt(offset)));
    out.writePad(1);
    out.writeCard16((short) getLength());
    out.writeCard32(context);
    out.writeCard8(elementHeader);
    out.writePad(3);
    int numClientSpecs = clientSpecs.size();
    out.writeCard32(numClientSpecs);
    int numRanges = ranges.size();
    out.writeCard32(numRanges);
    out.writeCard32(clientSpecs);
    for(Range t : ranges) {
      t.write(out);
    }
    out.writePadAlign(getSize());
  }
wenfer commented 7 months ago

i see release 0.13.0,it's fixed ? i can't get from maven central , did pushed to nexus ?

wenfer commented 7 months ago

I added a sync after the CreateContext request and that is the request causing the failure. From there I logged the request. Can you tell if the length is correct?

CreateContext(context=83886081, elementHeader=0, clientSpecs=[3], ranges=[Range(coreRequests=Range8(first=0, last=0), coreReplies=Range8(first=0, last=0), extRequests=ExtRange(major=Range8(first=0, last=0), minor=Range16(first=0, last=0)), extReplies=ExtRange(major=Range8(first=0, last=0), minor=Range16(first=0, last=0)), deliveredEvents=Range8(first=0, last=0), deviceEvents=Range8(first=2, last=6), errors=Range8(first=0, last=0), clientStarted=false, clientDied=false)]), size: 48, length: 12

Or maybe the write method is generated wrong?


  @Override
  public void write(byte offset, X11Output out) throws IOException {
    out.writeCard8((byte)(Byte.toUnsignedInt(OPCODE) + Byte.toUnsignedInt(offset)));
    out.writePad(1);
    out.writeCard16((short) getLength());
    out.writeCard32(context);
    out.writeCard8(elementHeader);
    out.writePad(3);
    int numClientSpecs = clientSpecs.size();
    out.writeCard32(numClientSpecs);
    int numRanges = ranges.size();
    out.writeCard32(numRanges);
    out.writeCard32(clientSpecs);
    for(Range t : ranges) {
      t.write(out);
    }
    out.writePadAlign(getSize());
  }

sorry , i dont know x11 more , so i can't help you ,i just test record plugin in rust , that's all

moaxcp commented 7 months ago

Anything is possible but length is the error returned. I was just wondering what the length should be. Can you compare it to the length in rust? Or even compare the binary results? There are a few structs involved so it could be an issue with structs. I will have to look into it more.

wenfer commented 7 months ago

Now

which request lengtherror in ? show me the java code, i'll try help

moaxcp commented 6 months ago

I think I figured it out I just need to find some time to work on it. The problem is in writing the output. This is what CreateContext has.

  @Override
  public void write(byte offset, X11Output out) throws IOException {
    out.writeCard8((byte)(Byte.toUnsignedInt(OPCODE) + Byte.toUnsignedInt(offset)));
    out.writePad(1);
    out.writeCard16((short) getLength());
    out.writeCard32(context);
    out.writeCard8(elementHeader);
    out.writePad(3);
    int numClientSpecs = clientSpecs.size();
    out.writeCard32(numClientSpecs);
    int numRanges = ranges.size();
    out.writeCard32(numRanges);
    out.writeCard32(clientSpecs);
    for(Range t : ranges) {
      t.write(out);
    }
    out.writePadAlign(getSize());
  }

For the rust client this is what I found.

let mut request0 = vec![
            major_opcode,
            CREATE_CONTEXT_REQUEST,
            0,
            0,
            context_bytes[0],
            context_bytes[1],
            context_bytes[2],
            context_bytes[3],
            element_header_bytes[0],
            0,
            0,
            0,
            num_client_specs_bytes[0],
            num_client_specs_bytes[1],
            num_client_specs_bytes[2],
            num_client_specs_bytes[3],
            num_ranges_bytes[0],
            num_ranges_bytes[1],
            num_ranges_bytes[2],
            num_ranges_bytes[3],
        ];

The problem is here out.writeCard8((byte)(Byte.toUnsignedInt(OPCODE) + Byte.toUnsignedInt(offset)));. The first byte is the addition of the opcode and the offset or majorOpcode. When what should happen is the majorOpcode should be the first byte followed by OPCODE.

I have to change the gradle-plugin to write the OPCODE instead of a padding for these extensions.

This explains why BigRequest works and all of the QueryVersion requests for the extensions because their OPCODE is 0 but everything else fails.

wenfer commented 6 months ago

The problem is here out.writeCard8((byte)(Byte.toUnsignedInt(OPCODE) + Byte.toUnsignedInt(offset)));. The first byte is the addition of the opcode and the offset or majorOpcode. When what should happen is the majorOpcode should be the first byte followed by OPCODE.

I have to change the gradle-plugin to write the OPCODE instead of a padding for these extensions.

This explains why BigRequest works and all of the QueryVersion requests for the extensions because their OPCODE is 0 but everything else fails.

you are hero !!!

moaxcp commented 6 months ago

This should be fixed now in 0.14.0

wenfer commented 6 months ago

i try in 0.14.0, the samples code return EnableContextReply field category is 4, in rust code category is 0, look like not fixed

moaxcp commented 6 months ago

One thing I noticed is you are supposed to use two clients for this. One for the control client and one for the data client. So I added a data client.

try (X11Client control = X11Client.connect()) {
            int rc = control.nextResourceId();
            Range8 empty = Range8.builder().build();
            ExtRange emptyExtRange = ExtRange.builder()
                    .major(empty)
                    .minor(Range16.builder().build())
                    .build();
            Range range = Range.builder()
                    .deviceEvents(Range8.builder().first(KeyPressEvent.NUMBER).last(MotionNotifyEvent.NUMBER).build())
                    .coreRequests(empty)
                    .coreReplies(empty)
                    .extRequests(emptyExtRange)
                    .extReplies(emptyExtRange)
                    .deliveredEvents(empty)
                    .clientStarted(false)
                    .errors(empty)
                    .clientDied(false)
                    .build();

            CreateContext createContext = CreateContext.builder()
                    .context(rc)
                    .clientSpecs(Collections.singletonList(Cs.ALL_CLIENTS.getValue()))
                    .elementHeader((byte) 0)
                    .ranges(Collections.singletonList(range))
                    .build();

            control.send(createContext);

            control.sync();

            EnableContext enableContext = EnableContext
                    .builder()
                    .context(rc)
                    .build();

            try (X11Client data = X11Client.connect()) {
                EnableContextReply enableContextReply = data.send(enableContext);
                log.info(String.format("enableContextReply: %s", enableContextReply));
            }
        }

I still get a category == 4 reply though.

The rust client shows an issue that I'm not sure my client is made to handle. It looks like it has mutliple replies.

for reply in data_conn.record_enable_context(rc)? {
        let reply = reply?;
        if reply.client_swapped {
            println!("Byte swapped clients are unsupported");
        } else if reply.category == RECORD_FROM_SERVER {
            let mut remaining = &reply.data[..];
            let mut should_exit = false;
            while !remaining.is_empty() {
                let (r, exit) = print_reply_data(&reply.data)?;
                remaining = r;
                if exit {
                    should_exit = true;
                }
            }
            if should_exit {
                break;
            }
        } else if reply.category == START_OF_DATA {
            println!("Press Escape to exit...");
        } else {
            println!("Got a reply with an unsupported category: {:?}", reply);
        }
    }

The current client does not support multiple replies for one request and I'm not sure it makes sense to me yet. Maybe these are events though. I'm just not sure.

moaxcp commented 6 months ago

To test multiple replies I added this.


            try (X11Client data = X11Client.connect()) {
                EnableContextReply enableContextReply = data.send(enableContext);
                log.info(String.format("enableContextReply: %s", enableContextReply));

                data.getNextEvent();
                data.getNextEvent();
            }

Then I get an error

com.github.moaxcp.x11client.X11ClientException: reply not expected when reading events
    at app//com.github.moaxcp.x11client.XProtocolService.getNextEvent(XProtocolService.java:138)
    at app//com.github.moaxcp.x11client.X11Client.getNextEvent(X11Client.java:251)
    at app//com.github.moaxcp.x11client.RecordIT.test(RecordIT.java:54)
    at java.base@17.0.7/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at java.base@17.0.7/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77)

It seems like a special case for the record extension and I'm not sure how to change the api for it.

moaxcp commented 6 months ago

I added this PR that seems to work. https://github.com/moaxcp/x11-client/pull/27

moaxcp commented 5 months ago

I updated the PR to help in reading the data in the record replies. Reading x11 replies is going to be tricky because I also need to track the sequence numbers of the requests to figure out what reply is being read.

moaxcp commented 5 months ago

I updated the PR with everything I have. I don't think it will work for reading requests and replies but it does work for most examples I have seen online.

moaxcp commented 3 months ago

I merged the PR and published the changes in 0.15.0. It should show up in maven soon.