Σε συνέχεια του προηγούμενου άρθρου θα συνεχίσουμε με μερικά επιπλέον βασικά πράγματα για το πως λειτουργεί το αγνό καθαρό Χ11. Η έστω και διαγώνια ανάγνωση του είναι απαραίτητη.
Ένα απλό πρόγραμμα
Θα χρησιμοποιήσουμε για την εξερεύνηση το παρακατω πρόγραμμα
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main() {
Display *display;
Window root, win, button;
XEvent event;
int screen;
GC gc;
XFontStruct *font;
// 1. Open connection to the X server
display = XOpenDisplay(NULL);
if (display == NULL) {
fprintf(stderr, "Error: Cannot open display\n");
exit(1);
}
screen = DefaultScreen(display);
root = RootWindow(display, screen);
unsigned long black = BlackPixel(display, screen);
unsigned long white = WhitePixel(display, screen);
// 2. Create the main application window
win = XCreateSimpleWindow(display, root, 100, 100, 300, 200, 1, black, white);
XSetStandardProperties(display, win, "Quit Button Example", "Quit", None, NULL, 0, NULL);
// 3. Create the button window as a child of the main window
button = XCreateSimpleWindow(display, win, 100, 80, 100, 40, 2, black, white);
// 4. Select input events for the windows
XSelectInput(display, win, ExposureMask);
XSelectInput(display, button, ExposureMask | ButtonPressMask);
// 5. Map (show) the windows on the screen
XMapWindow(display, win);
XMapWindow(display, button);
// 6. Create a Graphics Context and set a font for text drawing
gc = XCreateGC(display, win, 0, NULL);
XSetForeground(display, gc, black);
font = XLoadQueryFont(display, "fixed");
if (font != NULL) {
XSetFont(display, gc, font->fid);
}
// 7. Event loop
while (1) {
XNextEvent(display, &event);
// Redraw button text on expose events
if (event.type == Expose) {
if (event.xany.window == button) {
// Draw "Quit" text centered roughly inside the 100x40 button
XDrawString(display, button, gc, 35, 25, "Quit", 4);
}
}
// Handle mouse click on the button
else if (event.type == ButtonPress) {
if (event.xbutton.window == button) {
break; // Exit the loop and close
}
}
}
// 8. Clean up resources
if (font != NULL) {
XFreeFont(display, font);
}
XFreeGC(display, gc);
XDestroyWindow(display, win);
XCloseDisplay(display);
return 0;
}
Αν το τρέξουμε θα δούμε το παρακάτω:
Ένα απλό παράθυρο με ένα κουμπί πατώντας το θα τερματίσει το πρόγραμμα. Δεν κάνουμε χρήση κάποιου προχωρημένου toolbox όπως το gtk, απλά τα βασικά που προσφέρει.
H ιεραρχία των παραθύρων
Σε ένα τερματικό θα δώσουμε την εντολή: xwininfo -tree -root και θα δούμε κάτι σαν το παρακάτω:
xwininfo: Window id: 0x37d (the root window) (has no name)
Root window id: 0x37d (the root window) (has no name)
Parent window id: 0x0 (none)
58 children:
0x5a00001 (has no name): () 1x1+0+0 +0+0
...
0x5c00001 "Quit Button Example": () 300x200+3449+1120 +3449+1120
1 child:
0x5c00002 (has no name): () 100x40+100+80 +3549+1200
...
Με την εντολή αυτή βλέπουμε την ιεραρχία των παραθύρων. Υπάρχει ένα μεγάλο παράθυρο το root που ουσιαστικά είναι ο Framebuffer, ιεραρχικά κάτω του υπάρχουν 58 “παράθυρα” ένα από αυτά είναι η εφαρμογή μας που με τη σειρά της έχει ένα “παράθυρο” που είναι το κουμπί.
Root Window (Desktop / Screen Root)
└── win (0x5c00001 "Quit Button Example")
└── button (0x5c00002 "Quit" Button Window)
Ίσως, το γραφικό περιβάλλον να προσθέσει ένα επιπλέον παράθυρο με το “πλαίσιο” για τον τίτλο και τα borders. Ας δούμε κάποια στοιχεία για το κεντρικό παραθυρο. Με την εντολή xwininfo -root
xwininfo: Window id: 0x37d (the root window) (has no name)
Absolute upper-left X: 0
Absolute upper-left Y: 0
Relative upper-left X: 0
Relative upper-left Y: 0
Width: 5880
Height: 2560
Depth: 24
Visual: 0x27
Visual Class: TrueColor
Border width: 0
Class: InputOutput
Colormap: 0x26 (installed)
Bit Gravity State: ForgetGravity
Window Gravity State: NorthWestGravity
Backing Store State: NotUseful
Save Under State: no
Map State: IsViewable
Override Redirect State: no
Corners: +0+0 -0+0 -0-0 +0-0
-geometry 5880x2560+0+0
Θα δούμε το Visual Class: TrueColor με το χρωματικό μοντέλο. Θυμάστε πως κάθε παράθυρο έχει το δικό του; Η γεωμετρία του παραθύρου είναι 5880x2560. Σε τι μονάδες; Σε pixel, τι άλλο θα μπορούσε να είναι δηλαδή; Κρατήστε το αυτό είναι σημαντικό. Θα δούμε σε άλλα άρθρα πως πίσω από αυτό το απλό πράγμα κρύβεται η δυσκολία για scalling σε πολλαπλές οθόνες. Πώς προκύπτει αυτή η τεράστια ανάλυση; Έχω πολλές οθόνες
Ουσιαστικά είναι ένας μεγάλος framebuffer που όμως περιέχει περιοχές που μπορεί να υπάρχει ένα παράθυρο, αλλά να μην μπορούμε να τις δούμε σε κάποια οθόνη. Κρατήστε το επίσης αυτό.
Ας επιστρέψουμε στον κώδικα:
display = XOpenDisplay(NULL);
screen = DefaultScreen(display);
root = RootWindow(display, screen);
win = XCreateSimpleWindow(display, root, 100, 100, 300, 200, 1, black, white);
XSetStandardProperties(display, win, "Quit Button Example", "Quit", None, NULL, 0, NULL);
Θα ανοίξουμε μια σύνδεση δικτύου, θα πάρουμε τον framebuffer και το root window. Στη συνέχεια θα φτιάξουμε το παράθυρο σαν παιδί του root window. Όλα καλά αλλά που ακριβώς είναι το δικτυακό πρωτόκολλο;
Αυτό είναι κρυμμένο πίσω απο το Xlib μια βιβλιοθήκη και δε μιλάμε με αυτό άμεσα. Και αν και το πρωτόκολλο είναι ασύγχρονο το Xlib είναι σύγχρονο. Αργότερα προστέθηκε μια ασύγχρονη βιβλιοθήκη το xcb που είναι γρηγορότερη. Γιατί δείχνω το xlib; Καλή ερώτηση αλλά απλά τεκμηρίωση για αυτήν δεν υπάρχει και δε θα βρεις παραδείγματα σε κανένα βιβλίο. Η έλλειψη καλής τεκμηρίωσης είναι ένα επαναλαμβανόμενο θέμα όταν πας να σκαλίσεις βαθύτερα. Ευτυχώς, σαν προγραμματιστής σπάνια κάνεις απευθείας χρήση αυτών των βιβλιοθηκών.
Ο τίτλος και το πλαίσιο
Πώς μπήκε ο τίτλος και πως το πλαίσιο; Υπάρχει ένα ειδικό πρόγραμμα ο Window Manager που το κάνει αυτό και κρατάει τις ιδιότητες του παράθυρου και αποφασίζει που θα τοποθετηθεί. Στην πράξη είναι ενα πρόγραμμα που βλέπει το σήμα exposure όλων των παραθύρων. Επίσης, μπορεί τους αλλάξει μέγεθος με το ποντίκι να τα κρύψει κλπ. Ο απλούστερος είναι κάπου 1000 γραμμές κώδικα και οι δεκάδες που υπάρχουν προέκυχαν αντιγράφοντας ο ένας τον κώδικα του άλλου. Δεν υπάρχει κάποια βοηθητική βιβλιοθήκη. Έχουν ονόματα όπως twm, fvwm, afterstep, mutter, kwin
Αλλά θέλει ένα τίτλο. Που αποθηκεύετε αυτός; Ας δώσουμε την εντολή: xlsatoms | grep NAME
37 WM_ICON_NAME
39 WM_NAME
63 FONT_NAME
64 FAMILY_NAME
65 FULL_NAME
94 FONTNAME_REGISTRY
99 WEIGHT_NAME
103 SETWIDTH_NAME
105 ADD_STYLE_NAME
235 _XKB_RULES_NAMES
289 _KDE_NET_WM_APPMENU_SERVICE_NAME
311 _NET_DESKTOP_NAMES
321 _NET_WM_NAME
322 _NET_WM_VISIBLE_NAME
323 _NET_WM_ICON_NAME
324 _NET_WM_VISIBLE_ICON_NAME
395 WM_LOCALE_NAME
Και έχουμε τα xatoms τα οποία είναι αριθμοί που συνδέονται με μια πληροφορία. Ας δούμε τις πληροφορίες του root με την εντολή: xprop -root
_NET_WORKAREA(CARDINAL) = 0, 0, 5880, 1866
_NET_CLIENT_LIST_STACKING(WINDOW): window id # 0x5a00006, 0x5c00001
_NET_CLIENT_LIST(WINDOW): window id # 0x5a00006, 0x5c00001
XIM_SERVERS(ATOM) = @server=ibus
GDK_VISUALS(INTEGER) = 756, 600
AT_SPI_BUS(STRING) = "unix:path=/run/user/1000/at-spi/bus_0"
RESOURCE_MANAGER(STRING) = "Xcursor.size:\t30\nXcursor.theme:\tbreeze_cursors\nXft.antialias:\t1\nXft.dpi:\t120\nXft.hinting:\t1\nXft.hintstyle:\thintslight\nXft.rgba:\trgb\nxlib_quit.background:\tblue\nxlib_quit.buttonBackground:\tgreen\nxlib_quit.buttonForeground:\tyellow\n"
_KDE_NET_WM_BLUR_BEHIND_REGION(_KDE_NET_WM_BLUR_BEHIND_REGION) = 0x0
_NET_ACTIVE_WINDOW(WINDOW): window id # 0x200000
_NET_DESKTOP_GEOMETRY(CARDINAL) = 5880, 2560
_NET_DESKTOP_NAMES(UTF8_STRING) = "Desktop 1"
_NET_CURRENT_DESKTOP(CARDINAL) = 0
_NET_DESKTOP_LAYOUT(CARDINAL) = 0, 1, 1, 0
_NET_DESKTOP_VIEWPORT(CARDINAL) = 0, 0
_NET_NUMBER_OF_DESKTOPS(CARDINAL) = 1
_NET_SUPPORTING_WM_CHECK(WINDOW): window id # 0x200002
_NET_SUPPORTED(ATOM) = _NET_SUPPORTED, _NET_SUPPORTING_WM_CHECK, _NET_CLIENT_LIST, _NET_CLIENT_LIST_STACKING, _NET_NUMBER_OF_DESKTOPS, _NET_DESKTOP_GEOMETRY, _NET_CURRENT_DESKTOP, _NET_DESKTOP_NAMES, _NET_ACTIVE_WINDOW, _NET_WORKAREA, _NET_DESKTOP_LAYOUT, _NET_CLOSE_WINDOW, _NET_RESTACK_WINDOW, _NET_SHOWING_DESKTOP, _NET_WM_MOVERESIZE, _NET_MOVERESIZE_WINDOW, _NET_WM_NAME, _NET_WM_VISIBLE_NAME, _NET_WM_DESKTOP, _NET_WM_WINDOW_TYPE, _NET_WM_WINDOW_TYPE_NORMAL, _NET_WM_WINDOW_TYPE_DESKTOP, _NET_WM_WINDOW_TYPE_DOCK, _NET_WM_WINDOW_TYPE_TOOLBAR, _NET_WM_WINDOW_TYPE_MENU, _NET_WM_WINDOW_TYPE_DIALOG, _NET_WM_WINDOW_TYPE_UTILITY, _NET_WM_WINDOW_TYPE_SPLASH, _KDE_NET_WM_WINDOW_TYPE_OVERRIDE, _NET_WM_STATE, _NET_WM_STATE_MODAL, _NET_WM_STATE_MAXIMIZED_VERT, _NET_WM_STATE_MAXIMIZED_HORZ, _NET_WM_STATE_SHADED, _NET_WM_STATE_SKIP_TASKBAR, _NET_WM_STATE_SKIP_PAGER, _KDE_NET_WM_STATE_SKIP_SWITCHER, _NET_WM_STATE_HIDDEN, _NET_WM_STATE_FULLSCREEN, _NET_WM_STATE_ABOVE, _NET_WM_STATE_STAYS_ON_TOP, _NET_WM_STATE_BELOW, _NET_WM_STATE_DEMANDS_ATTENTION, _NET_WM_STATE_FOCUSED, _NET_WM_ICON, _NET_WM_PID, _NET_WM_PING, _NET_WM_USER_TIME, _NET_STARTUP_ID, _NET_WM_WINDOW_OPACITY, _NET_WM_FULLSCREEN_MONITORS, _NET_WM_ALLOWED_ACTIONS, _NET_WM_ACTION_MOVE, _NET_WM_ACTION_RESIZE, _NET_WM_ACTION_MINIMIZE, _NET_WM_ACTION_SHADE, _NET_WM_ACTION_MAXIMIZE_VERT, _NET_WM_ACTION_MAXIMIZE_HORZ, _NET_WM_ACTION_FULLSCREEN, _NET_WM_ACTION_CHANGE_DESKTOP, _NET_WM_ACTION_CLOSE, _NET_FRAME_EXTENTS, _KDE_NET_WM_FRAME_STRUT, _NET_WM_FULL_PLACEMENT, _KDE_NET_WM_ACTIVITIES, _KDE_NET_WM_SHADOW, _NET_WM_OPAQUE_REGION, _GTK_FRAME_EXTENTS, _GTK_SHOW_WINDOW_MENU
_XKB_RULES_NAMES(STRING) = "evdev", "pc105", "us", "", ""
Οι ιδιότητες που τυχαίνει να υποστηρίζει αυτός που χρησιμοποιώ θα βρεθούν στο _NET_SUPPORTED(ATOM) και για το παράθυρο είναι (απλά με xprop και κάνοντας κλικ, αντίστοιχα με xwininfo για το παράθυρο)
_NET_WM_ALLOWED_ACTIONS(ATOM) = _NET_WM_ACTION_MOVE, _NET_WM_ACTION_RESIZE, _NET_WM_ACTION_MINIMIZE, _NET_WM_ACTION_MAXIMIZE_VERT, _NET_WM_ACTION_MAXIMIZE_HORZ, \_NET_WM_ACTION_FULLSCREEN, _NET_WM_ACTION_CHANGE_DESKTOP, _NET_WM_ACTION_CLOSE
\_NET_WM_DESKTOP(CARDINAL) = 0
WM_STATE(WM_STATE):
window state: Normal
icon window: 0x0
_NET_WM_STATE(ATOM) = _NET_WM_STATE_FOCUSED
_KDE_NET_WM_ACTIVITIES(STRING) = "e984cebe-9b02-4211-84e7-6155337afb0b"
_KDE_NET_WM_FRAME_STRUT(CARDINAL) = 3, 3, 35, 5
_NET_FRAME_EXTENTS(CARDINAL) = 3, 3, 35, 5
_KDE_NET_WM_USER_CREATION_TIME(CARDINAL) = 10458754
WM_ICON_NAME(STRING) = "Quit"
WM_NAME(STRING) = "Quit Button Example"
Το WM_NAME κρατάει αυτή την πληροφορία στα αγγλικά
. Κανονικά θα έπρεπε να θέσω και τη _NET_WM_NAME που μπορεί να είναι και στα ελληνικά. Τι σημαίνουν τα υπόλοιπα; Το πρωτόκολλο δε λέει για καμία από αυτές. Κάθε window manager κάνει τα δικά του. Αλλά υπάρχουν τα
Που προσπαθούν να βάλουν μια τάξη στο χάος. Αλλά και πάλι καθένας θα κάνει τα δικά του. Καλή τύχη αν θέλεις να βρεις τις εσωτερικές διαστάσεις και τη θέση του παράθυρου. Έχω προσπαθήσει στο παρελθόν και έχω αποτυχει φρικτά.
Θέματα και τα XResources
Η ιεραρχία των παραθύρων που το κάθε κουμπί είναι ένα παράθυρο θα βρεθεί μόνο σε ελάχιστα αρχαϊκά προγράμματα. Το εξής ένα το xterm. Είναι το μόνο που μπορείς να το παραμετροποιήσεις όπως πρέπει με τον μηχανισμό των XResourses.
Αν έχεις ένα αρχείο ~/.Xresources ή ~/.Xdefaults μπορείς να θέσεις κάποιες ιδιότητες σε κάθε “παράθυρο”. Δε θα επεκταθώ σε αυτό το σύστημα, πως μπορείς να δεις τις τιμές ή να τις αλλάξεις με γραφικό τρόπο. Έτσι και αλλιώς ποτέ δε μου δούλεψε σωστά. To αρχείο αυτό μοιάζει κάπως έτσι
XTerm.vt100.foreground: white
XTerm.vt100.background: black
XTerm.vt100.color0: rgb:28/28/28
Το οποίο δεν αρκεί να το αλλάξεις θα πρέπει να το κάνεις και “compile”. Αν κάποιος θέλει περισσότερα ας δει το Arch Wiki.
Τι χρησιμοποιεί από αυτά μια εφαρμογή σήμερα;
Μια μοντέρνα εφαρμογή γραμμένη σε gtk ή Qt δε χρησιμοποιεί την εσωτερική ιεραρχία των παραθύρων ούτε τα XResourses. Είναι τα πάντα ένα παράθυρο, χωρίς εισαγωγικά αυτή τη φορά. Και θα βρει μόνη της τι θα πρέπει να γίνει αν κάνεις κάπου κλικ. Τα ICCM, EWMH υπάρχουν ακόμα σε XWayland. Σε καθαρό Wayland δεν μπορείς να πάρεις πληροφορίες για τη θέση των παραθύρων, μόνο τα ονόματα των κυρίων παραθύρων για τον launcher. Κάτι κερδίζεις κάτι χάνεις
Χρήση ΑΙ
Χρησιμοποίησα ΑΙ για τον κώδικα αντί να κάτσω να κατεβάσω τα βιβλία και να αντιγράψω. Επίσης, για έρευνα μιας και πλέον το search είναι ουσιαστικά νεκρό στις μέρες μας. Οτι λάθη υπάρχουν, που υπάρχουν, είναι 100% δική μου ευθύνη. Σε μια εποχή που έχουμε γεμίσει με άρθρα και prompts “Γράψε μου ένα άρθρο για αυτό” και που τα πάντα μοιάζει να τα έχει γράψει ο ίδιος άνθρωπος εξακολουθώ να χρησιμοποιώ τον δύσκολο δρόμο. Σίγουρα δε βγάζεις έτσι ποσότητα, ειδικά αν κυνηγάς τα κλικ. Για την ποιότητα δεν είμαι εγώ αυτός που θα αποφασίσει. Αν κάποιος έχει απορίες μπορεί να επεκταθώ περισσότερο.

