X11 matatás Xlib nélkül

Fórumok

Érdekes szösszenet, bár hasznát venni sosem fogom, de mégis lekötött, szerintem roppant tanulságos.

https://hereket.com/posts/from-scratch-x11-windowing/

Hogyan nyissunk X11 ablakot és írjuk rá, hogy "Helló világ", majd fogadjunk event-eket, mindössze 200 sornyi C kódból, bármiféle lib vagy header használata nélkül, csakis közvetlen socket írás / olvasással (POSIX headerök és libc azért kell neki, de semmi más).

#include <sys/socket.h>
#include <sys/un.h>
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <poll.h>


int32_t GlobalId = 0;
int32_t GlobalIdBase = 0;
int32_t GlobalIdMask = 0;
int32_t GlobalRootWindow = 0;
int32_t GlobalRootVisualId = 0;

int32_t GlobalTextOffsetX = 10;
int32_t GlobalTextOffsetY = 20;

#define READ_BUFFER_SIZE 16*1024

#define RESPONSE_STATE_FAILED 0
#define RESPONSE_STATE_SUCCESS 1
#define RESPONSE_STATE_AUTHENTICATE 2

#define X11_REQUEST_CREATE_WINDOW 1
#define X11_REQUEST_MAP_WINDOW 8
#define X11_REQUEST_IMAGE_TEXT_8 76
#define X11_REQUEST_OPEN_FONT 45
#define X11_REQUEST_CREATE_GC 55


#define X11_EVENT_FLAG_KEY_PRESS 0x00000001
#define X11_EVENT_FLAG_KEY_RELEASE 0x00000002
#define X11_EVENT_FLAG_EXPOSURE 0x8000


#define WINDOWCLASS_COPYFROMPARENT 0
#define WINDOWCLASS_INPUTOUTPUT 1
#define WINDOWCLASS_INPUTONLY 2

#define X11_FLAG_BACKGROUND_PIXEL 0x00000002 
#define X11_FLAG_WIN_EVENT 0x00000800 

#define X11_FLAG_FG 0x00000004
#define X11_FLAG_BG 0x00000008
#define X11_FLAG_FONT 0x00004000
#define X11_FLAG_GC_EXPOSURE 0x00010000

#define PAD(N) ((4 - (N % 4)) % 4)

void VerifyOrDie(int IsSuccess, const char *Message) {
    if(!IsSuccess)  {
        fprintf(stderr, "%s", Message);
        exit(13);
    }
}

void VerifyOrDieWidthErrno(int IsSuccess, const char *Message) {
    if(!IsSuccess)  {
        perror(Message);
        exit(13);
    }
}

void DumpResponseError(int Socket, char* ReadBuffer) {
        uint8_t ReasonLength = ReadBuffer[1];
        uint16_t MajorVersion = *((uint16_t*)&ReadBuffer[2]);
        uint16_t MinorVersion = *((uint16_t*)&ReadBuffer[4]);
        uint16_t AdditionalDataLength = *((uint16_t*)&ReadBuffer[6]); // Length in 4-byte units of "additional data"
        uint8_t *Message = (uint8_t*)&ReadBuffer[8];

        int BytesRead = read(Socket, ReadBuffer + 8, READ_BUFFER_SIZE-8);

        printf("State: %d\n", ReadBuffer[0]);
        printf("MajorVersion: %d\n", MajorVersion);
        printf("MinorVersion: %d\n", MinorVersion);
        printf("AdditionalDataLength: %d\n", AdditionalDataLength);
        printf("Reason: %s\n", Message);
}

void AuthenticateX11() {
    fprintf(stderr, "Current version of the app does not support authentication.\n");
    fprintf(stderr, "Please run 'xhost +local:' in your terminal to disable cookie based authentication\n");
    fprintf(stderr, "and allow local apps to communication with Xorg without it.");
}

int32_t GetNextId() {
    int32_t Result = (GlobalIdMask & GlobalId) | GlobalIdBase;
    GlobalId += 1;
    return Result;
}

void PrintResponseError(char *Data, int32_t Size) {
    char ErrorCode = Data[1];
    const char *ErrorNames[] = {
        "Unknown Error",
        "Request",
        "Value",
        "Window",
        "Pixmap",
        "Atom",
        "Cursor",
        "Font",
        "Match",
        "Drawable",
        "Access",
        "Alloc",
        "Colormap",
        "GContext",
        "IDChoice",
        "Name",
        "Length",
        "Implementation",
    };

    const char* ErrorName = "Unknown error";
    if(ErrorCode < sizeof(ErrorNames) / sizeof(ErrorNames[0])) {
        ErrorName = ErrorNames[ErrorCode];
    }

    
    uint16_t Minor = *((uint16_t*)&Data[8]);
    uint8_t Major = *((uint8_t*)&Data[10]);

    printf("\033[0;31m");
    printf("Response Error: [%d] %s", ErrorCode, ErrorName);
    printf("	Minor: %d, Major: %d", Minor, Major);
    printf("\033[0m\n");


}

