diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..c5c8553 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,16 @@ +# EditorConfig is awesome: https://EditorConfig.org + +# top-most EditorConfig file +root = true + +[*] +indent_style = space +indent_size = 4 +end_of_line = lf +charset = utf-8 +trim_trailing_whitespace = false + +[*.{js,jsx,ts,tsx,css,scss,html,svelte,postcss,glsl,c,h}] +indent_size = 2 +indent_style = tab +trim_trailing_whitespace = true \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt index 144ad3e..a95a436 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -23,9 +23,11 @@ set_property(GLOBAL PROPERTY USE_FOLDERS ON) list(APPEND CMAKE_MODULE_PATH "${PROJECT_SOURCE_DIR}/cmake") # Options -set(DEBUG OFF CACHE BOOL "Build MLX42 in debug mode, enabling assertions") -set(GLFW_FETCH ON CACHE BOOL "Clone and install GLFW") -set(BUILD_TESTS OFF CACHE BOOL "Build the tests to verify the integrity of the lib") +# ----------------------------------------------------------------------------- +option(BUILD_SHARED_LIBS "Build mlx42 as a shared library." OFF) +set(DEBUG OFF CACHE BOOL "Build MLX42 in debug mode, enabling assertions") +set(GLFW_FETCH ON CACHE BOOL "Clone and install GLFW") +set(BUILD_TESTS OFF CACHE BOOL "Build the tests to verify the integrity of the lib") # Compile Options # ----------------------------------------------------------------------------- @@ -99,7 +101,9 @@ add_custom_command( # Sources # ----------------------------------------------------------------------------- -add_library(mlx42 STATIC +# The library type is now determined by the BUILD_SHARED_LIBS option. +# By default, it builds a STATIC library. +add_library(mlx42 # Changed from "add_library(mlx42 STATIC" # Root ${SOURCE_DIR}/mlx_cursor.c @@ -134,6 +138,16 @@ add_library(mlx42 STATIC ) target_include_directories(mlx42 PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include) +# Add this block to control symbol visibility for shared library builds +if(BUILD_SHARED_LIBS) + set_target_properties(mlx42 PROPERTIES + C_VISIBILITY_PRESET hidden + CXX_VISIBILITY_PRESET hidden + ) + target_compile_definitions(mlx42 PRIVATE MLX42_BUILD_SHARED) +endif() + + # Dependencies # ----------------------------------------------------------------------------- @@ -192,4 +206,4 @@ install(TARGETS mlx42 RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}" ARCHIVE DESTINATION "${CMAKE_INSTALL_LIBDIR}" LIBRARY DESTINATION "${CMAKE_INSTALL_LIBDIR}" -) +) \ No newline at end of file diff --git a/README.md b/README.md index e183b8d..d50c99d 100644 --- a/README.md +++ b/README.md @@ -117,6 +117,14 @@ You can pass build [options](./docs/index.md#available-options) to cmake, e.g: ` You can find an example makefile in the documentation [here](https://github.com/codam-coding-college/MLX42/blob/master/docs/Basics.md). +#### Bindings (Python, etc) +MLX42 currently has a [Python FFI binding](./ffi/python/README.md) available. +In order to get it working you need to build the library first as shown above. +The only change is that you need to enable the option `BUILD_SHARED_LIBS` when building with cmake: +```bash +cmake -B build -DBUILD_SHARED_LIBS=ON && cmake --build build --parallel +``` + ---- ## For MacOS: diff --git a/docs/assets/python.png b/docs/assets/python.png new file mode 100644 index 0000000..219d74a Binary files /dev/null and b/docs/assets/python.png differ diff --git a/ffi/python/.gitignore b/ffi/python/.gitignore new file mode 100644 index 0000000..64d49ae --- /dev/null +++ b/ffi/python/.gitignore @@ -0,0 +1,216 @@ +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[codz] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py.cover +.hypothesis/ +.pytest_cache/ +cover/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +.pybuilder/ +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +# For a library or package, you might want to ignore these files since the code is +# intended to run in multiple environments; otherwise, check them in: +# .python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +# Pipfile.lock + +# UV +# Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control. +# This is especially recommended for binary packages to ensure reproducibility, and is more +# commonly ignored for libraries. +# uv.lock + +# poetry +# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. +# This is especially recommended for binary packages to ensure reproducibility, and is more +# commonly ignored for libraries. +# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control +# poetry.lock +# poetry.toml + +# pdm +# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. +# pdm recommends including project-wide configuration in pdm.toml, but excluding .pdm-python. +# https://pdm-project.org/en/latest/usage/project/#working-with-version-control +# pdm.lock +# pdm.toml +.pdm-python +.pdm-build/ + +# pixi +# Similar to Pipfile.lock, it is generally recommended to include pixi.lock in version control. +# pixi.lock +# Pixi creates a virtual environment in the .pixi directory, just like venv module creates one +# in the .venv directory. It is recommended not to include this directory in version control. +.pixi + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# Redis +*.rdb +*.aof +*.pid + +# RabbitMQ +mnesia/ +rabbitmq/ +rabbitmq-data/ + +# ActiveMQ +activemq-data/ + +# SageMath parsed files +*.sage.py + +# Environments +.env +.envrc +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# pytype static type analyzer +.pytype/ + +# Cython debug symbols +cython_debug/ + +# PyCharm +# JetBrains specific template is maintained in a separate JetBrains.gitignore that can +# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore +# and can be added to the global gitignore or merged into this file. For a more nuclear +# option (not recommended) you can uncomment the following to ignore the entire idea folder. +# .idea/ + +# Abstra +# Abstra is an AI-powered process automation framework. +# Ignore directories containing user credentials, local state, and settings. +# Learn more at https://abstra.io/docs +.abstra/ + +# Visual Studio Code +# Visual Studio Code specific template is maintained in a separate VisualStudioCode.gitignore +# that can be found at https://github.com/github/gitignore/blob/main/Global/VisualStudioCode.gitignore +# and can be added to the global gitignore or merged into this file. However, if you prefer, +# you could uncomment the following to ignore the entire vscode folder +# .vscode/ + +# Ruff stuff: +.ruff_cache/ + +# PyPI configuration file +.pypirc + +# Marimo +marimo/_static/ +marimo/_lsp/ +__marimo__/ + +# Streamlit +.streamlit/secrets.toml \ No newline at end of file diff --git a/ffi/python/README.md b/ffi/python/README.md new file mode 100644 index 0000000..0067f6f --- /dev/null +++ b/ffi/python/README.md @@ -0,0 +1,30 @@ +# Python Bindings for MLX42 + +This directory contains Python bindings for the MLX42 graphics library, allowing you to use MLX42 functionalities in Python applications (using ^=3.13). + +## Building + +```bash +git clone https://github.com/codam-coding-college/MLX42.git +cd MLX42 +cmake -B build -DBUILD_SHARED_LIBS=ON && cmake --build build --parallel +``` + +Within MLX42's root directory, there is an example script located at `ffi/python/example.py`. +You can run this script to see how to use the Python bindings: + +In case you have a static library (`libmlx42.a`), you can create a shared library from it using `clang` or `gcc`: +```bash +clang -shared -o libmlx42.so libmlx42.a +``` + +Afterwards, place the shared library in the `ffi/python/` directory: +```bash +# You can also move the shared library onto a system path +mv build/libmlx42.so ffi/python/libmlx42.so +cd ffi/python +python3 example.py +``` + +# Example Output +![Example Output](../../docs/assets/python.png) \ No newline at end of file diff --git a/ffi/python/example.py b/ffi/python/example.py new file mode 100644 index 0000000..02dfc34 --- /dev/null +++ b/ffi/python/example.py @@ -0,0 +1,89 @@ +# ============================================================================ +# Codam Coding College, Amsterdam 2018-2025, All Rights Reserved. +# See README in the root project for more information. +# ============================================================================ + +import signal +import logging +import random +from libmlx import * + +# ============================================================================ + +WIDTH = 512 +HEIGHT = 512 + +# ============================================================================ + +def ft_pixel(r, g, b, a): + """ Packs RGBA values into a single 32-bit integer. """ + return (r << 24) | (g << 16) | (b << 8) | a + +@mlx_loop_hook_func +def ft_randomize(param): + for i in range(image.contents.width): + for y in range(image.contents.height): + color = ft_pixel( + random.randint(0, 255), # R + random.randint(0, 255), # G + random.randint(0, 255), # B + random.randint(0, 255) # A + ) + mlx.mlx_put_pixel(image, i, y, color) + +@mlx_loop_hook_func +def ft_hook(param): + mlx_ptr = ctypes.cast(param, ctypes.POINTER(mlx_t)) + if mlx.mlx_is_key_down(mlx_ptr, MLX_KEY_ESCAPE): + mlx.mlx_close_window(mlx_ptr) + if mlx.mlx_is_key_down(mlx_ptr, MLX_KEY_UP): + image.contents.instances[0].y -= 5 + if mlx.mlx_is_key_down(mlx_ptr, MLX_KEY_DOWN): + image.contents.instances[0].y += 5 + if mlx.mlx_is_key_down(mlx_ptr, MLX_KEY_LEFT): + image.contents.instances[0].x -= 5 + if mlx.mlx_is_key_down(mlx_ptr, MLX_KEY_RIGHT): + image.contents.instances[0].x += 5 + +def main(): + # Better to encapsulate in a class, but keeping it simple for the example + global image + global mlx_ptr + + # Initialize MLX + mlx_ptr = mlx.mlx_init(500, 800, b"MLX42 Python Example", True) + if not mlx_ptr: + logging.critical(f"Error: {mlx.mlx_strerror(mlx.mlx_get_errno()).decode()}") + exit(1) + + # Create a new image + image = mlx.mlx_new_image(mlx_ptr, 128, 128) + if not image: + mlx.mlx_close_window(mlx_ptr) + logging.critical(f"Error: {mlx.mlx_strerror(mlx.mlx_get_errno()).decode()}") + exit(1) + + # Display the image in the window + if mlx.mlx_image_to_window(mlx_ptr, image, 0, 0) == -1: + mlx.mlx_delete_image(mlx_ptr, image) + mlx.mlx_close_window(mlx_ptr) + logging.critical(f"Error: {mlx.mlx_strerror(mlx.mlx_get_errno()).decode()}") + exit(1) + + mlx.mlx_loop_hook(mlx_ptr, ft_randomize, ctypes.cast(mlx_ptr, c_void_p)) + mlx.mlx_loop_hook(mlx_ptr, ft_hook, ctypes.cast(mlx_ptr, c_void_p)) + + mlx.mlx_loop(mlx_ptr) + mlx.mlx_terminate(mlx_ptr) + +# When receiving SIGINT (Ctrl+C), terminate MLX properly +# Otherwise we get issues related to callbacks as MLX is trying +# invoke it during termination. +def signal_handler(sig, frame): + mlx.mlx_terminate(mlx_ptr) + +# ============================================================================ + +if __name__ == "__main__": + signal.signal(signal.SIGINT, signal_handler) + main() \ No newline at end of file diff --git a/ffi/python/libmlx.py b/ffi/python/libmlx.py new file mode 100644 index 0000000..604e845 --- /dev/null +++ b/ffi/python/libmlx.py @@ -0,0 +1,334 @@ +# ============================================================================ +# Codam Coding College, Amsterdam 2018-2025, All Rights Reserved. +# See README in the root project for more information. +# ============================================================================ +# Python FFI bindings for MLX42 +# ============================================================================ +# This module provides Python bindings to the MLX42 graphics library using +# the ctypes library. It handles loading the appropriate shared library based +# on the operating system and defines necessary types, constants, and function +# prototypes for interacting with MLX42. +# ============================================================================ + +import ctypes +from os import path +from logging import critical +from platform import system + +# ============================================================================ + +try: # Load FFI Library + lib = 'libmlx42' + system = system() + if system == "Linux": + lib_name = f"{lib}.so" + elif system == "Darwin": + lib_name = f"{lib}.dylib" + elif system == "Windows": + lib_name = f"{lib}.dll" + else: + raise RuntimeError("Unsupported operating system") + # Check if in the current working directory first + if path.exists(f"./{lib_name}"): + mlx = ctypes.CDLL(f"./{lib_name}") + else: + mlx = ctypes.CDLL(lib_name) +except OSError as e: + critical(f"Could not load the MLX42 library '{lib_name}'.") + critical(e) + exit(1) + +# Type Definitions, Constants, and Enums +# ============================================================================ + +c_int32 = ctypes.c_int32 +c_uint32 = ctypes.c_uint32 +c_uint8 = ctypes.c_uint8 +c_size_t = ctypes.c_size_t +c_bool = ctypes.c_bool +c_double = ctypes.c_double +c_char_p = ctypes.c_char_p +c_void_p = ctypes.c_void_p + +# keys_t +MLX_KEY_SPACE = 32 +MLX_KEY_APOSTROPHE = 39 +MLX_KEY_COMMA = 44 +MLX_KEY_MINUS = 45 +MLX_KEY_PERIOD = 46 +MLX_KEY_SLASH = 47 +MLX_KEY_0 = 48 +MLX_KEY_1 = 49 +MLX_KEY_2 = 50 +MLX_KEY_3 = 51 +MLX_KEY_4 = 52 +MLX_KEY_5 = 53 +MLX_KEY_6 = 54 +MLX_KEY_7 = 55 +MLX_KEY_8 = 56 +MLX_KEY_9 = 57 +MLX_KEY_SEMICOLON = 59 +MLX_KEY_EQUAL = 61 +MLX_KEY_A = 65 +MLX_KEY_B = 66 +MLX_KEY_C = 67 +MLX_KEY_D = 68 +MLX_KEY_E = 69 +MLX_KEY_F = 70 +MLX_KEY_G = 71 +MLX_KEY_H = 72 +MLX_KEY_I = 73 +MLX_KEY_J = 74 +MLX_KEY_K = 75 +MLX_KEY_L = 76 +MLX_KEY_M = 77 +MLX_KEY_N = 78 +MLX_KEY_O = 79 +MLX_KEY_P = 80 +MLX_KEY_Q = 81 +MLX_KEY_R = 82 +MLX_KEY_S = 83 +MLX_KEY_T = 84 +MLX_KEY_U = 85 +MLX_KEY_V = 86 +MLX_KEY_W = 87 +MLX_KEY_X = 88 +MLX_KEY_Y = 89 +MLX_KEY_Z = 90 +MLX_KEY_LEFT_BRACKET = 91 +MLX_KEY_BACKSLASH = 92 +MLX_KEY_RIGHT_BRACKET = 93 +MLX_KEY_GRAVE_ACCENT = 96 +MLX_KEY_ESCAPE = 256 +MLX_KEY_ENTER = 257 +MLX_KEY_TAB = 258 +MLX_KEY_BACKSPACE = 259 +MLX_KEY_INSERT = 260 +MLX_KEY_DELETE = 261 +MLX_KEY_RIGHT = 262 +MLX_KEY_LEFT = 263 +MLX_KEY_DOWN = 264 +MLX_KEY_UP = 265 +MLX_KEY_PAGE_UP = 266 +MLX_KEY_PAGE_DOWN = 267 +MLX_KEY_HOME = 268 +MLX_KEY_END = 269 +MLX_KEY_CAPS_LOCK = 280 +MLX_KEY_SCROLL_LOCK = 281 +MLX_KEY_NUM_LOCK = 282 +MLX_KEY_PRINT_SCREEN = 283 +MLX_KEY_PAUSE = 284 +MLX_KEY_F1 = 290 +MLX_KEY_F2 = 291 +MLX_KEY_F3 = 292 +MLX_KEY_F4 = 293 +MLX_KEY_F5 = 294 +MLX_KEY_F6 = 295 +MLX_KEY_F7 = 296 +MLX_KEY_F8 = 297 +MLX_KEY_F9 = 298 +MLX_KEY_F10 = 299 +MLX_KEY_F11 = 300 +MLX_KEY_F12 = 301 +MLX_KEY_F13 = 302 +MLX_KEY_F14 = 303 +MLX_KEY_F15 = 304 +MLX_KEY_F16 = 305 +MLX_KEY_F17 = 306 +MLX_KEY_F18 = 307 +MLX_KEY_F19 = 308 +MLX_KEY_F20 = 309 +MLX_KEY_F21 = 310 +MLX_KEY_F22 = 311 +MLX_KEY_F23 = 312 +MLX_KEY_F24 = 313 +MLX_KEY_F25 = 314 +MLX_KEY_KP_0 = 320 +MLX_KEY_KP_1 = 321 +MLX_KEY_KP_2 = 322 +MLX_KEY_KP_3 = 323 +MLX_KEY_KP_4 = 324 +MLX_KEY_KP_5 = 325 +MLX_KEY_KP_6 = 326 +MLX_KEY_KP_7 = 327 +MLX_KEY_KP_8 = 328 +MLX_KEY_KP_9 = 329 +MLX_KEY_KP_DECIMAL = 330 +MLX_KEY_KP_DIVIDE = 331 +MLX_KEY_KP_MULTIPLY = 332 +MLX_KEY_KP_SUBTRACT = 333 +MLX_KEY_KP_ADD = 334 +MLX_KEY_KP_ENTER = 335 +MLX_KEY_KP_EQUAL = 336 +MLX_KEY_LEFT_SHIFT = 340 +MLX_KEY_LEFT_CONTROL = 341 +MLX_KEY_LEFT_ALT = 342 +MLX_KEY_LEFT_SUPER = 343 +MLX_KEY_RIGHT_SHIFT = 344 +MLX_KEY_RIGHT_CONTROL = 345 +MLX_KEY_RIGHT_ALT = 346 +MLX_KEY_RIGHT_SUPER = 347 +MLX_KEY_MENU = 348 + +# cursor_t +MLX_CURSOR_ARROW = 0x00036001 +MLX_CURSOR_IBEAM = 0x00036002 +MLX_CURSOR_CROSSHAIR = 0x00036003 +MLX_CURSOR_HAND = 0x00036004 +MLX_CURSOR_HRESIZE = 0x00036005 +MLX_CURSOR_VRESIZE = 0x00036006 + +# mouse_mode_t +MLX_MOUSE_NORMAL = 0x00034001 +MLX_MOUSE_HIDDEN = 0x00034002 +MLX_MOUSE_DISABLED = 0x00034003 + +# mouse_key_t +MLX_MOUSE_BUTTON_LEFT = 0 +MLX_MOUSE_BUTTON_RIGHT = 1 +MLX_MOUSE_BUTTON_MIDDLE = 2 + +# modifier_key_t +MLX_SHIFT = 0x0001 +MLX_CONTROL = 0x0002 +MLX_ALT = 0x0004 +MLX_SUPERKEY = 0x0008 +MLX_CAPSLOCK = 0x0010 +MLX_NUMLOCK = 0x0020 + +MLX_RELEASE = 0 +MLX_PRESS = 1 +MLX_REPEAT = 2 + +MLX_STRETCH_IMAGE = 0, # Should images resize with the window as it's being resized or not. Default: false +MLX_FULLSCREEN = 1, # Should the window be in Fullscreen, note it will fullscreen at the given resolution. Default: false +MLX_MAXIMIZED = 2, # Start the window in a maximized state, overwrites the fullscreen state if this is true. Default: false +MLX_DECORATED = 3, # Have the window be decorated with a window bar. Default: true +MLX_HEADLESS = 4, # Run in headless mode, no window is created. (NOTE: Still requires some form of window manager such as xvfb) +MLX_SETTINGS_MAX = 5, # Setting count. + +# Structures +# ============================================================================ + +class mlx_texture_t(ctypes.Structure): + _fields_ = [ + ("width", c_uint32), + ("height", c_uint32), + ("bytes_per_pixel", c_uint8), + ("pixels", ctypes.POINTER(c_uint8)) + ] + +class mlx_instance_t(ctypes.Structure): + _fields_ = [ + ("x", c_int32), + ("y", c_int32), + ("z", c_int32), + ("enabled", c_bool) + ] + +class xpm_t(ctypes.Structure): + _fields_ = [ + ("texture", mlx_texture_t), + ("color_count", c_int32), + ("cpp", c_int32), + ("mode", ctypes.c_char) + ] + +class mlx_key_data_t(ctypes.Structure): + _fields_ = [ + ("key", c_int32), # ENUM + ("action", c_int32), # ENUM + ("os_key", c_int32), + ("modifier", c_int32) # ENUM + ] + +class mlx_image_t(ctypes.Structure): + _fields_ = [ + ("width", c_uint32), + ("height", c_uint32), + ("pixels", ctypes.POINTER(c_uint8)), + ("instances", ctypes.POINTER(mlx_instance_t)), + ("count", c_size_t), + ("enabled", c_bool), + ("context", c_void_p) + ] + +class mlx_instance_t(ctypes.Structure): + _fields_ = [ + ("x", c_int32), + ("y", c_int32), + ("z", c_int32), + ("enabled", c_bool) + ] + +class mlx_t(ctypes.Structure): + _fields_ = [ + ("window", c_void_p), + ("context", c_void_p), + ("width", c_int32), + ("height", c_int32), + ("delta_time", c_double) + ] + +# Function Callbacks +# ============================================================================ + +mlx_scrollfunc = ctypes.CFUNCTYPE(None, c_double, c_double, c_void_p) +mlx_mousefunc = ctypes.CFUNCTYPE(None, c_int32, c_int32, c_int32, c_void_p) +mlx_cursorfunc = ctypes.CFUNCTYPE(None, c_double, c_double, c_void_p) +mlx_keyfunc = ctypes.CFUNCTYPE(None, mlx_key_data_t, c_void_p) +mlx_resizefunc = ctypes.CFUNCTYPE(None, c_int32, c_int32, c_void_p) +mlx_closefunc = ctypes.CFUNCTYPE(None, c_void_p) +mlx_loop_hook_func = ctypes.CFUNCTYPE(None, c_void_p) + +# Function Signatures +# ============================================================================ + +# Error +mlx.mlx_strerror.argtypes = [c_int32] # errno_t enum +mlx.mlx_strerror.restype = c_char_p + +mlx.mlx_get_errno.argtypes = [] +mlx.mlx_get_errno.restype = c_int32 # errno_t enum + + +# Generic MLX Functions +mlx.mlx_init.argtypes = [c_int32, c_int32, c_char_p, c_bool] +mlx.mlx_init.restype = ctypes.POINTER(mlx_t) + +mlx.mlx_set_setting.argtypes = [ctypes.c_int, c_int32] # mlx_settings_t +mlx.mlx_set_setting.restype = None + +mlx.mlx_close_window.argtypes = [ctypes.POINTER(mlx_t)] +mlx.mlx_close_window.restype = None + +mlx.mlx_loop.argtypes = [ctypes.POINTER(mlx_t)] +mlx.mlx_loop.restype = None + +mlx.mlx_terminate.argtypes = [ctypes.POINTER(mlx_t)] +mlx.mlx_terminate.restype = None + +mlx.mlx_get_time.argtypes = [] +mlx.mlx_get_time.restype = c_double + + +# Image Functions +mlx.mlx_put_pixel.argtypes = [ctypes.POINTER(mlx_image_t), c_uint32, c_uint32, c_uint32] +mlx.mlx_put_pixel.restype = None + +mlx.mlx_new_image.argtypes = [ctypes.POINTER(mlx_t), c_uint32, c_uint32] +mlx.mlx_new_image.restype = ctypes.POINTER(mlx_image_t) + +mlx.mlx_image_to_window.argtypes = [ctypes.POINTER(mlx_t), ctypes.POINTER(mlx_image_t), c_int32, c_int32] +mlx.mlx_image_to_window.restype = c_int32 + +mlx.mlx_delete_image.argtypes = [ctypes.POINTER(mlx_t), ctypes.POINTER(mlx_image_t)] +mlx.mlx_delete_image.restype = None + +# Input +mlx.mlx_is_key_down.argtypes = [ctypes.POINTER(mlx_t), ctypes.c_int] # keys_t +mlx.mlx_is_key_down.restype = c_bool + +# Hooks +mlx.mlx_loop_hook.argtypes = [ctypes.POINTER(mlx_t), mlx_loop_hook_func, c_void_p] +mlx.mlx_loop_hook.restype = c_bool \ No newline at end of file diff --git a/include/MLX42/MLX42.h b/include/MLX42/MLX42.h index 0429c83..6dc1750 100644 --- a/include/MLX42/MLX42.h +++ b/include/MLX42/MLX42.h @@ -6,7 +6,7 @@ /* By: W2Wizard +#+ */ /* +#+ */ /* Created: 2021/12/28 02:29:06 by W2Wizard #+# #+# */ -/* Updated: 2023/03/30 16:23:19 by ntamayo- ######## odam.nl */ +/* Updated: 2025/11/16 13:00:19 by w2wizard ######## odam.nl */ /* */ /* ************************************************************************** */ @@ -32,6 +32,17 @@ # include # include # include + +#if defined(_WIN32) +# if defined(MLX42_BUILD_SHARED) +# define MLX_API __declspec(dllexport) +# else +# define MLX_API __declspec(dllimport) +# endif +#else + #define MLX_API __attribute__((visibility("default"))) +#endif + # ifdef __cplusplus extern "C" { # endif @@ -478,7 +489,14 @@ typedef void mlx_win_cursor_t; * @param[in] val The error code. * @return The error string that describes the error code. */ -const char* mlx_strerror(mlx_errno_t val); +MLX_API const char* mlx_strerror(mlx_errno_t val); + +/** + * Gets the current global error code of MLX. Useful for FFI bindings. + * @example printf("Error: %s\n", mlx_strerror(get_mlx_errno())); + * @return The current global error code. + */ +MLX_API mlx_errno_t mlx_get_errno(void); //= Generic Functions =// @@ -491,7 +509,7 @@ const char* mlx_strerror(mlx_errno_t val); * @param[in] resize Enable window resizing. * @returns Ptr to the MLX handle or null on failure. */ -mlx_t* mlx_init(int32_t width, int32_t height, const char* title, bool resize); +MLX_API mlx_t* mlx_init(int32_t width, int32_t height, const char* title, bool resize); /** * Set a setting for MLX42. @@ -500,7 +518,7 @@ mlx_t* mlx_init(int32_t width, int32_t height, const char* title, bool resize); * @param[in] setting The settings value, See mlx_settings_t type. * @param[in] value Settings value to determine the state of the setting. Can be a boolean or an enum / macro. */ -void mlx_set_setting(mlx_settings_t setting, int32_t value); +MLX_API void mlx_set_setting(mlx_settings_t setting, int32_t value); /** * Notifies MLX that it should stop rendering and exit the main loop. @@ -508,7 +526,7 @@ void mlx_set_setting(mlx_settings_t setting, int32_t value); * * @param[in] mlx The MLX instance handle. */ -void mlx_close_window(mlx_t* mlx); +MLX_API void mlx_close_window(mlx_t* mlx); /** * Initializes the rendering of MLX, this function won't return until @@ -517,7 +535,7 @@ void mlx_close_window(mlx_t* mlx); * * @param[in] mlx The MLX instance handle. */ -void mlx_loop(mlx_t* mlx); +MLX_API void mlx_loop(mlx_t* mlx); /** * Lets you set a custom image as the program icon. @@ -529,7 +547,7 @@ void mlx_loop(mlx_t* mlx); * @param[in] mlx The MLX instance handle. * @param[in] image The image to use as icon. */ -void mlx_set_icon(mlx_t* mlx, mlx_texture_t* image); +MLX_API void mlx_set_icon(mlx_t* mlx, mlx_texture_t* image); /** * Terminates MLX and cleans up any of its used resources. @@ -538,14 +556,14 @@ void mlx_set_icon(mlx_t* mlx, mlx_texture_t* image); * * @param[in] mlx The MLX instance handle. */ -void mlx_terminate(mlx_t* mlx); +MLX_API void mlx_terminate(mlx_t* mlx); /** * Gets the elapsed time since MLX was initialized. * * @return The amount of time elapsed in seconds. */ -double mlx_get_time(void); +MLX_API double mlx_get_time(void); //= Window/Monitor Functions =// @@ -558,7 +576,7 @@ double mlx_get_time(void); * * @param[in] mlx The MLX instance handle. */ -void mlx_focus(mlx_t* mlx); +MLX_API void mlx_focus(mlx_t* mlx); /** * Gets the size of the specified monitor. @@ -567,7 +585,7 @@ void mlx_focus(mlx_t* mlx); * @param[in] width The width of the window. * @param[in] height The height of the window. */ -void mlx_get_monitor_size(int32_t index, int32_t* width, int32_t* height); +MLX_API void mlx_get_monitor_size(int32_t index, int32_t* width, int32_t* height); /** * Sets the window's position. @@ -579,7 +597,7 @@ void mlx_get_monitor_size(int32_t index, int32_t* width, int32_t* height); * @param[in] xpos The x position. * @param[in] ypos The y position. */ -void mlx_set_window_pos(mlx_t* mlx, int32_t xpos, int32_t ypos); +MLX_API void mlx_set_window_pos(mlx_t* mlx, int32_t xpos, int32_t ypos); /** * Gets the window's position. @@ -588,7 +606,7 @@ void mlx_set_window_pos(mlx_t* mlx, int32_t xpos, int32_t ypos); * @param[out] xpos The x position. * @param[out] ypos The y position. */ -void mlx_get_window_pos(mlx_t* mlx, int32_t* xpos, int32_t* ypos); +MLX_API void mlx_get_window_pos(mlx_t* mlx, int32_t* xpos, int32_t* ypos); /** * Changes the window size to the newly specified values. @@ -598,7 +616,7 @@ void mlx_get_window_pos(mlx_t* mlx, int32_t* xpos, int32_t* ypos); * @param[in] new_width The new desired width. * @param[in] new_height The new desired height. */ -void mlx_set_window_size(mlx_t* mlx, int32_t new_width, int32_t new_height); +MLX_API void mlx_set_window_size(mlx_t* mlx, int32_t new_width, int32_t new_height); /** * Sets the size limits of the specified window. @@ -613,7 +631,7 @@ void mlx_set_window_size(mlx_t* mlx, int32_t new_width, int32_t new_height); * @param[in] min_h The min height of the window. * @param[in] max_h The max height of the window. */ -void mlx_set_window_limit(mlx_t* mlx, int32_t min_w, int32_t min_h, int32_t max_w, int32_t max_h); +MLX_API void mlx_set_window_limit(mlx_t* mlx, int32_t min_w, int32_t min_h, int32_t max_w, int32_t max_h); /** * Sets the title of the window. @@ -621,7 +639,7 @@ void mlx_set_window_limit(mlx_t* mlx, int32_t min_w, int32_t min_h, int32_t max_ * @param[in] mlx The MLX instance handle. * @param[in] title The window title. */ -void mlx_set_window_title(mlx_t* mlx, const char* title); +MLX_API void mlx_set_window_title(mlx_t* mlx, const char* title); //= Input Functions =// @@ -632,7 +650,7 @@ void mlx_set_window_title(mlx_t* mlx, const char* title); * @param[in] key The keycode to check, use MLX_KEY_... to specify! * @returns True or false if the key is down or not. */ -bool mlx_is_key_down(mlx_t* mlx, keys_t key); +MLX_API bool mlx_is_key_down(mlx_t* mlx, keys_t key); /** * Checks whether a mouse button is pressed or not. @@ -641,7 +659,7 @@ bool mlx_is_key_down(mlx_t* mlx, keys_t key); * @param[in] key A specific mouse key. e.g MLX_MOUSE_BUTTON_0 * @returns True or false if the mouse key is down or not. */ -bool mlx_is_mouse_down(mlx_t* mlx, mouse_key_t key); +MLX_API bool mlx_is_mouse_down(mlx_t* mlx, mouse_key_t key); /** * Returns the current, relative, mouse cursor position on the window, starting @@ -654,7 +672,7 @@ bool mlx_is_mouse_down(mlx_t* mlx, mouse_key_t key); * @param[out] x The position. * @param[out] y The position. */ -void mlx_get_mouse_pos(mlx_t* mlx, int32_t* x, int32_t* y); +MLX_API void mlx_get_mouse_pos(mlx_t* mlx, int32_t* x, int32_t* y); /** * Sets the mouse position. @@ -662,7 +680,7 @@ void mlx_get_mouse_pos(mlx_t* mlx, int32_t* x, int32_t* y); * @param[in] mlx The MLX instance handle. * @param[in] pos The position. */ -void mlx_set_mouse_pos(mlx_t* mlx, int32_t x, int32_t y); +MLX_API void mlx_set_mouse_pos(mlx_t* mlx, int32_t x, int32_t y); /** * Defines the state for the cursor. @@ -670,7 +688,7 @@ void mlx_set_mouse_pos(mlx_t* mlx, int32_t x, int32_t y); * @param[in] mlx The MLX instance handle. * @param[in] mode A specified mouse mode. */ -void mlx_set_cursor_mode(mlx_t* mlx, mouse_mode_t mode); +MLX_API void mlx_set_cursor_mode(mlx_t* mlx, mouse_mode_t mode); /** * Retrieves the system standard cursor. @@ -678,7 +696,7 @@ void mlx_set_cursor_mode(mlx_t* mlx, mouse_mode_t mode); * @param[in] type The standard cursor type to create. * @return The cursor object or null on failure. */ -mlx_win_cursor_t* mlx_create_std_cursor(cursor_t type); +MLX_API mlx_win_cursor_t* mlx_create_std_cursor(cursor_t type); /** * Allows for the creation of custom cursors with a given texture. @@ -689,14 +707,14 @@ mlx_win_cursor_t* mlx_create_std_cursor(cursor_t type); * @param[in] texture The texture to use as cursor. * @returns The cursor object or null on failure. */ -mlx_win_cursor_t* mlx_create_cursor(mlx_texture_t* texture); +MLX_API mlx_win_cursor_t* mlx_create_cursor(mlx_texture_t* texture); /** * Destroys the given cursor object. * * @param[in] cursor The cursor object to destroy. */ -void mlx_destroy_cursor(mlx_win_cursor_t* cursor); +MLX_API void mlx_destroy_cursor(mlx_win_cursor_t* cursor); /** * Sets the current cursor to the given custom cursor. @@ -704,7 +722,7 @@ void mlx_destroy_cursor(mlx_win_cursor_t* cursor); * @param[in] mlx The MLX instance handle. * @param[in] cursor The cursor object to display, if null default cursor is selected. */ -void mlx_set_cursor(mlx_t* mlx, mlx_win_cursor_t* cursor); +MLX_API void mlx_set_cursor(mlx_t* mlx, mlx_win_cursor_t* cursor); //= Hooks =// @@ -716,7 +734,7 @@ void mlx_set_cursor(mlx_t* mlx, mlx_win_cursor_t* cursor); * @param[in] func The scroll wheel callback function. * @param[in] param An additional optional parameter. */ -void mlx_scroll_hook(mlx_t* mlx, mlx_scrollfunc func, void* param); +MLX_API void mlx_scroll_hook(mlx_t* mlx, mlx_scrollfunc func, void* param); /** * This function sets the mouse callback, which is called when a mouse @@ -726,7 +744,7 @@ void mlx_scroll_hook(mlx_t* mlx, mlx_scrollfunc func, void* param); * @param[in] func The mouse callback function. * @param[in] param An additional optional parameter. */ -void mlx_mouse_hook(mlx_t* mlx, mlx_mousefunc func, void* param); +MLX_API void mlx_mouse_hook(mlx_t* mlx, mlx_mousefunc func, void* param); /** * This function sets the cursor callback, which is called when the @@ -736,7 +754,7 @@ void mlx_mouse_hook(mlx_t* mlx, mlx_mousefunc func, void* param); * @param[in] func The cursor callback function. * @param[in] param An additional optional parameter. */ -void mlx_cursor_hook(mlx_t* mlx, mlx_cursorfunc func, void* param); +MLX_API void mlx_cursor_hook(mlx_t* mlx, mlx_cursorfunc func, void* param); /** * This function sets the key callback, which is called when a key is pressed @@ -746,7 +764,7 @@ void mlx_cursor_hook(mlx_t* mlx, mlx_cursorfunc func, void* param); * @param[in] func The keypress callback function. * @param[in] param An additional optional parameter. */ -void mlx_key_hook(mlx_t* mlx, mlx_keyfunc func, void* param); +MLX_API void mlx_key_hook(mlx_t* mlx, mlx_keyfunc func, void* param); /** * This function sets the close callback, which is called in attempt to close @@ -756,7 +774,7 @@ void mlx_key_hook(mlx_t* mlx, mlx_keyfunc func, void* param); * @param[in] func The close callback function. * @param[in] param An additional optional parameter. */ -void mlx_close_hook(mlx_t* mlx, mlx_closefunc func, void* param); +MLX_API void mlx_close_hook(mlx_t* mlx, mlx_closefunc func, void* param); /** * This function sets the resize callback, which is called when the window is @@ -766,7 +784,7 @@ void mlx_close_hook(mlx_t* mlx, mlx_closefunc func, void* param); * @param[in] func The resize callback function. * @param[in] param An additional optional parameter. */ -void mlx_resize_hook(mlx_t* mlx, mlx_resizefunc func, void* param); +MLX_API void mlx_resize_hook(mlx_t* mlx, mlx_resizefunc func, void* param); /** * Generic loop hook for any custom hooks to add to the main loop. @@ -777,7 +795,7 @@ void mlx_resize_hook(mlx_t* mlx, mlx_resizefunc func, void* param); * @param[in] param The parameter to pass on to the function. * @returns Whether or not the hook was added successfully. */ -bool mlx_loop_hook(mlx_t* mlx, void (*f)(void*), void* param); +MLX_API bool mlx_loop_hook(mlx_t* mlx, void (*f)(void*), void* param); //= Texture Functions =// @@ -787,7 +805,7 @@ bool mlx_loop_hook(mlx_t* mlx, void (*f)(void*), void* param); * @param[in] path Path to the PNG file. * @return If successful the texture data is returned, else NULL. */ -mlx_texture_t* mlx_load_png(const char* path); +MLX_API mlx_texture_t* mlx_load_png(const char* path); /** * Loads an XPM42 texture from the given file path. @@ -795,14 +813,14 @@ mlx_texture_t* mlx_load_png(const char* path); * @param[in] path The file path to the XPM texture. * @returns The XPM texture struct containing its information. */ -xpm_t* mlx_load_xpm42(const char* path); +MLX_API xpm_t* mlx_load_xpm42(const char* path); /** * Deletes a texture by freeing its allocated data. * * @param[in] texture The texture to free. */ -void mlx_delete_texture(mlx_texture_t* texture); +MLX_API void mlx_delete_texture(mlx_texture_t* texture); /** * Deletes an XPM42 texture by freeing its allocated data. @@ -812,7 +830,7 @@ void mlx_delete_texture(mlx_texture_t* texture); * * @param[in] xpm The xpm texture to delete. */ -void mlx_delete_xpm42(xpm_t* xpm); +MLX_API void mlx_delete_xpm42(xpm_t* xpm); /** * Converts a given texture to an image. @@ -821,7 +839,7 @@ void mlx_delete_xpm42(xpm_t* xpm); * @param[in] texture The texture to use to create the image from. * @return mlx_image_t* The image created from the texture. */ -mlx_image_t* mlx_texture_to_image(mlx_t* mlx, mlx_texture_t* texture); +MLX_API mlx_image_t* mlx_texture_to_image(mlx_t* mlx, mlx_texture_t* texture); //= Image Functions =// @@ -836,7 +854,7 @@ mlx_image_t* mlx_texture_to_image(mlx_t* mlx, mlx_texture_t* texture); * @param[in] y The Y coordinate position. * @param[in] color The color value to put. */ -void mlx_put_pixel(mlx_image_t* image, uint32_t x, uint32_t y, uint32_t color); +MLX_API void mlx_put_pixel(mlx_image_t* image, uint32_t x, uint32_t y, uint32_t color); /** * Creates and allocates a new image buffer. @@ -846,7 +864,7 @@ void mlx_put_pixel(mlx_image_t* image, uint32_t x, uint32_t y, uint32_t color); * @param[in] height The desired height of the image. * @return Pointer to the image buffer, if it failed to allocate then NULL. */ -mlx_image_t* mlx_new_image(mlx_t* mlx, uint32_t width, uint32_t height); +MLX_API mlx_image_t* mlx_new_image(mlx_t* mlx, uint32_t width, uint32_t height); /** * Draws a new instance of an image, it will then share the same @@ -865,7 +883,7 @@ mlx_image_t* mlx_new_image(mlx_t* mlx, uint32_t width, uint32_t height); * @param[in] y The Y position. * @return Index to the instance, or -1 on failure. */ -int32_t mlx_image_to_window(mlx_t* mlx, mlx_image_t* img, int32_t x, int32_t y); +MLX_API int32_t mlx_image_to_window(mlx_t* mlx, mlx_image_t* img, int32_t x, int32_t y); /** * Deleting an image will remove it from the render queue as well as any and all @@ -878,7 +896,7 @@ int32_t mlx_image_to_window(mlx_t* mlx, mlx_image_t* img, int32_t x, int32_t y); * @param[in] mlx The MLX instance handle. * @param[in] image The image to delete. */ -void mlx_delete_image(mlx_t* mlx, mlx_image_t* image); +MLX_API void mlx_delete_image(mlx_t* mlx, mlx_image_t* image); /** * Allows you to resize an image, a new pixel buffer is allocated @@ -889,7 +907,7 @@ void mlx_delete_image(mlx_t* mlx, mlx_image_t* image); * @param[in] nheight The new height. * @return True if image was resized or false on error. */ -bool mlx_resize_image(mlx_image_t* img, uint32_t nwidth, uint32_t nheight); +MLX_API bool mlx_resize_image(mlx_image_t* img, uint32_t nwidth, uint32_t nheight); /** * Sets the depth / Z axis value of an instance. @@ -901,7 +919,7 @@ bool mlx_resize_image(mlx_image_t* img, uint32_t nwidth, uint32_t nheight); * @param[in] instance The instance on which to change the depth. * @param[in] zdepth The new depth value. */ -void mlx_set_instance_depth(mlx_instance_t* instance, int32_t zdepth); +MLX_API void mlx_set_instance_depth(mlx_instance_t* instance, int32_t zdepth); //= String Functions =// @@ -914,14 +932,14 @@ void mlx_set_instance_depth(mlx_instance_t* instance, int32_t zdepth); * @param[in] y The Y location. * @return Image ptr to the string. */ -mlx_image_t* mlx_put_string(mlx_t* mlx, const char* str, int32_t x, int32_t y); +MLX_API mlx_image_t* mlx_put_string(mlx_t* mlx, const char* str, int32_t x, int32_t y); /** * Retrieve the texture data for the built-in font. * * @return Pointer to the built-in font texture. */ -const mlx_texture_t* mlx_get_font(void); +MLX_API const mlx_texture_t* mlx_get_font(void); /** * This function lets you retrieve the X offset @@ -932,7 +950,7 @@ const mlx_texture_t* mlx_get_font(void); * @param[in] c The character to get the offset from. * @return Non-negative if found or -1 if not found. */ -int32_t mlx_get_texoffset(char c); +MLX_API int32_t mlx_get_texoffset(char c); # ifdef __cplusplus } diff --git a/src/utils/mlx_error.c b/src/utils/mlx_error.c index 1a9f867..cc36b5c 100644 --- a/src/utils/mlx_error.c +++ b/src/utils/mlx_error.c @@ -6,7 +6,7 @@ /* By: W2Wizard +#+ */ /* +#+ */ /* Created: 2021/12/28 02:51:54 by W2Wizard #+# #+# */ -/* Updated: 2022/11/22 08:50:15 by jvan-hal ######## odam.nl */ +/* Updated: 2025/11/16 13:00:50 by w2wizard ######## odam.nl */ /* */ /* ************************************************************************** */ @@ -36,9 +36,9 @@ static const char* mlx_errors[MLX_ERRMAX] = { /** * Functions to set the error number, simply for convenience. - * + * * @param val The error value. - * @return Always false + * @return Always false */ bool mlx_error(mlx_errno_t val) { @@ -58,3 +58,8 @@ const char* mlx_strerror(mlx_errno_t val) return (mlx_errors[val]); } + +mlx_errno_t mlx_get_errno(void) +{ + return (mlx_errno); +} \ No newline at end of file