project('cthulhu', version: '2026.07.18', meson_version: '>= 1.0.0', ) python = import('python') i18n = import('i18n') python_minimum_version = '3.9' python3 = python.find_installation('python3', required: true) if not python3.language_version().version_compare(f'>= @python_minimum_version@') error(f'Python @python_minimum_version@ or newer is required.') endif # Hard dependencies (checked via pkg-config) dependency('atspi-2', version: '>= 2.52.0') dependency('atk-bridge-2.0', version: '>= 2.26.0') dependency('pygobject-3.0', version: '>= 3.18') # Hard Python module dependencies gi_result = python.find_installation('python3', modules:['gi'], required: false) if not gi_result.found() error('gi (PyGObject) is required') endif json_result = python.find_installation('python3', modules:['json'], required: false) if not json_result.found() error('json module is required') endif pluggy_result = python.find_installation('python3', modules:['pluggy'], required: false) if not pluggy_result.found() error('pluggy module is required') endif # End users might not have the Gtk development libraries installed, making pkg-config fail. # Therefore, check this dependency via python. gtk_major_version = '3' gtk_minor_version = '0' gtk_command = ' '.join([ 'import gi; gi.require_version("Gtk", "3.0"); from gi.repository import Gtk;', 'print(f"{Gtk.get_major_version()}.{Gtk.get_minor_version()}.{Gtk.get_micro_version()}");', f'failed = Gtk.get_major_version() != @gtk_major_version@;', 'exit(failed)' ]) gtk_test = run_command(python3, '-c', gtk_command, check: false) description = f'GTK @gtk_major_version@.@gtk_minor_version@' version = gtk_test.stdout().strip() if gtk_test.returncode() != 0 error(f'@description@ failed (found @version@)') endif # Optional python modules with their descriptions optional_modules = { 'brlapi': 'braille output', 'louis': 'contracted braille', 'speechd': 'speech output', 'dasbus': 'D-Bus remote controller', 'pywayland': 'Wayland shared workspace backend', 'gi.repository.Wnck': 'mouse review', } summary = {} wine_access_mode = get_option('wine-access') wine_access_tools = { 'cmake': find_program('cmake', required: false), 'winegcc': find_program('winegcc', required: false), 'wineg++': find_program('wineg++', required: false), 'widl': find_program('widl', required: false), } wine_access_enabled = wine_access_mode != 'disabled' and host_machine.cpu_family() == 'x86_64' foreach tool_name, tool : wine_access_tools if not tool.found() wine_access_enabled = false if wine_access_mode == 'enabled' error(f'-Dwine-access=enabled requires @tool_name@') endif endif endforeach if wine_access_mode == 'enabled' and host_machine.cpu_family() != 'x86_64' error('-Dwine-access=enabled currently requires an x86_64 build host') endif if wine_access_enabled wine_access_outputs = custom_target( 'wine-access-helper', output: ['cthulhu-wine-access.exe', 'cthulhu-wine-access.exe.so'], command: [ find_program('bash'), files('scripts/build-wine-access.sh'), meson.current_source_dir() / 'wine-access', meson.current_build_dir() / 'wine-access-build', '@OUTPUT0@', '@OUTPUT1@', ], depend_files: files( 'scripts/build-wine-access.sh', 'wine-access/CMakeLists.txt', 'wine-access/main.cpp', 'wine-access/nvdaController.idl', 'wine-access/winelib-toolchain.cmake', ), build_by_default: true, install: true, install_dir: get_option('libexecdir') / 'cthulhu' / 'wine', ) summary += {'Wine accessibility': 'yes (Winelib x86_64 helper)'} else summary += {'Wine accessibility': 'no'} endif foreach module, description : optional_modules result = python.find_installation('python3', modules:[module], required: false) if result.found() summary += {description: f'yes (found @module@)'} else summary += {description: f'no (missing @module@)'} endif endforeach # Optional synthesizer services speech_output = [] if python.find_installation('python3', modules:['speechd'], required: false).found() speech_output += ['speechd'] endif if speech_output.length() > 0 summary += {'speech output': 'yes (found @0@)'.format(', '.join(speech_output))} else summary += {'speech output': 'no (missing speechd)'} endif # Check for GStreamer gstreamer_dep = dependency('gstreamer-1.0', required: false) if gstreamer_dep.found() summary += {'sound support': 'yes (found gstreamer)'} else summary += {'sound support': 'no (missing gstreamer)'} endif # Update icon cache manually (desktop-neutral) - optional, ignore failures gtk_update_icon_cache = find_program('gtk4-update-icon-cache', required: false) if gtk_update_icon_cache.found() meson.add_install_script('sh', '-c', 'gtk4-update-icon-cache -q -t -f "$MESON_INSTALL_DESTDIR_PREFIX/' + get_option('datadir') / 'icons' / 'hicolor' + '" || echo "Icon cache update skipped"') endif summary += {'Install dir': python.find_installation('python3').get_install_dir()} subdir('docs') subdir('icons') subdir('po') subdir('sounds') subdir('src') summary(summary)