joncampbell123 / dosbox-x

DOSBox-X fork of the DOSBox project
GNU General Public License v2.0
2.83k stars 383 forks source link

External application control interface via named pipe #4601

Open joncampbell123 opened 1 year ago

joncampbell123 commented 1 year ago

Is your feature request related to a problem? Please describe.

I've been getting requests lately from people who obviously want to control DOSBox-X from another program, such as a recent one where a programmer wishes to direct DOSBox-X from their Python script.

What you want

I don't think my suggestion of sending to a named pipe serial port in DOSBox-X and writing a TSR is coming off well with these users, so I think it's time to consider a more direct interface via a named pipe (that you specify in dosbox.conf or the command line) since that is something Python could easily support.

The external commands would allow most of the same injection/monitoring that the DOSBox Integration Device already allows.

The programmer in question wishes to inject mouse movement into the guest DOS environment.

Describe alternatives you've considered

No response

Additional information

No response

Have you checked that no similar feature request(s) exist?

Code of Conduct & Contributing Guidelines

Raarbiarsan1899 commented 1 year ago

Is that possible to write named pipe and inject an SDL_event for GFX_Events function start at line 5363 of sdlmain.cpp? I tried hard-coding for the case SDL_MOUSEMOTION and the mouse does stay at the hard-coded position (x 235 y 347). My thought is to override polled event and inject a customed event. Is that achievable? Thanks!

void GFX_Events() {

....

SDL_Event event;

....

while (SDL_PollEvent(&event)) {

    ......

    switch (event.type) {

    ......

    case SDL_MOUSEMOTION:

        int32_t test_x = 235;
        int32_t test_y = 347;

        event.motion.state = 0;
        event.motion.which = 0;
        event.motion.x = test_x;
        event.motion.y = test_y;
        event.motion.xrel = 0l;
        event.motion.yrel = 0l;
        //event.motion.xrel = (Sint16)((rx >= 0) ? min(rx, 32767l) : max(rx, -32768l));
        //event.motion.yrel = (Sint16)((ry >= 0) ? min(ry, 32767l) : max(ry, -32768l));

        HandleMouseMotion(&event.motion);
        break;

        ......
joncampbell123 commented 1 year ago

What I had in mind was more controlling the guest DOS application or OS by injecting keystrokes into it's mouse and keyboard I/O, not DOSBox-X itself.

If you're looking at injecting input into DOSBox-X interfaces, perhaps there should be specialized messages for that use case? Ones that specifically trigger shortcuts by name? And then push specific buttons by name?

The reason I suggest this is that if you hardcode around locations in the window to interact with DOSBox-X UI elements your remote control code will break if anything moves around on screen.

Raarbiarsan1899 commented 1 year ago

Yeah, I have commented out SDL_MOUSEMOTION, SDL_MOUSEBUTTONDOWN, SDL_MOUSEBUTTONUP in GFX_Events() so that hovering over window will not interrupt API. Ideally it would be good to switch remote control on and off by API. My current plan is to encode signals (maybe 64bits) for x and y position, mouse buttons, button pressed or released, API take over/release control, etc. I've tested mouse buttons and they worked very well. The reason I'm injecting through interface is I have very limited knowledge about operating system while I am hungering for a quick walkaround solution.

Thanks for plans of more sophisticate ways of implementing these APIs because based on my limit understanding of OS, I/O would be more stable and more "nature" pathway. I'm looking forward for your progresses on that.

Here is my test code:

c++:

...... void GFX_Events() {

......

HANDLE hPipe;
char buffer[4];

hPipe = CreateNamedPipe(
    "\\\\.\\pipe\\MyNamedPipeNew", // Pipe name
    PIPE_ACCESS_INBOUND,         // Pipe open mode
    PIPE_TYPE_BYTE | PIPE_READMODE_BYTE | PIPE_WAIT, // Pipe mode and blocking mode
    1, // Maximum instances
    sizeof(buffer), // Output buffer size
    sizeof(buffer), // Input buffer size
    NMPWAIT_USE_DEFAULT_WAIT, // Default timeout (0 means default)
    NULL // Security attributes
);

if(hPipe == INVALID_HANDLE_VALUE) {
    //LOG_MSG("Error creating named pipe.");
}
else {
    //LOG_MSG("Pipe created successfully.");
}

if(ConnectNamedPipe(hPipe, NULL) != 0) {
    DWORD bytesRead;
    ReadFile(hPipe, buffer, sizeof(buffer), &bytesRead, NULL);
    memcpy(&test_input, buffer, sizeof(test_input));

    // Pass the string to LOG_MSG
    char log_msg[100];

    if(test_input== 3) {
        LOG_MSG("is 3");

        SDL_memset(&event, 0, sizeof(event));
        event.type = SDL_MOUSEMOTION;
        event.motion.type = SDL_MOUSEMOTION;
        event.motion.state = 0;
        event.motion.which = 0;
        event.motion.x = 345l; // to be implemented
        event.motion.y = 240l; // to be implemented
        event.motion.xrel = 0l;
        event.motion.yrel = 0l;
        HandleMouseMotion(&event.motion);

    }

    else if(test_input== 4) {
        LOG_MSG("is 4");

        SDL_memset(&event, 0, sizeof(event));
        event.type = SDL_MOUSEBUTTONDOWN;
        event.motion.type = SDL_MOUSEBUTTONDOWN;
        event.motion.state = 0;
        event.motion.which = 0;
        event.motion.x = 345l; // to be implemented
        event.motion.y = 240l; // to be implemented
        event.motion.xrel = 0l;
        event.motion.yrel = 0l;
        event.button.button = SDL_BUTTON_LEFT;
        event.button.state = SDL_PRESSED;
        HandleMouseButton(&event.button, &event.motion);

    }

    else if(test_input == 5) {
        LOG_MSG("is 5");

        SDL_memset(&event, 0, sizeof(event));
        event.type = SDL_MOUSEBUTTONUP;
        event.motion.type = SDL_MOUSEBUTTONUP;
        event.motion.state = 0;
        event.motion.which = 0;
        event.motion.x = 345l;
        event.motion.y = 240l;
        event.motion.xrel = 0l;
        event.motion.yrel = 0l;
        event.button.button = SDL_BUTTON_LEFT;
        event.button.state = SDL_RELEASED;
        //push_event_status = SDL_PushEvent(&event);

        HandleMouseButton(&event.button, &event.motion);

    }

    DisconnectNamedPipe(hPipe);

}

CloseHandle(hPipe);

while (SDL_PollEvent(&event)) {
......
}

.......

python:

import ctypes import os import struct import time

transmitted_data = 3 # 4 or 5

with open(r'\.\pipe\MyNamedPipeNew', 'wb') as pipe: pipe.write(struct.pack('<i', transmitted_data))