void PrintAndProcessEvent(char *Data, int32_t Size) {
    char EventCode = Data[0];
    const char* EventNames[] = {
        "-- Wrong Event Code --",
        "-- Wrong Event Code --",
        "KeyPress",
        "KeyRelease",
        "ButtonPress",
        "ButtonRelease",
        "MotionNotify",
        "EnterNotify",
        "LeaveNotify",
        "FocusIn",
        "FocusOut",
        "KeymapNotify",
        "Expose",
        "GraphicsExposure",
        "NoExposure",
        "VisibilityNotify",
        "CreateNotify",
        "DestroyNotify",
        "UnmapNotify",
        "MapNotify",
        "MapRequest",
        "ReparentNotify",
        "ConfigureNotify",
        "ConfigureRequest",
        "GravityNotify",
        "ResizeRequest",
        "CirculateNotify",
        "CirculateRequest",
        "PropertyNotify",
        "SelectionClear",
        "SelectionRequest",
        "SelectionNotify",
        "ColormapNotify",
        "ClientMessage",
        "MappingNotify",
    };

#define REPLY_EVENT_CODE_KEY_PRESS 2
#define REPLY_EVENT_CODE_EXPOSE 12

const char* TERMINAL_TEXT_COLOR_RED = "\033[0;32m";
const char* TERMINAL_TEXT_COLOR_CLEAR = "\033[0m";

    if(EventCode == REPLY_EVENT_CODE_EXPOSE) {
        // NOTE: Exposure event
        const char *EventName = "Expose";
        uint16_t SequenceNumber = *((uint16_t*)&Data[2]);
        uint32_t Window = *((uint32_t*)&Data[4]);
        uint16_t X = *((uint16_t*)&Data[8]);
        uint16_t Y = *((uint16_t*)&Data[10]);
        uint16_t Width = *((uint16_t*)&Data[12]);
        uint16_t Height = *((uint16_t*)&Data[14]);
        uint16_t Count = *((uint16_t*)&Data[16]);

        printf(TERMINAL_TEXT_COLOR_RED);
            printf("%s: ", EventName);
        printf(TERMINAL_TEXT_COLOR_CLEAR);

        printf("Seq %d, ", SequenceNumber);
        printf("Win %d: ", Window);
        printf("X %d: ", X);
        printf("Y %d: ", Y);
        printf("Width %d: ", Width);
        printf("Height %d: ", Height);
        printf("Count %d: ", Count);
        printf("\n");
        /* printf("%s: Seq %d\n", EventName, SequenceNumber); */
    } else if(EventCode == REPLY_EVENT_CODE_KEY_PRESS) {
        const char *EventName = "KeyPress";
        char KeyCode = Data[1];
        uint16_t SequenceNumber = *((uint16_t*)&Data[2]);
        uint32_t TimeStamp = *((uint32_t*)&Data[4]);
        uint32_t RootWindow = *((uint32_t*)&Data[8]);
        uint32_t EventWindow = *((uint32_t*)&Data[12]);
        uint32_t ChildWindow = *((uint32_t*)&Data[16]); // NOTE: Always 0
        int16_t RootX = *((int16_t*)&Data[20]);
        int16_t RootY = *((int16_t*)&Data[22]);
        int16_t EventX = *((int16_t*)&Data[24]);
        int16_t EventY = *((int16_t*)&Data[26]);
        int16_t SetOfKeyButMask = *((int16_t*)&Data[28]);
        int8_t IsSameScreen = *((int8_t*)&Data[30]);

        printf(TERMINAL_TEXT_COLOR_RED);
            printf("%s: ", EventName);
        printf(TERMINAL_TEXT_COLOR_CLEAR);

        // NOTE: Temporary hack that will not work everywhere
        int StepSize = 10;
        if(KeyCode == 25) { GlobalTextOffsetY += StepSize; }
        if(KeyCode == 39) { GlobalTextOffsetY -= StepSize; }
        if(KeyCode == 38) { GlobalTextOffsetX -= StepSize; }
        if(KeyCode == 40) { GlobalTextOffsetX += StepSize; }

        printf("Code %u, ", (uint8_t)KeyCode);
        printf("Seq %d, ", SequenceNumber);
        printf("Time %d, ", TimeStamp);
        printf("Root %d, ", RootWindow);
        printf("EventW %d, ", EventWindow);
        printf("Child %d, ", ChildWindow);
        printf("RX %d, ", RootX);
        printf("RY %d, ", RootY);
        printf("EX %d, ", EventX);
        printf("EY %d, ", EventY);
        printf("\n");
    } else {
        const char* EventName = " - Unknown Event Code -";
        if(EventCode < sizeof(EventNames) / sizeof(EventNames[0])) {
            EventName = EventNames[EventCode];
        }
        // printf("-------------Event: %s\n", EventName);
        // for(int i = 0; i < Size; i++) {
            // printf("%c", Data[i]);
        // }
        // printf("\n");
    }

}

