x11 icon indicating copy to clipboard operation
x11 copied to clipboard

RECORD plugin can't work

Open wenfer opened this issue 1 year ago • 19 comments

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?

wenfer avatar Nov 29 '23 09:11 wenfer

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.

moaxcp avatar Nov 29 '23 18:11 moaxcp

/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);
            }
        }
    }
  }```

wenfer avatar Nov 30 '23 05:11 wenfer

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 avatar Dec 04 '23 04:12 moaxcp

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 avatar Dec 05 '23 04:12 moaxcp

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());
  }

moaxcp avatar Dec 05 '23 22:12 moaxcp

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

wenfer avatar Dec 07 '23 01:12 wenfer

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

wenfer avatar Dec 07 '23 01:12 wenfer

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.

moaxcp avatar Dec 07 '23 23:12 moaxcp

Now

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

wenfer avatar Dec 08 '23 10:12 wenfer

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.

moaxcp avatar Dec 09 '23 22:12 moaxcp

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 !!!

wenfer avatar Dec 12 '23 01:12 wenfer

This should be fixed now in 0.14.0

moaxcp avatar Dec 20 '23 23:12 moaxcp

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

wenfer avatar Dec 22 '23 06:12 wenfer

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 avatar Dec 25 '23 00:12 moaxcp

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 [email protected]/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at [email protected]/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 avatar Dec 25 '23 00:12 moaxcp

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

moaxcp avatar Dec 26 '23 19:12 moaxcp

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 avatar Jan 10 '24 04:01 moaxcp

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 avatar Feb 02 '24 23:02 moaxcp

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

moaxcp avatar Mar 15 '24 21:03 moaxcp