|
| 1 | +--- |
| 2 | +title: MiniLibX Python Manual |
| 3 | +abstract: > |
| 4 | + This documentation is a PORT of the ORIGINAL MiniLibX docs. |
| 5 | +
|
| 6 | + It describes the Python package that provides access to the |
| 7 | + MiniLibX graphics library. It allows creating windows, drawing pixels, |
| 8 | + handling images, and capturing keyboard and mouse input through a thin |
| 9 | + wrapper over the original C API, keeping function names and behavior as |
| 10 | + close as possible to the native MiniLibX library. |
| 11 | +author: "Nora de Fitero Teijeira (dde-fite)" |
| 12 | +titlepage-logo: "media/42_MiniLibX_Python_Manual.jpg" |
| 13 | +logo-width: 300px |
| 14 | +acknowledgements: > |
| 15 | + The original MiniLibX documentation was created by Olivier Crouzet under the MIT license. |
| 16 | + This is a derivative work based on his work, created on a non-profit basis with the aim of sharing knowledge among the 42 student community. |
| 17 | +
|
| 18 | + This derivative work is published under the MIT license by Nora de Fitero Teijeira. |
| 19 | +--- |
| 20 | + |
| 21 | +# Introduction |
| 22 | + |
| 23 | +The MiniLibX Python Wrapper allows you to create graphical software easily without any knowledge of X-Window/Wayland/Vulkan on Unix/Linux, or AppKit on macOS. It provides: |
| 24 | + |
| 25 | +- Window creation and management |
| 26 | +- Pixel-level drawing |
| 27 | +- Image manipulation for faster rendering |
| 28 | +- Keyboard and mouse input handling |
| 29 | +- PNG and XPM image loading |
| 30 | + |
| 31 | +## How a graphics server works |
| 32 | + |
| 33 | +This library interacts with the underlying graphics system of your operating system. Before diving into usage, it's helpful to understand how graphics servers manage windows and handle user input. |
| 34 | + |
| 35 | + |
| 36 | +### Historical X-Window concept |
| 37 | + |
| 38 | +X-Window is a network-oriented graphical system for Unix. It is based on two main parts: |
| 39 | + |
| 40 | +- On one side, your software wants to draw something on the screen and or get keyboard & mouse entries. |
| 41 | + |
| 42 | +- On the other side, the X-Server manages the screen, keyboard and mouse |
| 43 | +(It is often referred to as a \"display\"). |
| 44 | + |
| 45 | +A network connection must be established between these two entities to |
| 46 | +send drawing orders (from the software to the X-Server), and |
| 47 | +keyboard/mouse events (from the X-Server to the software). |
| 48 | + |
| 49 | +Nowadays, most of the time, both run on the same computer. |
| 50 | + |
| 51 | +### Modern graphical approach |
| 52 | + |
| 53 | +Modern computers come with a powerful GPU that is directly accessed by |
| 54 | +applications. Along GPU libraries like Vulkan or OpenGL, the Wayland |
| 55 | +protocol ensure communication with the compositor program that manages |
| 56 | +the various windows on screen and the user input events. For your own |
| 57 | +application: |
| 58 | + |
| 59 | +- The Vulkan or OpenGL library allow you to directly draw any content into |
| 60 | +your window. |
| 61 | +- The Wayland compositor handles the place of your window on screen and |
| 62 | +send you back the keyboard and mouse inputs from the user. |
| 63 | + |
| 64 | +Unfortunately, this gain of graphical power through GPU access removes |
| 65 | +the networking aspects that exist with X-Window. It is not possible for |
| 66 | +a program to access a remote GPU and show its window on a remote |
| 67 | +display. But current software architectures are more likely based on a |
| 68 | +local display application that gets data in JSON through a web API. |
| 69 | + |
| 70 | +# Getting started |
| 71 | + |
| 72 | +## Requirements |
| 73 | +### Arch Linux |
| 74 | +```bash |
| 75 | +sudo pacman -S libxcb xcb-util-keysyms zlib libbsd vulkan-icd-loader vulkan-tools shaderc |
| 76 | +``` |
| 77 | + |
| 78 | +### Debian/Ubuntu |
| 79 | +```bash |
| 80 | +sudo apt install libxcb libxcb-keysyms libvulkan libz libbsd glslc |
| 81 | +``` |
| 82 | + |
| 83 | +## Installation |
| 84 | +First compile MiniLibX. |
| 85 | +```bash |
| 86 | +make install |
| 87 | +``` |
| 88 | + |
| 89 | +Create a virtual environment with your preferred manager and open it: |
| 90 | + |
| 91 | +- For bash/zsh: |
| 92 | +```bash |
| 93 | +python -m venv .venv |
| 94 | +source .venv/bin/activate |
| 95 | +``` |
| 96 | + |
| 97 | +- For fish: |
| 98 | +```bash |
| 99 | +python -m venv .venv |
| 100 | +source .venv/bin/activate.fish |
| 101 | +``` |
| 102 | + |
| 103 | +And install the package: |
| 104 | +```bash |
| 105 | +pip install mlx_CLXV-2.2-py3-none-any.whl |
| 106 | +``` |
| 107 | + |
| 108 | +## Example of use |
| 109 | + |
| 110 | +This small Python script displays a small black window with text. It will also print the screen dimensions to stdout and listen for user clicks. |
| 111 | + |
| 112 | +We will explain the functions used in this example later. |
| 113 | + |
| 114 | +```python |
| 115 | +from mlx import Mlx |
| 116 | + |
| 117 | +def mymouse(button, x, y, mystuff): |
| 118 | + print(f"Got mouse event! button {button} at {x},{y}.") |
| 119 | + |
| 120 | +def mykey(keynum, mystuff): |
| 121 | + print(f"Got key {keynum}, and got my stuff back:") |
| 122 | + print(mystuff) |
| 123 | + if keynum == 32: |
| 124 | + m.mlx_mouse_hook(win_ptr, None, None) |
| 125 | + |
| 126 | +m = Mlx() |
| 127 | +mlx_ptr = m.mlx_init() |
| 128 | +win_ptr = m.mlx_new_window(mlx_ptr, 200, 200, "test") |
| 129 | +m.mlx_clear_window(mlx_ptr, win_ptr) |
| 130 | +m.mlx_string_put(mlx_ptr, win_ptr, 20, 20, 255, "Hello PyMlx!") |
| 131 | +(ret, w, h) = m.mlx_get_screen_size(mlx_ptr) |
| 132 | +print(f"Got screen size: {w} x {h} .") |
| 133 | + |
| 134 | +stuff = [1, 2] |
| 135 | +m.mlx_mouse_hook(win_ptr, mymouse, None) |
| 136 | +m.mlx_key_hook(win_ptr, mykey, stuff) |
| 137 | + |
| 138 | +m.mlx_loop(mlx_ptr) |
| 139 | +``` |
| 140 | + |
| 141 | +# Behind the Scenes |
| 142 | + |
| 143 | +When an instance of the Mlx class is created, the first thing it does is construct the path to the C library called libmlx.so. |
| 144 | + |
| 145 | +```python |
| 146 | +def __init__(self): |
| 147 | + module_dir = os.path.dirname(os.path.abspath(__file__)) |
| 148 | + self.so_file = os.path.join(module_dir, "libmlx.so") |
| 149 | + #... |
| 150 | +``` |
| 151 | + |
| 152 | +- \_\_file\_\_ is a special Python variable that contains the path to the file where this code is executed. |
| 153 | + |
| 154 | +- os.path.dirname extracts the path from __file__ and uses os.path.join to create the path to the library. |
| 155 | + |
| 156 | +It then declares the mlx_func variable, which acts as a bridge between Python and C. Using the CDLL function from Python's ctypes module, it loads the library and calls the original functions. |
| 157 | + |
| 158 | +```python |
| 159 | +def __init__(self): |
| 160 | + # ... |
| 161 | + self.mlx_func = CDLL(self.so_file) |
| 162 | + # ... |
| 163 | +``` |
| 164 | + |
| 165 | +For each C function available in the Python wrapper, there is a declaration within the Mlx class: |
| 166 | + |
| 167 | +```python |
| 168 | +def mlx_init(self): |
| 169 | + self.mlx_func.mlx_init.restype = c_void_p |
| 170 | + return self.mlx_func.mlx_init() |
| 171 | +``` |
| 172 | + |
| 173 | +You can see how it calls mlx_func.mlx_init(). This mlx_init() is already the original C function. It is necessary to specify the data type returned by the function with mlx_init.restype, which in this case is c_void_p (equivalent to void *). |
| 174 | + |
| 175 | +All of this is passed to CDLL, which, using the previously loaded library, will execute the function and return whatever the function returns. |
| 176 | + |
| 177 | + |
| 178 | +# Initialization and cleanup: mlx_init(), mlx_release() |
| 179 | + |
| 180 | +## Synopsis |
| 181 | + |
| 182 | +```python |
| 183 | +from mlx import Mlx |
| 184 | + |
| 185 | +def mlx_init() -> int: # void * |
| 186 | + |
| 187 | +def mlx_release() -> int: # void * |
| 188 | +``` |
| 189 | + |
| 190 | +## Description |
| 191 | + |
| 192 | +First of all, you need to initialize the connection between your software and the graphic and user sub-systems. Once this completed, you'll be able to use other MiniLibX functions to send and receive the messages from the display, like "I want to draw a yellow pixel in this window" or "did the user hit a key?". |
| 193 | + |
| 194 | +The mlx_init function will create this connection. No parameters are needed, ant it will return a void * identifier, used for further calls to the library routines. The mlx_release function can be used at the end of the program to disconnect from the graphic system and release resources. |
| 195 | + |
| 196 | +If **mlx_init()** fails to set up the connection to the display, it will return None. |
| 197 | + |
| 198 | + |
| 199 | +## Return values |
| 200 | + |
| 201 | +If **mlx_init()** set up the connection to the display correctly, it will return an **int as a pointer**; otherwise, it returns **None**. |
| 202 | + |
| 203 | +# Managing windows: mlx_new_window(), mlx_clear_window, mlx_destroy_window |
| 204 | + |
| 205 | +## Synopsis |
| 206 | +c_void_p, c_uint, c_uint, c_char_p |
| 207 | +```python |
| 208 | + def mlx_new_window(mlx_ptr: int, width: int, height: int, title: str ) -> int: # void * |
| 209 | + |
| 210 | + def mlx_clear_window(mlx_ptr: int, win_ptr: int) -> int: |
| 211 | + |
| 212 | + def mlx_destroy_window(mlx_ptr: int, win_ptr: int ) -> int: |
| 213 | +``` |
| 214 | + |
| 215 | +## Description |
| 216 | + |
| 217 | +The **mlx_new_window** () function creates a new window on the screen, |
| 218 | +using the *width* and *height* parameters to determine its size, and |
| 219 | +*title* as the text that should be displayed in the window\'s title bar. |
| 220 | +The *mlx_ptr* parameter is the connection identifier returned by |
| 221 | +**mlx_init** () (see the **mlx** man page). **mlx_new_window** () |
| 222 | +returns a *void \** window identifier that can be used by other MiniLibX |
| 223 | +calls. Note that the MiniLibX can handle an arbitrary number of separate |
| 224 | +windows. |
| 225 | + |
| 226 | +**mlx_clear_window** () and **mlx_destroy_window** () respectively clear |
| 227 | +(in black) and destroy the given window. They both have the same |
| 228 | +parameters: *mlx_ptr* is the screen connection identifier, and *win_ptr* |
| 229 | +is a window identifier. |
| 230 | + |
| 231 | +## Return values |
| 232 | + |
| 233 | +If **mlx_new_window()** fails to create a new window (whatever the |
| 234 | +reason), it will return NULL, otherwise a non-null pointer is returned |
| 235 | +as a window identifier. **mlx_clear_window** and **mlx_destroy_window** |
| 236 | +return nothing. |
| 237 | + |
| 238 | +# Drawing inside windows: mlx_pixel_put(), mlx_string_put() |
| 239 | + |
| 240 | +## Synopsis |
| 241 | + |
| 242 | + int |
| 243 | + |
| 244 | +**mlx_pixel_put** ( *void \*mlx_ptr, void \*win_ptr, unsigned int x, |
| 245 | +unsigned int y, unsigned int color* ); |
| 246 | + |
| 247 | + int |
| 248 | + |
| 249 | +**mlx_string_put** ( *void \*mlx_ptr, void \*win_ptr, unsigned int x, |
| 250 | +unsigned int y, unsigned int color, char \*string* ); |
| 251 | + |
| 252 | +## Description |
| 253 | + |
| 254 | +The **mlx_pixel_put** () function draws a defined pixel in the window |
| 255 | +*win_ptr* using the ( *x* , *y* ) coordinates, and the specified *color* |
| 256 | +. The origin (0,0) is the upper left corner of the window, the x and y |
| 257 | +axis respectively pointing right and down. The connection identifier, |
| 258 | +*mlx_ptr* , is needed (see the **mlx** man page). |
| 259 | + |
| 260 | +Parameters for **mlx_string_put** () have the same meaning. Instead of a |
| 261 | +simple pixel, the specified *string* will be displayed at ( *x* , *y* ). |
| 262 | + |
| 263 | +Both functions will discard any display outside the window. This makes |
| 264 | +**mlx_pixel_put** slow. Consider using images instead. |
| 265 | + |
| 266 | +## Color management |
| 267 | + |
| 268 | +The *color* parameter has an unsigned integer type. The displayed colour |
| 269 | +needs to be encoded in this integer, following a defined scheme. All |
| 270 | +displayable colours can be split in 3 basic colours: red, green and |
| 271 | +blue. Three associated values, in the 0-255 range, represent how much of |
| 272 | +each colour is mixed up to create the original colour. The fourth byte |
| 273 | +represent transparency, where 0 is fully transparent and 255 opaque. |
| 274 | +Theses four values must be set inside the unsigned integer to display |
| 275 | +the right colour. The bytes of this integer are filled as shown in the |
| 276 | +picture below: |
| 277 | + |
| 278 | + | B | G | R | A | colour integer |
| 279 | + +---+---+---+---+ |
| 280 | + |
| 281 | +While filling the integer, make sure you avoid endian problems. Example: |
| 282 | +the \"blue\" byte will be the least significant byte inside the integer |
| 283 | +on a little endian machine. |
| 284 | + |
| 285 | +# Handle events: |
| 286 | + |
| 287 | +## Synopsis |
| 288 | + |
| 289 | + int |
| 290 | + |
| 291 | +**mlx_loop** ( *void \*mlx_ptr* ); |
| 292 | + |
| 293 | + int |
| 294 | + |
| 295 | +**mlx_key_hook** ( *void \*win_ptr, int (\*funct_ptr)(), void \*param* |
| 296 | +); |
| 297 | + |
| 298 | + int |
| 299 | + |
| 300 | +**mlx_mouse_hook** ( *void \*win_ptr, int (\*funct_ptr)(), void \*param* |
| 301 | +); |
| 302 | + |
| 303 | + int |
| 304 | + |
| 305 | +**mlx_expose_hook** ( *void \*win_ptr, int (\*funct_ptr)(), void |
| 306 | +\*param* ); |
| 307 | + |
| 308 | + int |
| 309 | + |
| 310 | +**mlx_loop_hook** ( *void \*mlx_ptr, int (\*funct_ptr)(), void \*param* |
| 311 | +); |
| 312 | + |
| 313 | + int |
| 314 | + |
| 315 | +**mlx_loop_exit** ( *void \*mlx_ptr* ); |
| 316 | + |
| 317 | +## Events |
| 318 | + |
| 319 | +The graphical system is bi-directional. On one hand, the program sends |
| 320 | +orders to the screen to display pixels, images, and so on. On the other |
| 321 | +hand, it can get information from the keyboard and mouse associated to |
| 322 | +the screen. To do so, the program receives \"events\" from the keyboard |
| 323 | +or the mouse. |
| 324 | + |
| 325 | +## Description |
| 326 | + |
| 327 | +To receive events, you must use **mlx_loop** (). This function never |
| 328 | +returns, unless **mlx_loop_exit** is called. It is an infinite loop that |
| 329 | +waits for an event, and then calls a user-defined function associated |
| 330 | +with this event. A single parameter is needed, the connection identifier |
| 331 | +*mlx_ptr* (see the **mlx manual).** |
| 332 | + |
| 333 | +You can assign different functions to the three following events:\ |
| 334 | +- A key is released\ |
| 335 | +- The mouse button is pressed\ |
| 336 | +- A part of the window should be re-drawn (this is called an \"expose\" |
| 337 | +event, and it is your program\'s job to handle it in the Unix/Linux X11 |
| 338 | +environment, but at the opposite it never happens on Unix/Linux |
| 339 | +Wayland-Vulkan nor on MacOS).\ |
| 340 | + |
| 341 | +Each window can define a different function for the same event. |
| 342 | + |
| 343 | +The three functions **mlx_key_hook** (), **mlx_mouse_hook** () and |
| 344 | +**mlx_expose_hook** () work exactly the same way. *funct_ptr* is a |
| 345 | +pointer to the function you want to be called when an event occurs. This |
| 346 | +assignment is specific to the window defined by the *win_ptr* |
| 347 | +identifier. The *param* address will be passed back to your function |
| 348 | +every time it is called, and should be used to store the parameters it |
| 349 | +might need. |
| 350 | + |
| 351 | +The syntax for the **mlx_loop_hook** () function is similar to the |
| 352 | +previous ones, but the given function will be called when no event |
| 353 | +occurs, and is not bound to a specific window. |
| 354 | + |
| 355 | +When it catches an event, the MiniLibX calls the corresponding function |
| 356 | +with fixed parameters: |
| 357 | + |
| 358 | + |
| 359 | + expose_hook(void *param); |
| 360 | + key_hook(unsigned int keycode, void *param); |
| 361 | + mouse_hook(unsigned int button, unsigned int x, unsigned int y, void *param); |
| 362 | + loop_hook(void *param); |
| 363 | + |
| 364 | +These function names are arbitrary. They here are used to distinguish |
| 365 | +parameters according to the event. These functions are NOT part of the |
| 366 | +MiniLibX. |
| 367 | + |
| 368 | +*param* is the address specified in the mlx\_\*\_hook calls. This |
| 369 | +address is never used nor modified by the MiniLibX. On key and mouse |
| 370 | +events, additional information is passed: *keycode* tells you which key |
| 371 | +is pressed (just try to find out :) ), ( *x* , *y* ) are the coordinates |
| 372 | +of the mouse click in the window, and *button* tells you which mouse |
| 373 | +button was pressed. |
| 374 | + |
| 375 | +## GOING FURTHER WITH EVENTS |
| 376 | + |
| 377 | +The MiniLibX provides a much generic access to other available events. |
| 378 | +The *mlx.h* include define **mlx_hook()** in the same manner |
| 379 | +mlx\_\*\_hook functions work. The event and mask values will be taken |
| 380 | +from the historical X11 include file \"X.h\". Some Wayland and MacOS |
| 381 | +events are mapped to these values when it makes sense, and the mask may |
| 382 | +not be used in some configurations. |
| 383 | + |
| 384 | +See source code of the MiniLibX to find out how it will call your own |
| 385 | +function for a specific event. |
| 386 | + |
| 387 | +# Got any suggestions? |
| 388 | +If you find any errors or have any new ideas for improving this repository, feel free to open an Issue or Pull Request, or contact me at my email address: nora@defitero.com |
0 commit comments