void GetAndProcessReply(int Socket) {
    char Buffer[1024] = {};
    int32_t BytesRead = read(Socket, Buffer, 1024);

    uint8_t Code = Buffer[0];

    if(Code == 0) {
        PrintResponseError(Buffer, BytesRead);
    } else if (Code == 1) {
        printf("---------------- Unexpected reply\n");
    } else {
        // NOTE: Event?
        PrintAndProcessEvent(Buffer, BytesRead);
    }
}

int X_InitiateConnection(int Socket) { 
    // TODO: Remove global variables and put them into 'connection' struct.
    int SetupStatus = 1;
    char SendBuffer[16*1024] = {};
    char ReadBuffer[16*1024] = {};

    uint8_t InitializationRequest[12] = {};
    InitializationRequest[0] = 'l';
    InitializationRequest[1] = 0;
    InitializationRequest[2] = 11;

    int BytesWritten = write(Socket, (char*)&InitializationRequest, sizeof(InitializationRequest));
    VerifyOrDie(BytesWritten == sizeof(InitializationRequest), "Wrong amount of bytes written during initialization");

    int BytesRead = read(Socket, ReadBuffer, 8);

    if(ReadBuffer[0] == RESPONSE_STATE_FAILED) {
        DumpResponseError(Socket, ReadBuffer);
    }
    else if(ReadBuffer[0] == RESPONSE_STATE_AUTHENTICATE) {
        AuthenticateX11();
    }
    else if(ReadBuffer[0] == RESPONSE_STATE_SUCCESS) {
        printf("INIT Response SUCCESS. BytesRead: %d\n", BytesRead);

        BytesRead = read(Socket, ReadBuffer + 8, READ_BUFFER_SIZE-8);
        printf("---------------------------%d\n", BytesRead);

        /* -------------------------------------------------------------------------------- */
        uint8_t _Unused = ReadBuffer[1];
        uint16_t MajorVersion = *((uint16_t*)&ReadBuffer[2]);
        uint16_t MinorVersion = *((uint16_t*)&ReadBuffer[4]);
        uint16_t AdditionalDataLength = *((uint16_t*)&ReadBuffer[6]); // Length in 4-byte units of "additional data"

        uint32_t ResourceIdBase = *((uint32_t*)&ReadBuffer[12]);
        uint32_t ResourceIdMask = *((uint32_t*)&ReadBuffer[16]);
        uint16_t LengthOfVendor = *((uint16_t*)&ReadBuffer[24]);
        uint8_t NumberOfFormants = *((uint16_t*)&ReadBuffer[29]);
        uint8_t *Vendor = (uint8_t *)&ReadBuffer[40];

        int32_t VendorPad = PAD(LengthOfVendor);
        int32_t FormatByteLength = 8 * NumberOfFormants;
        int32_t ScreensStartOffset = 40 + LengthOfVendor + VendorPad + FormatByteLength;

        uint32_t RootWindow = *((uint32_t*)&ReadBuffer[ScreensStartOffset]);
        uint32_t RootVisualId = *((uint32_t*)&ReadBuffer[ScreensStartOffset + 32]);

        GlobalIdBase = ResourceIdBase;
        GlobalIdMask = ResourceIdMask;
        GlobalRootWindow = RootWindow;
        GlobalRootVisualId = RootVisualId;

        SetupStatus = 0;
    }

    return SetupStatus;
}

int X_CreatWindow(int Socket, int X, int Y, int Width, int Height) {
    // TODO: Put this into 'connection' struct
    char SendBuffer[16*1024] = {};
    char ReadBuffer[16*1024] = {};

    int32_t WindowId = GetNextId();
    int32_t Depth = 0;
    uint32_t BorderWidth = 1;
    int32_t CreateWindowFlagCount = 2;
    int RequestLength = 8+CreateWindowFlagCount;

    SendBuffer[0] = X11_REQUEST_CREATE_WINDOW;
    SendBuffer[1] = Depth;
    *((int16_t *)&SendBuffer[2]) = RequestLength;
    *((int32_t *)&SendBuffer[4]) = WindowId;
    *((int32_t *)&SendBuffer[8]) = GlobalRootWindow;
    *((int16_t *)&SendBuffer[12]) = X;
    *((int16_t *)&SendBuffer[14]) = Y;
    *((int16_t *)&SendBuffer[16]) = Width;
    *((int16_t *)&SendBuffer[18]) = Height;
    *((int16_t *)&SendBuffer[20]) = BorderWidth;
    *((int16_t *)&SendBuffer[22]) = WINDOWCLASS_INPUTOUTPUT;
    *((int32_t *)&SendBuffer[24]) = GlobalRootVisualId;
    *((int32_t *)&SendBuffer[28]) = X11_FLAG_WIN_EVENT | X11_FLAG_BACKGROUND_PIXEL;
    *((int32_t *)&SendBuffer[32]) = 0xff000000;
    *((int32_t *)&SendBuffer[36]) = X11_EVENT_FLAG_EXPOSURE | X11_EVENT_FLAG_KEY_PRESS;

    int BytesWritten = write(Socket, (char *)&SendBuffer, RequestLength*4);

    return WindowId;
}

int X_MapWindow(int Socket, int WindowId) {
    // TODO: Put this into 'connection' struct
    char SendBuffer[16*1024] = {};
    char ReadBuffer[16*1024] = {};

    SendBuffer[0] = X11_REQUEST_MAP_WINDOW;
    SendBuffer[1] = 0;
    *((int16_t *)&SendBuffer[2]) = 2;
    *((int32_t *)&SendBuffer[4]) = WindowId;

    int BytesWritten = write(Socket, (char *)&SendBuffer, 2*4);
    return 0;
}

void X_OpenFont(int32_t Socket, char *FontName, int32_t FontId) {
    char SendBuffer[16*1024] = {};
    char ReadBuffer[16*1024] = {};
    int BytesWritten = 0;
    int BytesRead = 0;

    int32_t FontNameLength = strlen((char *)FontName);
    int32_t Pad = PAD(FontNameLength);
    int RequestLength = (3 + (FontNameLength + Pad)/4);

    SendBuffer[0] = X11_REQUEST_OPEN_FONT;
    SendBuffer[1] = 0;
    *((uint16_t *)&SendBuffer[2]) = RequestLength;
    *((uint32_t *)&SendBuffer[4]) = FontId;
    *((uint16_t *)&SendBuffer[8]) = FontNameLength;
    strncpy(SendBuffer + 12, (char *)FontName, FontNameLength);

    int32_t WriteSize = 12 + FontNameLength + Pad;
    BytesWritten = write(Socket, (char *)&SendBuffer, WriteSize);
}

void X_CreateGC(int32_t Socket, int32_t GcId, int32_t FontId) {
    char SendBuffer[16*1024] = {};

    int32_t CreateGcFlagCount = 3;
    int RequestLength = 4 + CreateGcFlagCount;

    SendBuffer[0] = X11_REQUEST_CREATE_GC;
    SendBuffer[1] = 0;
    *((int16_t *)&SendBuffer[2]) = RequestLength;
    *((int32_t *)&SendBuffer[4]) = GcId;
    *((int32_t *)&SendBuffer[8]) = GlobalRootWindow;
    *((int32_t *)&SendBuffer[12]) = X11_FLAG_FG | X11_FLAG_BG | X11_FLAG_FONT;
    *((int32_t *)&SendBuffer[16]) = 0xFF00FF00; // Foreground
    *((int32_t *)&SendBuffer[20]) = 0xFF000000; // Background
    *((int32_t *)&SendBuffer[24]) = FontId; // Font

    write(Socket, (char *)&SendBuffer, RequestLength*4);
}

void WriteText(int Socket, int WindowId, int GCid, int16_t X, int16_t Y, const char *Text, int32_t TextLength) {
    char Buffer[16*1024] = {};

    uint32_t ContentLength = 4 + (TextLength + PAD(TextLength))/4;

    Buffer[0] = (uint8_t)X11_REQUEST_IMAGE_TEXT_8;
    Buffer[1] = TextLength;
    *((int16_t *)&Buffer[2]) = ContentLength; 
    *((int32_t *)&Buffer[4]) = WindowId;
    *((int32_t *)&Buffer[8]) = GCid;
    *((int16_t *)&Buffer[12]) = X; 
    *((int16_t *)&Buffer[14]) = Y; 

    strncpy(&Buffer[16], (char *)Text, TextLength);
    int BytesWritten = write(Socket, (char *)&Buffer, ContentLength*4);
}

int main(){
    int Socket = socket(AF_UNIX, SOCK_STREAM, 0);
    VerifyOrDie(Socket > 0, "Couldn't open a socket(...)");

    struct sockaddr_un Address;
    memset(&Address, 0, sizeof(struct sockaddr_un));
    Address.sun_family = AF_UNIX;
    strncpy(Address.sun_path, "/tmp/.X11-unix/X0", sizeof(Address.sun_path)-1);

    int Status = connect(Socket, (struct sockaddr *)&Address, sizeof(Address));
    VerifyOrDieWidthErrno(Status == 0, "Couldn't connect to a unix socket with connect(...)");

    int SetupStatus = X_InitiateConnection(Socket);

    if(SetupStatus == 0) {
        int32_t X = 100;
        int32_t Y = 100;
        uint32_t Width = 600;
        uint32_t Height = 300;
        int WindowId = X_CreatWindow(Socket, X, Y, Width, Height);

        X_MapWindow(Socket, WindowId);

        int32_t FontId = GetNextId();
        X_OpenFont(Socket, (int8_t *)"fixed", FontId);

        int32_t GcId = GetNextId();
        X_CreateGC(Socket, GcId, FontId);

        struct pollfd PollDescriptors[1] = {};
        PollDescriptors[0].fd = Socket;
        PollDescriptors[0].events = POLLIN;
        int32_t DescriptorCount = 1;
        int32_t IsProgramRunning = 1;
        while(IsProgramRunning){
            int32_t EventCount = poll(PollDescriptors, DescriptorCount, -1);

            if(PollDescriptors[0].revents & POLLERR) {
                printf("------- Error\n");
            }

            if(PollDescriptors[0].revents & POLLHUP) {
                printf("---- Connection close\n");
                IsProgramRunning = 0;
            }

            char* t1 = "Hello, World!";
            char* t2 = "This is a test text directly written to X";
            char* t3 = "Whooha. Is this even legal? Let's keep a secret!";
            WriteText(Socket, WindowId, GcId, GlobalTextOffsetX, GlobalTextOffsetY, t1, strlen(t1));
            WriteText(Socket, WindowId, GcId, GlobalTextOffsetX, GlobalTextOffsetY + 15, t2, strlen(t2));
            WriteText(Socket, WindowId, GcId, GlobalTextOffsetX, GlobalTextOffsetY + 30, t3, strlen(t3));

            GetAndProcessReply(PollDescriptors[0].fd);
        }
    }

}

Fordítás:

gcc main.c -o main

Ennyi! A linkelt blogposztban részletesen el van magyarázva minden, érdemes elolvasni hozzá.

Hozzászólások

Vagány. Bár sok gyakorlati haszna nincs, ahogy írja, tanulási célzattal jó, meg egy projektnél spórolhat vele pár KB memóriát, de nagyon többet nem. Az X server úgyis megeszi a sok megáját, sőt, meg az xlib annyira minimalista (még a nálánál vastagabb libxcb is csak pár KB-okat eszik), hogy nem hinném, hogy valaki saját szakállra értelmesen minimalizálni tudná, ha minden funkcióját ki akarná váltani.

Plusz a minimlizmus könnyen a gyakorlati használhatóság rovására mehet. Emiatt dobtam a dwm-et is, ami ugyan xlib-es, de elég minimalista, és gond is volt belőle, mert bizonyos bar/panel-eknek, meg saját képernyővédős scripteknek, és más programoknak (xdo, xdotool, xtitle, stb.) kellett az EWMH protokoll, meg az XCB is sokkal több funkciót nyújt, és ezek sokat dobnak a használhatóságon. dwm alatt rendszeres probléma volt, hogy voltak alkalmazások, amik nem jelentek meg, vagy nem a megfelelő méretben, mert a WM figyelmen kívül hagyta az ablakméretezési vagy átméreteződési igényüket, nem tudtak előre konfigolt méretben megjelenni. A bspwm ezzel szemben XCB-s, EWMH-t is támogat, és alig pár KB a memóriafoglalási különbsége az dwm-hez képest, vagyis van nagyobb, mert a dwm a panelt és a billentyűzetkezelést is magában foglalta, míg bspwm-nél ez ki van szervezve (sxhkd, de lehet helyette mxhkd, xbindkey-s, stb. is használni, meg kell hozzá egy panel, polybar, lemonbar, dzen2, vagy hasonló).

Egyszerűen így csak olyan kevés KB memória spórolható, hogy elenyészik a betöltött fontokhoz, háttérképhez, meg esetleg ikonokhoz és egyéb toolkit-ekhez (Gtk3, Qt5) stb.-hez képest. Az tényleg nem fog senkin segíteni, hogy egy WM 1920 KB-ot, vagy 2048 KB-ot foglal a memóriából, főleg azután, hogy az ember indít még vagy xlib-es, vagy libxcb-s vagy netán gtk3-as, vagy más programokat, amik úgyis betöltik az extra libet, akkor már használhatná azt a WM is.

Sajnos ez az összes WM-nek is csapdája. Hiába sovány egy WM, amint elindítod rá a szokásos programokat, ugyanoda hízhat, mint egy bloatabb DE, ami eleve előretöltötte magának a sok libet, de azt nem kellett már utána betölteni a futó programoknak (böngésző, Electron appok, egyéb GUI-s dolgok). Így egy WM kezdeti idle fogyasztása megtévesztő. Mondjuk én kerülöm a GUI-t, minden szoftverem terminálos szinte, alig van pár kivétel, amit nem tudtam elkerülni eddig (böngésző, Signal, nagyon ritkán Steam, Carla, Soundgarden, Musescore, stb. zenéléshez). Néha még egy Thunderbirdöt is beröffentek, amíg nem oldom meg a HTML-s mailszerkesztést, de csak annak erejéig, mert a TB-öt kiváltottam lényegében aerc-vel.

Futtatok ugyan néha grafikus kimenetű programot, mpv, zathura, sxiv, st, feh, dmenu, polybar, de ezek nem GUI-sak, nincs menürendszerük, ablakdíszítésük, toolbarjuk, stb., hanem az ablakukat kvázi framebuffer/layer-nek használják csak, toolkitet se használnak emiatt. De ezeken kívül tényleg semmi, minden terminálos kizárólag (Vifm, neovim, aerc, cmus, htop, pulsemixer, fzf/less-alapú saját scriptek, transmission-cli, sc/sc-im, calc, scrot, stardict-cli, stb.).

A computer is like air conditioning – it becomes useless when you open Windows.” (Linus Torvalds)

Igen, én is írtam, hogy gyakorlati haszna nem sok, de mindenképp érdekes.

Spórolni nem nagyon lehet vele, esetleg azt látom előnyének, hogy olyan gépen is el fog indulni, ahol nincs fenn az Xlib (az Xserver futhat akár egy másik számítógépen is!), ezért az ldso elhasalna. De akkor már egyszerűbb statikusan linkelni az Xlib-et.

Egyszerűen így csak olyan kevés KB memória spórolható, hogy elenyészik a betöltött fontokhoz, háttérképhez, meg esetleg ikonokhoz

Pontosítsunk, ezek egyike sincs a memóriában a kliens oldalon. Ezeket mind csak a szerver tárolja, nem a kliens program, az csak hivatkozik rájuk. (Lásd pl a fenti példában az X_OpenFont, a kliens csak a font nevét küldi a szervernek, aztán meg csak egy int azonosítóval hivatkozik a továbbiakban rá, maga a font és a glifjei nincsenek nála a memóriában).

Pontosítsunk, ezek egyike sincs a memóriában a kliens oldalon. Ezeket mind csak a szerver tárolja, nem a kliens program, az csak hivatkozik rájuk. (Lásd pl a fenti példában az X_OpenFont, a kliens csak a font nevét küldi a szervernek, aztán meg csak egy int azonosítóval hivatkozik a továbbiakban rá, maga a font és a glifjei nincsenek nála a memóriában).

Csak az a baj, hogy már több, mint egy évtizede a client-side rendering a menő, azaz ezeket a szuper X szerver funkciókat alig használja valami.

Most hogy írod, kipróbáltam, és Arch-on nekem is ezeket írja, gcc-vel és clang-gal fordítva is. Próbáltam emelt joggal futni, az se segít rajta, szóval nem is jogosultsági probléma.

A computer is like air conditioning – it becomes useless when you open Windows.” (Linus Torvalds)

Elsőre nálam is ugyanez volt a hibaüzenet (debian stable, gcc). Benne van a leírásban: For this simplification to work we need to disable regular cookie based authentication and allow all app on local machine to connect to X server directly. To to that you need to open a terminal and type xhost +local: Later if you want to revert back you could use xhost -local: to force cookie based authentication back.

És valóban, így már működik.

Kösz szépen az infót. Fel kellett tennem hozzá az xorg-xhost csomagot, mert nem volt xhost parancs, de most már végrehajtva a leírásodban írtakat, valóban működik, ablakba kiír, háttérben a terminálba írogatja, hogy fogadta a billentyűzeteseményeket is. Így valóban tudja azt, amit a leírása állít.

A computer is like air conditioning – it becomes useless when you open Windows.” (Linus Torvalds)

Szerkesztve: 2024. 05. 11., szo – 14:26

erdekes. nyilvan ezt csinalja a libX11 is, sok mindne egyeb mellett. hasonloan oldottak meg az opensshd-ben is a libsystemd kivaltasat, de az csak kb 20 sor volt :)

en a memleakelo es nem threadsafe libmiltert valtottam ki, eloszor a pure python ppymilterrel majd vegul azt is kukaztam es irtam egy minimal asyncio implementaciot.

ezeket a 30 eves protokollokat ahol egy structban atadsz par int-et es stringet es a valasz is hasonlo, nem tul nehez c-ben vagy barmilyen nyelven implementalni, folosleges ehhez egy kulon lib... foleg ha 30 eve nem valtozott az api.

Visszavonom, amit írtam, nevezetesen, hogy nincs gyakorlati haszna. Elmélyedtem kicsit én is a leírásban, és észrevettem, hogy zseniálisabb ez, mint elsőre látszott. Arra lesz nekem jó, hogy POSIX shell scriptben implementáljam, tisztán ilyen implementáció nincs. Ahogy nézem, semmi megvalósíthatatlan nincs benne, socket-nyitás, meg strukturáltan átadott adatok, ez menni fog shellben is, nem kell hozzá se Python, se C. Kíváncsi vagyok sikerül-e.

Nekem ez arra lesz jó, hogy nem kell fent lennie libeknek ultraminimális rendszeren, meg ha scriptként fut, akkor menet közben tudom a futó WM-et változtatni, nem kell recompile-restart ciklus, hogy kalapáljak a működésén.

A computer is like air conditioning – it becomes useless when you open Windows.” (Linus Torvalds)

Rust-ot semmiképp. Majdnem biztos vagyok benne, hogy sikerül, mert pl. Pythonra van hozzá lib, ha azon meg tudták valósítani, akkor shell scriptben is lehet.

Ráadásul én a példakód tudásának a töredékét igénylem, pl. nem akarok ablakot létrehozni, meg akármit is írogatni bele. Nekem elég, ha már létező, X alatt futó más alkalmazások, díszítés nélküli ablakát csesztetem, melyik legyen felül, milyen méretben legyen, esetleg virtuális asztalkezelés.

A computer is like air conditioning – it becomes useless when you open Windows.” (Linus Torvalds)

Mindazonáltal ha írok tetszőleges alkalmazást, használok egy-két libet, majd a a használt libek forrásából szimplán kivágom a használt függvények forrását és azokat bemásolom a kódomba, akkor pont ugyanitt tartok. Esetleg a nem használt extra ficsorokat (amik a lib absztrakciójához, vagy általam nem igényelt szolgáltatásaihoz kapcsolódó kódok) még ki is törlöm a forrásból, akkor eljutok fenti helyzethez.

Közben próbálkozok a shell scriptes implementációval, de már a legelső lépésben akadályba ütköztem. Nem találom, hogy hogyan lehet shellben socket-re írni. A neten néhol netcat-openbsd-t javasolnak (mivel a tradicionális netcat nem támogat unixos socketeket, csak hálózati TCP/UDP socketeket), más, főleg linuxos források a socat parancsot javasolják. Még ebben sem világos, hogy hogyan kell socket-et nyitni, vagy kell-e egyáltalán, mert ahogy olvasom, mikor fut az X szerver, akkor egy átlag linuxos rendszeren legalábbis, máris nyitva van a socket, amit a /tmp/.X11-unix/X0 (vagy 0 helyett más szám, ha a $DISPLAY értéke más) címen lehet elérni, de azt nem értem, hogy ide hogyan tudnék irkálni, vagy olvasni róla. Csinált már valaki hasonlót?

A computer is like air conditioning – it becomes useless when you open Windows.” (Linus Torvalds)

Ha bash elég, akkor /dev/tcp és /dev/udp is lehet :-)

Amúgy stdin-ről a tmp-beli UNIX-domain socketre így írsz socat segítségével

socat - unix-connect:/tmp/.X11-unix/X0

(ha olvasni akarsz, akkor egyszerűen megcseréled a két paramétert.)

és ugyanez fent említett netcat-tel:

nc -U /tmp/.X11-imox/X0

(Ha olvasni akarsz, akkor adsz neki egy extra -l azaz "listen" opciót.)

Nem baj, kösz a helyesbítést. Szükség se volt rá, mert működik listen meghívása nélkül is, elküldtem socat-tel az előre preparált bájtokból álló stringet a unix-connect:/tmp/.X11-unix/X0 socket-re, és nem csak hogy elfogadta, stdout-ra már nyomja is magától a választ, amit elemeztem | xxd | less segítségével, és a X.org szervernek 1-es a visszatérő értéke mindjárt a 0. bájban, elfogadta a csatlakozást, közöl is magáról kb. 8 KiB-nyi infót. Ez egy biztató kezdet.

Most már csak értelmes parancsokat kell preparálnom újabb stringként, amit elküldök, hogy ablakot nyissak. Menni fog ez. Nyilván most még sügér, meg fixre drótozott értékekkel dolgozok, de majd absztrahálok rajta, ha minden működik, hogy változókkal hívom a funkciókat, meg a $DISPLAY értékét is figyelembe veszem, úgy már nem lesz semmi fixre drótozva.

Szerk.: már az ablak bezárása megy. A nyitása még nem, ott elrontok valamit. A másik trükk, hogy minden parancs elég kell külön inicializálás, nem lehet úgy, hogy előbb elküldöm a socat-tel az inicializálást, aztán külön socat a többi utasításnak. Minden socat-sorban minden adatot el kell küldeni, inicializálástól elkezdve az összes végrehajtott utasításon át.

A computer is like air conditioning – it becomes useless when you open Windows.” (Linus Torvalds)

nem lehet úgy, hogy előbb elküldöm a socat-tel az inicializálást, aztán külön socat a többi utasításnak

Persze hogy nem. Amikor a socat-ot tartalmazó pipeline lefut, az általa létrehozott file descriptor-ok megszűnnek, azaz a socket lezáródik. A szerver számára minden új kapcsolódás egyben új klienst jelent. Amíg olyan egyszerű dolgot akarsz, mint valamilyen input alapján generált parancs elküldése, addig teljesen jól működik az, ahogy írtad, tehát hogy mindhez külön inicializálás, stb. De bármi bonyolultabb (ablak kezelése, események feldolgozása) esetén már nagyon nem optimális, inkább antipattern. Működhet pl. a socat UNIX-CONNECT:/tmp/.X11-unix/X0 EXEC:/path/to/script formátum. Így a script stdout-ját a socat továbbítja a már kapcsolódott socket-be, ill. a socket-ből a script stdin-jére. Gyakorlati haszna még így is kétséges, mert minden parancsot string-ként előállítani, átalakítani (pl. xxd -r) binárissá; minden beérkező bináris adatot stringre (pl. xxd), és így elemezni lényegesen nagyobb cpu használattal (a c-ben írthoz képest), valamint rengeteg fork() rendszerhívással jár.

Megcsinálom ezt az átirányítást, hogy az egész script bemenete, kimenete átirányítódjon, ne záródjon le a socket.

Az xxd-t nem arra használom, hogy binárissá alakítsak, arra printf-et használok. Az xxd csak debugolási okból van ott, hogy lássam a választ, legyen esélyem érteni, különben csak krix-krax látszik a kimenetben.

Azzal tisztában vagyok, hogy sok fork lesz, a scriptes implementáció nem a leghatékonyabb erőforrások terén, de nem fog sokat irkálni a socketre, nem bonyolítom agyon túl sok funkcióval.

A computer is like air conditioning – it becomes useless when you open Windows.” (Linus Torvalds)

Kérdeznék még. Ezt a socat unix-connect://blabla exec:/path/to/script parancsot a vonatkozó scriptből adom ki, vagy még a script futása előtt?

Úgy nem jó, hogy a script elején benyomom neki, hogy socat - unix-connect:=/tmp/.X11-unix/X0 & amivel a háttérben ott fut, nem szakad meg, és míg fut a script, addig életben tartja, és ami a kimenetbe megy, azt már irányítja is át a unix socketbe?

A computer is like air conditioning – it becomes useless when you open Windows.” (Linus Torvalds)

Szerkesztve: 2024. 05. 16., cs – 23:27

Közben a kód írója megírta ugyanazt Bash-ben is, jelentősen beelőzött vele. Bár azért az ő projektje kicsit más, ő Bash-t használt, meg hexadecimális karaktereket a parancsstring-ekben, meg netcat-et, míg én szigorúbban POSIX kompatibilis megoldást írok, ami azzal jár, hogy pl. csak oktális karaktereket használok stringekben, netcat-et csak BSD-n, Linuxon a GNU netcat nem kezeli a -U kapcsolót, ergo nem támogat Unix socketeket.

A célja is más a projektnek, ez a fószer saját alkalmazásnak készít ablakot benne, én ilyet nem akarok, nálam a gyorsbillentyűre induló, mások által írt alkalmazások nyitják a saját ablakukat, én csak bezárom, átméretezem őket, váltok az ablakaik között.

Egyik megoldása érdekes, fifo-ba ír, és azt küldi a socketbe, így nem szakad meg az összeköttetés a socket felé. Ezt lehet átveszem, mert jobb, mint az én megoldásom.

A computer is like air conditioning – it becomes useless when you open Windows.” (Linus Torvalds)

Szerkesztve: 2024. 05. 17., p – 17:31

Közben megint elakadtam, nem működik a kód. Ablakot szeretnék a többi fölé hozni, hogy ne takarják a felettük lévők. Ez Xlib-ben XRaiseWindow(display, window_id) függvény, de low level szinten nem működik az X11 protokoll leírása alapján. Próbáltam több funkciót is hívni, ChangeWindowAttributes (2-es műveleti kód), ConfigureWindow (12-es műveleti kód), ChangeWindowAttributes (13-as kód), de sehogy sem működik, nincs hatása. Pedig biztosan a jó maszkolással, a jó ablakazonosítóra hívom meg, és nem működik sehogy.

Szerk.: közben kiderült, hogy a bspwm miatt nincs hatása, az tartja saját kézben az ablakokat, nem engedi őket változtatni. A saját nowm implementációm alatt működik, a ConfigureWindow meghívásával.

A computer is like air conditioning – it becomes useless when you open Windows.” (Linus Torvalds)