aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGravatar Hanspeter Portner <dev@open-music-kontrollers.ch>2019-08-19 20:53:29 +0200
committerGravatar Hanspeter Portner <dev@open-music-kontrollers.ch>2019-08-19 20:53:29 +0200
commite8c89d26cc9e90ab8fa8673bf4f72271549f4946 (patch)
treea60bfc7e5392031c6a6e648ed8aae50eb1c5dd44
parentf2a3fbfb8703aa1640084f2a2582bd895b8dc85e (diff)
downloadd2tk-e8c89d26cc9e90ab8fa8673bf4f72271549f4946.zip
d2tk-e8c89d26cc9e90ab8fa8673bf4f72271549f4946.tar.gz
d2tk-e8c89d26cc9e90ab8fa8673bf4f72271549f4946.tar.bz2
d2tk-e8c89d26cc9e90ab8fa8673bf4f72271549f4946.tar.xz
Squashed 'pugl/' changes from 83a54034..908e249c
908e249c Add missing link flag to pkg-config files 074da160 Fix repetition in README fd56df57 Mac: Disable annoying bell on tab or other special key presses 561b71af Clean up includes 45f8844c Fix unnecessary conversions eb443a7a Test: Remove use of static data 1c1e053d Replace puglIgnoreKeyRepeat() with a hint 9f4a5e73 Test: Remove locale hacks 7096fa29 Test: Configure GL in configure handler instead of main b628c721 Test: Factor out FPS printer 2359dafa Add missing string termination a865ca52 Windows: Support UTF8 in window titles bb083463 X11: Support UTF8 in window titles 568d40cd Cleanup: Remove debug print that snuck in there somehow 6f39b67f Fix incorrect comment 06d7b4ac Add stability and distribution information to README 0f3a2c93 X11: Implement double buffering for Cairo f97516d6 Add animation to Cairo test program 94f74785 Build separate libraries b0a92df9 Mac: Separate backends from platform implementation 074538ba Mac: Add Cairo on Quartz support 2ea798d6 Mac: Make drawView a generic NSView 64d63867 Mac: Separate NSOpenGLView from input handling view e383016b Mac: Use scrolling delta for scroll events b5b0b7c0 Add option to install entire implementation as headers 3f98de4e Set hidden visibility globally 776c8622 Properly check for libm 0d2c8a13 Build both static and shared library by default a90a4006 Windows: Enable shared library build f89f6813 Cleanup: Use fancy syntax for waf environments 626a49c2 Cleanup: Clean up includes and call IWYU in lint target 560bcf2a Cleanup: Fix Python whitespace 29940c35 Fix various clang-tidy warnings c74467ae Fix potential memory leak on error 37fe29ab Reorganize source to separate private implementation details 41dea932 Clean up file documentation 50499530 Completely separate backends from platform implementation 657a30d2 Expose PuglBackend type 982ea3f0 Add deprecation macro and deprecate puglInitResizable() e2389032 Move trivial backend dispatch functions to common code c6c91cca Move backend to PuglView 4d1a4421 Mac: Factor some functionality out into a backend 78ae4dee Windows: Disable DPI scaling 39553be4 Windows: Add Cairo support b63194bf Windows: Factor out GL backend 6a77f966 Make enterContext take a drawing parameter like leaveContext 6a3159df Give backends general names 65e0b7d4 Rename getHandle to getContext for consistent terminology ffdc710f Rename PuglDrawContext to PuglBackend a84fe498 Cleanup: Remove redundant preprocessor checks e340a602 Cleanup: Remove redundant context type checks 95bd290f Cleanup: Add some constness ca2adc0e Mac: Set test app bundles as high resolution capable e1c3b14e Mac: Fix warning e7236e4a Fix unused parameter warnings and prevent rot 4c4b388f Fix building pugl_cairo_test as C++ eada1042 Windows: Implement size constraints 55199fe9 Clear GL context in puglLeaveContext() on all platforms 1249cf5e Improve packing of PuglViewImpl f165aa36 X11: Fix unused parameter warnings 70d27c87 X11: Close input context on destruction 54b1d113 Add missing include 64c66ce8 Add logo resources 3359c78b Mac: Fix build on MacOS older than 10.10 2e3e9353 Mac: Use mach_absolute_time() 9bc5853b Use standard Cairo include paths everywhere 581cd286 Windows: Fix initial display once again bba5958e Use standard cairo include path 552e107f Fix type of PuglEventText::time c86f7123 Mac: Fix event Y coordinates 27dbe942 Handle backspace and delete consistently across platforms d746d668 Remove PUGL_API from documentation d32a920d Update README 31e0bebb Support additional special keys 3526bf91 Unify key and character fields and separate text events 1deb98f5 Rename PuglEventKey::utf8 to "string" with char type fd7d496d Remove view pointer from events 9d127e38 Represent event time as double in seconds on all platforms b0ddcbba Simplify modifier translation code 84834d6d Test: Move some generic code to a separate header 41373723 Test: Move test programs to a subdirectory face6b17 Test: Factor out event printing 18b6722f Mac: Give new child views focus on creation ebbf1568 Mac: Fix initial view allocation 5797e423 Mac: Fix coordinate system eb64f8a3 Windows: Only kill timer when actually flashing bea2f16b Windows: Only set double buffered pixel format when requested 07834ba6 Windows: Clean up and factor out window flags d29f1af6 Windows: Focus windows when shown 8ee97014 Windows: Improve live resize smoothness 614896f4 Windows: Fix configured window size 9ca94acb Windows: Use DispatchMessage b47bfc5f X11: Fix memory leak 1fef5e07 X11: Factor out impl variable for brevity 8d8e662f X11: Improve live resize smoothness b00a145f X11: Factor out window event mask 37b8dd23 Flatten AUTHORS file and add some missing people f08a5400 Update README 1b3f4fb9 Update stale copyright dates 99e18246 Use the standard blurb in pkg-config description 94f74fa9 Fix some warnings bc78b748 Tidy up X11 code f3366ef9 Make windows miniaturizable on MacOS 354f7576 Remove redundant and mismatched prototype 7a5dcae7 Show minimize and maximize buttons on top level windows on Windows 80191fb0 Add puglRequestAttention() 08149089 Fix handling of WM_DELETE_WINDOW a322ffa3 Use local display and window variables for brevity cb897c6f Implement focus on MacOS 17af0352 Implement puglGrabFocus on Windows and MacOS 1ffbc794 Implement aspect ratio on MacOS b5edd05a Set square aspect ratio on pugl_test window fa4b48b5 Gracefully handle failure to create window 35c050fb Set title and minimum size for pugl_cairo_test window 20114f1e Fix Cairo on MacOS fe920b2c Fix initial display on Windows again b5ef9123 Implement focus on Windows 51922247 Implement enter and leave notifications on Windows 0ef7e431 Print more information about crossing and focus events 38b4790a Implement enter and leave notifications on MacOS 89d7378a Show mouse enter/leave state in pugl_test background 29126e6a Remove redundant prototypes ae9a0995 Draw during resizing on MacOS 17a09847 Clean up Objective C syntax 93216a6e Draw during resizing on Windows 29605530 Draw in pugl_test according to display time aa7fb0aa Fix initial window display on Windows 97348233 Clean up event loop on MacOS c1e49399 Fix tracking area implementation on MacOS 753af54d Make time start from approximately zero 5ba0ea7c Remove redisplay flag and use system events instead 4d0704ba Add CI rows for MinGW e7d9aaf9 Use C for Windows implementation cf6da599 Make translateKey take a PuglEventKey f1697241 Send a configure event on initial window mapping 0497e8e5 Tidy up whitespace 9c3c035c Clean up redundant wrappers around SetWindowLongPtr 6d1e70f6 Implement attributes on Windows a1569ce4 Clean up window creation on Windows a70a3d26 Fix horizontal mouse axis in pugl_test d34e0eb1 Fix various warnings b81dbe03 Implement attributes on MacOS aa9eafb7 Enable vsync on Windows c91eb8ee Draw while resizing on Windows c3c55efc Add pugl_test option to continuously animate and report FPS 664b4972 Add puglGetTime() bd5d6431 Fix documentation b0d931d6 Remove redundant glFlush on Windows cb67b9a7 Fix some warnings on Windows d2dab054 Remove dead code on Windows 3d535a3f Fix double-buffering a522c769 Add pugl_test options for FSAA and double-buffering 2c19c0b4 Fix window embedding on Windows 5d13701f Fix window embedding on MacOS a978fd6a Make event processing non-blocking on MacOS bf7bc771 Defer to NSOpenGLView reshape method a6878f88 Build test programs in bundles on MacOS 6e0b096f Disable deprecation warnings on MacOS 4aaad461 Fix const casts bca65c93 Consistently use uint32_t everywhere 24aef2fc Fix void function prototypes 13dd9c75 Fix unnecessary const cast 7b5b07b2 Add ARM CI rows d4eaaff2 Add Gitlab CI configuration c18706e3 Fix MacOS build 7887266e Update autowaf 4806680e Squashed 'waflib/' changes from d7a7ca4..27a69a7 ee834a83 Add puglGetProcAddress for using OpenGL extensions 982c8910 Squash blank line 197c19b7 Add configuration API 51dcb00d Gracefully handle failure to create window in pugl_test 64f4befe Remove spurious double blank lines bde3d802 Add missing default case 26e312ac Fix implicit double to int casts 539c1ddd Clean up includes f46b5124 Factor out drawing context from platform window implementation cf80f78f Remove PUGL_CAIRO_GL 373b2b78 Separate internal types from functions 222320e6 Fix build with Python 2 b24b75b6 Squashed 'waflib/' changes from 542b3f5..d7a7ca4 e549b762 Merge commit 'b24b75b6ca964d5bb35e1802fdf7d03c794236ff' a1e028e8 Lint wscript 6b488fa9 Fix minor clang-tidy warnings befca854 Use clang-tidy for linting 4de165e8 Remove unused submodule 4426a331 Factor out common waf target parameters 423a9894 Print nicer configuration summary 9f009766 Don't abuse autowaf.define() bfa266ef Use autowaf.set_lib_env() 8093902d Clean up waf options bdb65b8a Remove test options 8421272d Replace waf-light with minimal waf wrapper script 77c56952 Add pyc files to gitignore b2ad0b0b Replace symlink with minimal waf wrapper script 8239fedf Squashed 'waflib/' changes from 391d285..542b3f5 6bdd956e Merge commit '8239fedfd133916f0ac26bd85ff524afca243ddb' 444365a7 Add msgs argument to autowaf.display_summary() and show flags efe1b5a2 Automatically define version 7e02a000 Add autowaf.add_flags() for terse flag definition 0173f690 Clean up wscript 0fcdaecc Add __pycache__ to gitignore 0186a78f Fix GCC8 fall-through warnings fb674c72 Switch to bundled source waf b6e9de2d Squashed 'waflib/' content from commit 391d285 62dcd3f3 Merge commit 'b6e9de2de9725e2f5a3170b8171ad1a1e95e8339' as 'waflib' c4dcd960 Don't clear entire cairo surface on each expose 39ad8490 Fix size constraints on OSX 70616649 Print Unicode code points in standard format 7a05b7c0 Send zero instead of replacement char for invalid key strings 665dfdd7 Fix implicit integer conversion warning 5a1dacef Fix duplicate method declaration 34c74f89 Implement special key handling on MacOS 8b8f97da Fix merging of expose events REVERT: 83a54034 omk: use /* fall through */ marker REVERT: 2986f313 omk: fix compiler warnings. REVERT: 371c2ec0 omk: fix memory leak. REVERT: 035884a7 Don't clear cairo canvas, support gl stencils REVERT: 4fed5fe2 Change ulong to unsigned long for FreeBSD. REVERT: 0406d71d Implement clipboard REVERT: c977ef29 Implement special key handling on MacOS git-subtree-dir: pugl git-subtree-split: 908e249c8030698e4313df9aeb81cfb7763e5150
-rw-r--r--.gitignore2
-rw-r--r--.gitlab-ci.yml104
-rw-r--r--AUTHORS17
-rw-r--r--COPYING2
-rw-r--r--Doxyfile.in23
-rw-r--r--README.md76
-rw-r--r--pugl.pc.in7
-rw-r--r--pugl/cairo_gl.h106
-rw-r--r--pugl/detail/implementation.c (renamed from pugl/pugl_internal.h)195
-rw-r--r--pugl/detail/implementation.h46
-rw-r--r--pugl/detail/mac.h59
-rw-r--r--pugl/detail/mac.m860
-rw-r--r--pugl/detail/mac_cairo.m166
-rw-r--r--pugl/detail/mac_gl.m184
-rw-r--r--pugl/detail/types.h107
-rw-r--r--pugl/detail/win.c697
-rw-r--r--pugl/detail/win.h100
-rw-r--r--pugl/detail/win_cairo.c199
-rw-r--r--pugl/detail/win_gl.c306
-rw-r--r--pugl/detail/x11.c588
-rw-r--r--pugl/detail/x11.h43
-rw-r--r--pugl/detail/x11_cairo.c176
-rw-r--r--pugl/detail/x11_gl.c218
-rw-r--r--pugl/gl.h1
-rw-r--r--pugl/glew.h3
-rw-r--r--pugl/glu.h3
-rw-r--r--pugl/pugl.h279
-rw-r--r--pugl/pugl.hpp11
-rw-r--r--pugl/pugl_cairo_backend.h42
-rw-r--r--pugl/pugl_gl_backend.h42
-rw-r--r--pugl/pugl_osx.m746
-rw-r--r--pugl/pugl_win.cpp704
-rw-r--r--pugl/pugl_x11.c810
-rw-r--r--pugl_test.c261
-rw-r--r--resources/Info.plist.in20
-rw-r--r--resources/pugl.ipe293
-rw-r--r--resources/pugl.pngbin0 -> 2641 bytes
-rw-r--r--resources/pugl.svg83
-rw-r--r--test/pugl_cairo_test.c (renamed from pugl_cairo_test.c)113
-rw-r--r--test/pugl_test.c229
-rw-r--r--test/test_utils.h167
-rwxr-xr-xwafbin97489 -> 404 bytes
-rw-r--r--waflib/.gitignore2
-rw-r--r--waflib/Build.py1496
-rw-r--r--waflib/COPYING25
-rw-r--r--waflib/ConfigSet.py361
-rw-r--r--waflib/Configure.py639
-rw-r--r--waflib/Context.py737
-rw-r--r--waflib/Errors.py68
-rw-r--r--waflib/Logs.py379
-rw-r--r--waflib/Node.py970
-rw-r--r--waflib/Options.py342
-rw-r--r--waflib/README.md24
-rw-r--r--waflib/Runner.py617
-rw-r--r--waflib/Scripting.py620
-rw-r--r--waflib/Task.py1406
-rw-r--r--waflib/TaskGen.py917
-rw-r--r--waflib/Tools/__init__.py3
-rw-r--r--waflib/Tools/ar.py24
-rw-r--r--waflib/Tools/asm.py73
-rw-r--r--waflib/Tools/bison.py49
-rw-r--r--waflib/Tools/c.py39
-rw-r--r--waflib/Tools/c_aliases.py144
-rw-r--r--waflib/Tools/c_config.py1351
-rw-r--r--waflib/Tools/c_osx.py193
-rw-r--r--waflib/Tools/c_preproc.py1091
-rw-r--r--waflib/Tools/c_tests.py229
-rw-r--r--waflib/Tools/ccroot.py791
-rw-r--r--waflib/Tools/clang.py29
-rw-r--r--waflib/Tools/clangxx.py30
-rw-r--r--waflib/Tools/compiler_c.py110
-rw-r--r--waflib/Tools/compiler_cxx.py111
-rw-r--r--waflib/Tools/compiler_d.py85
-rw-r--r--waflib/Tools/compiler_fc.py73
-rw-r--r--waflib/Tools/cs.py211
-rw-r--r--waflib/Tools/cxx.py40
-rw-r--r--waflib/Tools/d.py97
-rw-r--r--waflib/Tools/d_config.py64
-rw-r--r--waflib/Tools/d_scan.py211
-rw-r--r--waflib/Tools/dbus.py70
-rw-r--r--waflib/Tools/dmd.py80
-rw-r--r--waflib/Tools/errcheck.py237
-rw-r--r--waflib/Tools/fc.py203
-rw-r--r--waflib/Tools/fc_config.py488
-rw-r--r--waflib/Tools/fc_scan.py120
-rw-r--r--waflib/Tools/flex.py62
-rw-r--r--waflib/Tools/g95.py66
-rw-r--r--waflib/Tools/gas.py18
-rw-r--r--waflib/Tools/gcc.py156
-rw-r--r--waflib/Tools/gdc.py55
-rw-r--r--waflib/Tools/gfortran.py93
-rw-r--r--waflib/Tools/glib2.py489
-rw-r--r--waflib/Tools/gnu_dirs.py131
-rw-r--r--waflib/Tools/gxx.py157
-rw-r--r--waflib/Tools/icc.py30
-rw-r--r--waflib/Tools/icpc.py30
-rw-r--r--waflib/Tools/ifort.py413
-rw-r--r--waflib/Tools/intltool.py231
-rw-r--r--waflib/Tools/irixcc.py66
-rw-r--r--waflib/Tools/javaw.py579
-rw-r--r--waflib/Tools/ldc2.py56
-rw-r--r--waflib/Tools/lua.py38
-rw-r--r--waflib/Tools/md5_tstamp.py38
-rw-r--r--waflib/Tools/msvc.py1020
-rw-r--r--waflib/Tools/nasm.py26
-rw-r--r--waflib/Tools/nobuild.py24
-rw-r--r--waflib/Tools/perl.py156
-rw-r--r--waflib/Tools/python.py631
-rw-r--r--waflib/Tools/qt5.py796
-rw-r--r--waflib/Tools/ruby.py186
-rw-r--r--waflib/Tools/suncc.py67
-rw-r--r--waflib/Tools/suncxx.py67
-rw-r--r--waflib/Tools/tex.py543
-rw-r--r--waflib/Tools/vala.py355
-rw-r--r--waflib/Tools/waf_unit_test.py296
-rw-r--r--waflib/Tools/winres.py78
-rw-r--r--waflib/Tools/xlc.py65
-rw-r--r--waflib/Tools/xlcxx.py65
-rw-r--r--waflib/Utils.py1029
-rw-r--r--waflib/__init__.py3
-rw-r--r--waflib/ansiterm.py342
-rw-r--r--waflib/extras/__init__.py3
-rw-r--r--waflib/extras/autowaf.py1452
-rw-r--r--waflib/extras/batched_cc.py173
-rw-r--r--waflib/extras/biber.py58
-rw-r--r--waflib/extras/bjam.py128
-rw-r--r--waflib/extras/blender.py108
-rw-r--r--waflib/extras/boo.py81
-rw-r--r--waflib/extras/boost.py525
-rw-r--r--waflib/extras/build_file_tracker.py28
-rw-r--r--waflib/extras/build_logs.py110
-rw-r--r--waflib/extras/buildcopy.py85
-rw-r--r--waflib/extras/c_bgxlc.py32
-rw-r--r--waflib/extras/c_dumbpreproc.py72
-rw-r--r--waflib/extras/c_emscripten.py87
-rw-r--r--waflib/extras/c_nec.py74
-rw-r--r--waflib/extras/cabal.py152
-rw-r--r--waflib/extras/cfg_altoptions.py110
-rw-r--r--waflib/extras/clang_compilation_database.py85
-rw-r--r--waflib/extras/codelite.py875
-rw-r--r--waflib/extras/color_gcc.py39
-rw-r--r--waflib/extras/color_rvct.py51
-rw-r--r--waflib/extras/compat15.py406
-rw-r--r--waflib/extras/cppcheck.py591
-rw-r--r--waflib/extras/cpplint.py209
-rw-r--r--waflib/extras/cross_gnu.py227
-rw-r--r--waflib/extras/cython.py147
-rw-r--r--waflib/extras/dcc.py72
-rw-r--r--waflib/extras/distnet.py430
-rw-r--r--waflib/extras/doxygen.py227
-rw-r--r--waflib/extras/dpapi.py87
-rw-r--r--waflib/extras/eclipse.py431
-rw-r--r--waflib/extras/erlang.py110
-rw-r--r--waflib/extras/fast_partial.py518
-rw-r--r--waflib/extras/fc_bgxlf.py32
-rw-r--r--waflib/extras/fc_cray.py51
-rw-r--r--waflib/extras/fc_nag.py61
-rw-r--r--waflib/extras/fc_nec.py60
-rw-r--r--waflib/extras/fc_nfort.py52
-rw-r--r--waflib/extras/fc_open64.py58
-rw-r--r--waflib/extras/fc_pgfortran.py68
-rw-r--r--waflib/extras/fc_solstudio.py62
-rw-r--r--waflib/extras/fc_xlf.py63
-rw-r--r--waflib/extras/file_to_object.py137
-rw-r--r--waflib/extras/fluid.py30
-rw-r--r--waflib/extras/freeimage.py74
-rw-r--r--waflib/extras/fsb.py31
-rw-r--r--waflib/extras/fsc.py64
-rw-r--r--waflib/extras/gccdeps.py214
-rw-r--r--waflib/extras/gdbus.py87
-rw-r--r--waflib/extras/gob2.py17
-rw-r--r--waflib/extras/halide.py151
-rwxr-xr-xwaflib/extras/javatest.py118
-rw-r--r--waflib/extras/kde4.py93
-rw-r--r--waflib/extras/local_rpath.py19
-rw-r--r--waflib/extras/lv2.py75
-rw-r--r--waflib/extras/make.py142
-rw-r--r--waflib/extras/midl.py69
-rw-r--r--waflib/extras/msvcdeps.py256
-rw-r--r--waflib/extras/msvs.py1048
-rw-r--r--waflib/extras/netcache_client.py390
-rw-r--r--waflib/extras/objcopy.py50
-rw-r--r--waflib/extras/ocaml.py348
-rw-r--r--waflib/extras/package.py76
-rw-r--r--waflib/extras/parallel_debug.py462
-rw-r--r--waflib/extras/pch.py148
-rw-r--r--waflib/extras/pep8.py106
-rw-r--r--waflib/extras/pgicc.py75
-rw-r--r--waflib/extras/pgicxx.py20
-rw-r--r--waflib/extras/proc.py54
-rw-r--r--waflib/extras/protoc.py223
-rw-r--r--waflib/extras/pyqt5.py241
-rw-r--r--waflib/extras/pytest.py225
-rw-r--r--waflib/extras/qnxnto.py72
-rw-r--r--waflib/extras/qt4.py695
-rw-r--r--waflib/extras/relocation.py85
-rw-r--r--waflib/extras/remote.py327
-rw-r--r--waflib/extras/resx.py35
-rw-r--r--waflib/extras/review.py325
-rw-r--r--waflib/extras/rst.py260
-rw-r--r--waflib/extras/run_do_script.py139
-rw-r--r--waflib/extras/run_m_script.py88
-rw-r--r--waflib/extras/run_py_script.py104
-rw-r--r--waflib/extras/run_r_script.py86
-rw-r--r--waflib/extras/sas.py71
-rw-r--r--waflib/extras/satellite_assembly.py57
-rw-r--r--waflib/extras/scala.py128
-rw-r--r--waflib/extras/slow_qt4.py96
-rw-r--r--waflib/extras/softlink_libs.py76
-rw-r--r--waflib/extras/stale.py98
-rw-r--r--waflib/extras/stracedeps.py174
-rw-r--r--waflib/extras/swig.py237
-rw-r--r--waflib/extras/syms.py84
-rw-r--r--waflib/extras/ticgt.py300
-rw-r--r--waflib/extras/unity.py108
-rw-r--r--waflib/extras/use_config.py185
-rw-r--r--waflib/extras/valadoc.py140
-rw-r--r--waflib/extras/waf_xattr.py150
-rw-r--r--waflib/extras/why.py78
-rw-r--r--waflib/extras/win32_opts.py170
-rw-r--r--waflib/extras/wix.py87
-rw-r--r--waflib/extras/xcode6.py727
-rw-r--r--waflib/fixpy2.py64
-rwxr-xr-xwaflib/processor.py64
-rwxr-xr-xwaflib/waf16
-rw-r--r--wscript385
226 files changed, 47398 insertions, 3040 deletions
diff --git a/.gitignore b/.gitignore
index e511642..dad71c3 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,5 @@
.waf*/
build/
.lock-waf*
+__pycache__
+*.pyc
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
new file mode 100644
index 0000000..4fe8cf9
--- /dev/null
+++ b/.gitlab-ci.yml
@@ -0,0 +1,104 @@
+stages:
+ - build
+
+.build_template: &build_definition
+ stage: build
+
+arm32_dbg:
+ <<: *build_definition
+ image: lv2plugin/debian-arm32
+ script: python ./waf configure build -dsT --no-coverage
+ variables:
+ CC: "arm-linux-gnueabihf-gcc"
+ CXX: "arm-linux-gnueabihf-g++"
+
+arm32_rel:
+ <<: *build_definition
+ image: lv2plugin/debian-arm32
+ script: python ./waf configure build -sT --no-coverage
+ variables:
+ CC: "arm-linux-gnueabihf-gcc"
+ CXX: "arm-linux-gnueabihf-g++"
+
+arm64_dbg:
+ <<: *build_definition
+ image: lv2plugin/debian-arm64
+ script: python ./waf configure build -dsT --no-coverage
+ variables:
+ CC: "aarch64-linux-gnu-gcc"
+ CXX: "aarch64-linux-gnu-g++"
+
+arm64_rel:
+ <<: *build_definition
+ image: lv2plugin/debian-arm64
+ script: python ./waf configure build -sT --no-coverage
+ variables:
+ CC: "aarch64-linux-gnu-gcc"
+ CXX: "aarch64-linux-gnu-g++"
+
+x64_dbg:
+ <<: *build_definition
+ image: lv2plugin/debian-x64
+ script: python ./waf configure build -dsT --no-coverage
+
+x64_rel:
+ <<: *build_definition
+ image: lv2plugin/debian-x64
+ script: python ./waf configure build -sT --no-coverage
+
+mingw32_dbg:
+ <<: *build_definition
+ image: lv2plugin/debian-mingw32
+ script: python ./waf configure build -dsT --no-coverage --target=win32
+ variables:
+ CC: "i686-w64-mingw32-gcc"
+ CXX: "i686-w64-mingw32-g++"
+
+mingw32_rel:
+ <<: *build_definition
+ image: lv2plugin/debian-mingw32
+ script: python ./waf configure build -sT --no-coverage --target=win32
+ variables:
+ CC: "i686-w64-mingw32-gcc"
+ CXX: "i686-w64-mingw32-g++"
+
+mingw64_dbg:
+ <<: *build_definition
+ image: lv2plugin/debian-mingw64
+ script: python ./waf configure build -dsT --no-coverage --target=win32
+ variables:
+ CC: "x86_64-w64-mingw32-gcc"
+ CXX: "x86_64-w64-mingw32-g++"
+
+mingw64_rel:
+ <<: *build_definition
+ image: lv2plugin/debian-mingw64
+ script: python ./waf configure build -sT --no-coverage --target=win32
+ variables:
+ CC: "x86_64-w64-mingw32-gcc"
+ CXX: "x86_64-w64-mingw32-g++"
+
+mac_dbg:
+ <<: *build_definition
+ script: python ./waf configure build -dsT --no-coverage
+ tags:
+ - macos
+
+mac_rel:
+ <<: *build_definition
+ script: python ./waf configure build -sT --no-coverage
+ tags:
+ - macos
+
+win_dbg:
+ <<: *build_definition
+ script:
+ - python ./waf configure build -dT --no-coverage
+ tags:
+ - windows
+
+win_rel:
+ <<: *build_definition
+ script: python ./waf configure build -T --no-coverage
+ tags:
+ - windows
diff --git a/AUTHORS b/AUTHORS
index 5625baa..6c09a5b 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -1,11 +1,8 @@
-Author:
- David Robillard <d@drobilla.net>
+Pugl is primarily written and maintained by David Robillard <d@drobilla.net>
+with contributions from (in increasing chronological order):
-Original GLX inspiration, portability fixes:
- Ben Loftis
-
-Various fixes and improvements:
- Robin Gareus <robin@gareus.org>
-
-UTF-8 and Windows event work:
- Erik Åldstedt Sund <erikalds@gmail.com>
+Ben Loftis <ben@harrisonconsoles.com>
+Robin Gareus <robin@gareus.org>
+Erik Åldstedt Sund <erikalds@gmail.com>
+Hanspeter Portner <dev@open-music-kontrollers.ch>
+Stefan Westerfeld <stefan@space.twc.de>
diff --git a/COPYING b/COPYING
index e1e203d..b16a139 100644
--- a/COPYING
+++ b/COPYING
@@ -1,4 +1,4 @@
-Copyright 2011-2014 David Robillard <http://drobilla.net>
+Copyright 2011-2019 David Robillard <http://drobilla.net>
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
diff --git a/Doxyfile.in b/Doxyfile.in
index 59e3331..b332116 100644
--- a/Doxyfile.in
+++ b/Doxyfile.in
@@ -1,4 +1,4 @@
-# Doxyfile 1.8.12
+# Doxyfile 1.8.13
# This file describes the settings to be used by the documentation system
# doxygen (www.doxygen.org) for a project.
@@ -663,7 +663,7 @@ SHOW_USED_FILES = YES
# (if specified).
# The default value is: YES.
-SHOW_FILES = NO
+SHOW_FILES = YES
# Set the SHOW_NAMESPACES tag to NO to disable the generation of the Namespaces
# page. This will remove the Namespaces entry from the Quick Index and from the
@@ -780,7 +780,7 @@ WARN_LOGFILE =
# spaces. See also FILE_PATTERNS and EXTENSION_MAPPING
# Note: If this tag is empty the current directory is searched.
-INPUT = pugl
+INPUT = @PUGL_SRCDIR@/pugl
# This tag can be used to specify the character encoding of the source files
# that doxygen parses. Internally doxygen uses the UTF-8 encoding. Doxygen uses
@@ -1987,7 +1987,7 @@ ENABLE_PREPROCESSING = YES
# The default value is: NO.
# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
-MACRO_EXPANSION = NO
+MACRO_EXPANSION = YES
# If the EXPAND_ONLY_PREDEF and MACRO_EXPANSION tags are both set to YES then
# the macro expansion is limited to the macros specified with the PREDEFINED and
@@ -1995,7 +1995,7 @@ MACRO_EXPANSION = NO
# The default value is: NO.
# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
-EXPAND_ONLY_PREDEF = NO
+EXPAND_ONLY_PREDEF = YES
# If the SEARCH_INCLUDES tag is set to YES, the include files in the
# INCLUDE_PATH will be searched if a #include is found.
@@ -2027,7 +2027,7 @@ INCLUDE_FILE_PATTERNS =
# recursively expanded use the := operator instead of the = operator.
# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
-PREDEFINED =
+PREDEFINED = PUGL_API PUGL_DEPRECATED_BY
# If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then this
# tag can be used to specify a list of macro names that should be expanded. The
@@ -2140,7 +2140,7 @@ HIDE_UNDOC_RELATIONS = YES
# http://www.graphviz.org/), a graph visualization toolkit from AT&T and Lucent
# Bell Labs. The other options in this section have no effect if this option is
# set to NO
-# The default value is: NO.
+# The default value is: YES.
HAVE_DOT = NO
@@ -2296,7 +2296,9 @@ DIRECTORY_GRAPH = NO
# Note: If you choose svg you need to set HTML_FILE_EXTENSION to xhtml in order
# to make the SVG files visible in IE 9+ (other browsers do not have this
# requirement).
-# Possible values are: png, jpg, gif, svg, png:gd, png:gd:gd, png:cairo,
+# Possible values are: png, png:cairo, png:cairo:cairo, png:cairo:gd, png:gd,
+# png:gd:gd, jpg, jpg:cairo, jpg:cairo:gd, jpg:gd, jpg:gd:gd, gif, gif:cairo,
+# gif:cairo:gd, gif:gd, gif:gd:gd, svg, png:gd, png:gd:gd, png:cairo,
# png:cairo:gd, png:cairo:cairo, png:cairo:gdiplus, png:gdiplus and
# png:gdiplus:gdiplus.
# The default value is: png.
@@ -2349,6 +2351,11 @@ DIAFILE_DIRS =
PLANTUML_JAR_PATH =
+# When using plantuml, the PLANTUML_CFG_FILE tag can be used to specify a
+# configuration file for plantuml.
+
+PLANTUML_CFG_FILE =
+
# When using plantuml, the specified paths are searched for files specified by
# the !include statement in a plantuml block.
diff --git a/README.md b/README.md
index 77809d8..8169f79 100644
--- a/README.md
+++ b/README.md
@@ -1,28 +1,70 @@
-PUGL
+Pugl
====
-Pugl is a minimal portable API for GUIs which supports embedding and is
-suitable for use in plugins. It works on X11, Mac OS X, and Windows. GUIs can
-be drawn with OpenGL or Cairo.
+Pugl (PlUgin Graphics Library) is a minimal portable API for GUIs which is
+suitable for use in plugins. It works on X11, MacOS, and Windows, and
+optionally supports OpenGL and Cairo graphics contexts.
-Pugl is vaguely similar to GLUT, but with some significant distinctions:
+Pugl is vaguely similar to libraries like GLUT and GLFW, but with some
+distinguishing features:
- * Minimal in scope, providing only what is necessary to draw and receive
- keyboard and mouse input.
+ * Minimal in scope, providing only a thin interface to isolate
+ platform-specific details from applications.
- * No reliance on static data whatsoever, so the API can be used in plugins or
- multiple independent parts of a program.
+ * Zero dependencies, aside from standard system libraries.
- * Single implementation, which is small, liberally licensed Free / Open Source
- Software, and suitable for direct inclusion in programs if avoiding a
- library dependency is desired.
+ * Support for embedding in native windows, for example as a plugin or
+ component within a larger application that is not based on Pugl.
- * Support for embedding in other windows, so Pugl code can draw to a widget
- inside a larger GUI.
+ * Simple and extensible event-based API that makes dispatching in application
+ or toolkit code easy with minimal boilerplate.
- * More complete support for keyboard input, including additional "special"
- keys, modifiers, and support for detecting individual modifier key presses.
+ * Suitable not only for continuously rendering applications like games, but
+ also event-driven applications that only draw when necessary.
-For more information, see <http://drobilla.net/software/pugl>.
+ * Explicit context and no static data whatsoever, so that several instances
+ can be used within a single program at once.
+
+ * Small, liberally licensed Free Software implementation that is suitable for
+ vendoring and/or static linking. Pugl can be installed as a library, or
+ used by simply copying the headers into a project.
+
+Stability
+---------
+
+Pugl is currently being developed towards a long-term stable API. For the time
+being, however, the API may break occasionally. Please report any relevant
+feedback, or file feature requests, so that we can ensure that the released API
+is stable for as long as possible.
+
+Distribution
+------------
+
+Pugl is designed for flexible distribution. It can be used by simply including
+the source code, or installed and linked against as a static or shared library.
+Static linking or direct inclusion is a good idea for plugins that will be
+distributed as binaries to avoid dependency problems.
+
+If you are including the code, please use a submodule so that suitable changes
+can be merged upstream to keep fragmentation to a minimum.
+
+When installed, Pugl is split into different libraries to keep dependencies
+minimal. The core implementation is separate from graphics backends:
+
+ * The core implementation for a particular platform is in one library:
+ `pugl_x11`, `pugl_mac`, or `pugl_win`. This does not depend on backends or
+ their dependencies.
+
+ * Backends for platforms are in separate libraries, which depend on the core:
+ `pugl_x11_cairo`, `pugl_x11_gl`, `pugl_mac_cairo`, and so on.
+
+Applications must link against the core and at least one backend. Normally,
+this can be achieved by simply depending on the package `pugl-gl-0` or
+`pugl-cairo-0`. Though it is possible to compile everything into a monolithic
+library, distributions should retain this separation so that GL applications
+don't depend on Cairo and its dependencies, or vice-versa.
+
+Distributions are encouraged to include static libraries if possible so that
+developers can build portable plugin binaries.
-- David Robillard <d@drobilla.net>
diff --git a/pugl.pc.in b/pugl.pc.in
index d3606cd..531cd54 100644
--- a/pugl.pc.in
+++ b/pugl.pc.in
@@ -3,8 +3,9 @@ exec_prefix=@EXEC_PREFIX@
libdir=@LIBDIR@
includedir=@INCLUDEDIR@
-Name: Pugl
+Name: @NAME@
+Description: @DESCRIPTION@
Version: @PUGL_VERSION@
-Description: Lightweight portable OpenGL API
-Libs: -L${libdir} -l@LIB_PUGL@
+Requires: @REQUIRES@
+Libs: -L${libdir} @LIBS@
Cflags: -I${includedir}/pugl-@PUGL_MAJOR_VERSION@
diff --git a/pugl/cairo_gl.h b/pugl/cairo_gl.h
deleted file mode 100644
index fb4cb2a..0000000
--- a/pugl/cairo_gl.h
+++ /dev/null
@@ -1,106 +0,0 @@
-/*
- Copyright 2016 David Robillard <http://drobilla.net>
-
- Permission to use, copy, modify, and/or distribute this software for any
- purpose with or without fee is hereby granted, provided that the above
- copyright notice and this permission notice appear in all copies.
-
- THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
- WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
- MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
- ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
- WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
- ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
- OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
-*/
-
-#if defined(PUGL_HAVE_GL) && defined(PUGL_HAVE_CAIRO)
-
-#include <cairo/cairo.h>
-#include <stdint.h>
-
-#include "pugl/gl.h"
-
-typedef struct {
- unsigned texture_id;
- uint8_t* buffer;
-} PuglCairoGL;
-
-static cairo_surface_t*
-pugl_cairo_gl_create(PuglCairoGL* ctx, int width, int height, int bpp)
-{
- free(ctx->buffer);
- ctx->buffer = (uint8_t*)calloc(bpp * width * height, sizeof(uint8_t));
- if (!ctx->buffer) {
- fprintf(stderr, "failed to allocate surface buffer\n");
- return NULL;
- }
-
- return cairo_image_surface_create_for_data(
- ctx->buffer, CAIRO_FORMAT_ARGB32, width, height, bpp * width);
-}
-
-static void
-pugl_cairo_gl_free(PuglCairoGL* ctx)
-{
- free(ctx->buffer);
- ctx->buffer = NULL;
-}
-
-static void
-pugl_cairo_gl_configure(PuglCairoGL* ctx,
- int width __attribute__((unused)), int height __attribute__((unused)))
-{
- glDisable(GL_DEPTH_TEST);
- glEnable(GL_BLEND);
- glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
- glEnable(GL_TEXTURE_RECTANGLE_ARB);
-
- glMatrixMode(GL_PROJECTION);
- glLoadIdentity();
- glOrtho(-1.0f, 1.0f, -1.0f, 1.0f, -1.0f, 1.0f);
-
- glClear(GL_COLOR_BUFFER_BIT);
-
- glDeleteTextures(1, &ctx->texture_id);
- glGenTextures(1, &ctx->texture_id);
- glBindTexture(GL_TEXTURE_RECTANGLE_ARB, ctx->texture_id);
- glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_DECAL);
-}
-
-static void
-pugl_cairo_gl_draw(PuglCairoGL* ctx, int width, int height)
-{
- glMatrixMode(GL_MODELVIEW);
- glLoadIdentity();
- glViewport(0, 0, width, height);
- glClear(GL_COLOR_BUFFER_BIT);
-
- glPushMatrix();
- glEnable(GL_TEXTURE_RECTANGLE_ARB);
- glEnable(GL_TEXTURE_2D);
-
- glTexImage2D(GL_TEXTURE_RECTANGLE_ARB, 0, GL_RGBA8,
- width, height, 0,
- GL_BGRA, GL_UNSIGNED_BYTE, ctx->buffer);
-
- glBegin(GL_QUADS);
- glTexCoord2f(0.0f, (GLfloat)height);
- glVertex2f(-1.0f, -1.0f);
-
- glTexCoord2f((GLfloat)width, (GLfloat)height);
- glVertex2f(1.0f, -1.0f);
-
- glTexCoord2f((GLfloat)width, 0.0f);
- glVertex2f(1.0f, 1.0f);
-
- glTexCoord2f(0.0f, 0.0f);
- glVertex2f(-1.0f, 1.0f);
- glEnd();
-
- glDisable(GL_TEXTURE_2D);
- glDisable(GL_TEXTURE_RECTANGLE_ARB);
- glPopMatrix();
-}
-
-#endif
diff --git a/pugl/pugl_internal.h b/pugl/detail/implementation.c
index 5976cd7..7eeba01 100644
--- a/pugl/pugl_internal.h
+++ b/pugl/detail/implementation.c
@@ -1,5 +1,5 @@
/*
- Copyright 2012-2016 David Robillard <http://drobilla.net>
+ Copyright 2012-2019 David Robillard <http://drobilla.net>
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
@@ -15,56 +15,27 @@
*/
/**
- @file pugl_internal.h Private platform-independent definitions.
-
- Note this file contains function definitions, so it must be compiled into
- the final binary exactly once. Each platform specific implementation file
- including it once should achieve this.
-
- If you are copying the pugl code into your source tree, the following
- symbols can be defined to tweak pugl behaviour:
-
- PUGL_HAVE_CAIRO: Include Cairo support code.
- PUGL_HAVE_GL: Include OpenGL support code.
+ @file implementation.c Platform-independent implementation.
*/
-#include <stdlib.h>
-#include <string.h>
-
+#include "pugl/detail/implementation.h"
#include "pugl/pugl.h"
-typedef struct PuglInternalsImpl PuglInternals;
-
-struct PuglViewImpl {
- PuglHandle handle;
- PuglEventFunc eventFunc;
-
- PuglInternals* impl;
- char* selection;
-
- char* windowClass;
- PuglNativeWindow parent;
- PuglContextType ctx_type;
- uintptr_t transient_parent;
-
- int width;
- int height;
- int min_width;
- int min_height;
- int min_aspect_x;
- int min_aspect_y;
- int max_aspect_x;
- int max_aspect_y;
- bool ignoreKeyRepeat;
- bool redisplay;
- bool resizable;
- bool visible;
-};
+#include <stdbool.h>
+#include <stdlib.h>
+#include <string.h>
-PuglInternals* puglInitInternals(void);
+static PuglHints
+puglDefaultHints(void)
+{
+ static const PuglHints hints = {
+ 2, 0, 4, 4, 4, 4, 24, 8, 0, true, true, false, false
+ };
+ return hints;
+}
PuglView*
-puglInit(int* pargc __attribute__((unused)), char** argv __attribute__((unused)))
+puglInit(int* PUGL_UNUSED(pargc), char** PUGL_UNUSED(argv))
{
PuglView* view = (PuglView*)calloc(1, sizeof(PuglView));
if (!view) {
@@ -73,18 +44,66 @@ puglInit(int* pargc __attribute__((unused)), char** argv __attribute__((unused))
PuglInternals* impl = puglInitInternals();
if (!impl) {
+ free(view);
return NULL;
}
- view->ctx_type = PUGL_GL;
- view->impl = impl;
- view->width = 640;
- view->height = 480;
+ view->hints = puglDefaultHints();
+ view->impl = impl;
+ view->width = 640;
+ view->height = 480;
+ view->start_time = puglGetTime(view);
return view;
}
void
+puglInitWindowHint(PuglView* view, PuglWindowHint hint, int value)
+{
+ switch (hint) {
+ case PUGL_USE_COMPAT_PROFILE:
+ view->hints.use_compat_profile = value;
+ break;
+ case PUGL_CONTEXT_VERSION_MAJOR:
+ view->hints.context_version_major = value;
+ break;
+ case PUGL_CONTEXT_VERSION_MINOR:
+ view->hints.context_version_minor = value;
+ break;
+ case PUGL_RED_BITS:
+ view->hints.red_bits = value;
+ break;
+ case PUGL_GREEN_BITS:
+ view->hints.green_bits = value;
+ break;
+ case PUGL_BLUE_BITS:
+ view->hints.blue_bits = value;
+ break;
+ case PUGL_ALPHA_BITS:
+ view->hints.alpha_bits = value;
+ break;
+ case PUGL_DEPTH_BITS:
+ view->hints.depth_bits = value;
+ break;
+ case PUGL_STENCIL_BITS:
+ view->hints.stencil_bits = value;
+ break;
+ case PUGL_SAMPLES:
+ view->hints.samples = value;
+ break;
+ case PUGL_DOUBLE_BUFFER:
+ view->hints.double_buffer = value;
+ break;
+ case PUGL_RESIZABLE:
+ view->hints.resizable = value;
+ break;
+ case PUGL_IGNORE_KEY_REPEAT:
+ view->hints.ignoreKeyRepeat = value;
+ break;
+ }
+}
+
+void
puglInitWindowSize(PuglView* view, int width, int height)
{
view->width = width;
@@ -130,7 +149,7 @@ puglInitWindowParent(PuglView* view, PuglNativeWindow parent)
void
puglInitResizable(PuglView* view, bool resizable)
{
- view->resizable = resizable;
+ view->hints.resizable = resizable;
}
void
@@ -139,10 +158,11 @@ puglInitTransientFor(PuglView* view, uintptr_t parent)
view->transient_parent = parent;
}
-void
-puglInitContextType(PuglView* view, PuglContextType type)
+int
+puglInitBackend(PuglView* view, const PuglBackend* backend)
{
- view->ctx_type = type;
+ view->backend = backend;
+ return 0;
}
void
@@ -170,10 +190,28 @@ puglGetSize(PuglView* view, int* width, int* height)
*height = view->height;
}
+void*
+puglGetContext(PuglView* view)
+{
+ return view->backend->getContext(view);
+}
+
+void
+puglEnterContext(PuglView* view, bool drawing)
+{
+ view->backend->enter(view, drawing);
+}
+
+void
+puglLeaveContext(PuglView* view, bool drawing)
+{
+ view->backend->leave(view, drawing);
+}
+
void
puglIgnoreKeyRepeat(PuglView* view, bool ignore)
{
- view->ignoreKeyRepeat = ignore;
+ puglInitWindowHint(view, PUGL_IGNORE_KEY_REPEAT, ignore);
}
void
@@ -183,10 +221,10 @@ puglSetEventFunc(PuglView* view, PuglEventFunc eventFunc)
}
/** Return the code point for buf, or the replacement character on error. */
-static uint32_t
+uint32_t
puglDecodeUTF8(const uint8_t* buf)
{
-#define FAIL_IF(cond) { if (cond) return 0xFFFD; }
+#define FAIL_IF(cond) do { if (cond) return 0xFFFD; } while (0)
// http://en.wikipedia.org/wiki/UTF-8
@@ -196,12 +234,12 @@ puglDecodeUTF8(const uint8_t* buf)
return 0xFFFD;
} else if (buf[0] < 0xE0) {
FAIL_IF((buf[1] & 0xC0) != 0x80);
- return (buf[0] << 6) + buf[1] - 0x3080;
+ return (buf[0] << 6) + buf[1] - 0x3080u;
} else if (buf[0] < 0xF0) {
FAIL_IF((buf[1] & 0xC0) != 0x80);
FAIL_IF(buf[0] == 0xE0 && buf[1] < 0xA0);
FAIL_IF((buf[2] & 0xC0) != 0x80);
- return (buf[0] << 12) + (buf[1] << 6) + buf[2] - 0xE2080;
+ return (buf[0] << 12) + (buf[1] << 6) + buf[2] - 0xE2080u;
} else if (buf[0] < 0xF5) {
FAIL_IF((buf[1] & 0xC0) != 0x80);
FAIL_IF(buf[0] == 0xF0 && buf[1] < 0x90);
@@ -211,27 +249,27 @@ puglDecodeUTF8(const uint8_t* buf)
return ((buf[0] << 18) +
(buf[1] << 12) +
(buf[2] << 6) +
- buf[3] - 0x3C82080);
+ buf[3] - 0x3C82080u);
}
return 0xFFFD;
}
-static void
+void
puglDispatchEvent(PuglView* view, const PuglEvent* event)
{
switch (event->type) {
case PUGL_NOTHING:
break;
case PUGL_CONFIGURE:
- view->width = event->configure.width;
- view->height = event->configure.height;
- puglEnterContext(view);
+ view->width = (int)event->configure.width;
+ view->height = (int)event->configure.height;
+ puglEnterContext(view, false);
view->eventFunc(view, event);
puglLeaveContext(view, false);
break;
case PUGL_EXPOSE:
if (event->expose.count == 0) {
- puglEnterContext(view);
+ puglEnterContext(view, true);
view->eventFunc(view, event);
puglLeaveContext(view, true);
}
@@ -240,34 +278,3 @@ puglDispatchEvent(PuglView* view, const PuglEvent* event)
view->eventFunc(view, event);
}
}
-
-static void
-puglClearSelection(PuglView* view)
-{
- if(view->selection) {
- free(view->selection);
- view->selection = NULL;
- }
-}
-
-static void
-puglSetSelection(PuglView* view, const char *selection, size_t len)
-{
- puglClearSelection(view);
-
- if(selection) {
- view->selection = (char*)malloc(len + 1);
- if(view->selection) {
- memcpy(view->selection, selection, len);
- view->selection[len] = 0;
- }
- }
-}
-
-static const char*
-puglGetSelection(PuglView* view, size_t* len)
-{
- if(len)
- *len = view->selection ? strlen(view->selection) : 0;
- return view->selection;
-}
diff --git a/pugl/detail/implementation.h b/pugl/detail/implementation.h
new file mode 100644
index 0000000..50f54f5
--- /dev/null
+++ b/pugl/detail/implementation.h
@@ -0,0 +1,46 @@
+/*
+ Copyright 2012-2019 David Robillard <http://drobilla.net>
+
+ Permission to use, copy, modify, and/or distribute this software for any
+ purpose with or without fee is hereby granted, provided that the above
+ copyright notice and this permission notice appear in all copies.
+
+ THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+*/
+
+/**
+ @file implementation.h Shared declarations for implementation.
+*/
+
+#ifndef PUGL_DETAIL_IMPLEMENTATION_H
+#define PUGL_DETAIL_IMPLEMENTATION_H
+
+#include "pugl/detail/types.h"
+#include "pugl/pugl.h"
+
+#include <stdint.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/** Allocate and initialise internals (implemented once per platform) */
+PuglInternals* puglInitInternals(void);
+
+/** Return the Unicode code point for `buf` or the replacement character. */
+uint32_t puglDecodeUTF8(const uint8_t* buf);
+
+/** Dispatch `event` to `view`, optimising configure/expose if possible. */
+void puglDispatchEvent(PuglView* view, const PuglEvent* event);
+
+#ifdef __cplusplus
+} /* extern "C" */
+#endif
+
+#endif // PUGL_DETAIL_IMPLEMENTATION_H
diff --git a/pugl/detail/mac.h b/pugl/detail/mac.h
new file mode 100644
index 0000000..9a5997d
--- /dev/null
+++ b/pugl/detail/mac.h
@@ -0,0 +1,59 @@
+/*
+ Copyright 2012-2019 David Robillard <http://drobilla.net>
+ Copyright 2017 Hanspeter Portner <dev@open-music-kontrollers.ch>
+
+ Permission to use, copy, modify, and/or distribute this software for any
+ purpose with or without fee is hereby granted, provided that the above
+ copyright notice and this permission notice appear in all copies.
+
+ THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+*/
+
+/**
+ @file win.h Shared definitions for MacOS implementation.
+*/
+
+#include "pugl/pugl.h"
+
+#import <Cocoa/Cocoa.h>
+
+#include <stdint.h>
+
+@interface PuglWrapperView : NSView<NSTextInputClient>
+{
+@public
+ PuglView* puglview;
+ NSTrackingArea* trackingArea;
+ NSMutableAttributedString* markedText;
+ NSTimer* timer;
+ NSTimer* urgentTimer;
+}
+
+- (void) dispatchConfigure:(NSRect)bounds;
+
+@end
+
+@interface PuglWindow : NSWindow
+{
+@public
+ PuglView* puglview;
+}
+
+- (void) setPuglview:(PuglView*)view;
+
+@end
+
+struct PuglInternalsImpl {
+ NSApplication* app;
+ PuglWrapperView* wrapperView;
+ NSView* drawView;
+ id window;
+ NSEvent* nextEvent;
+ uint32_t mods;
+};
diff --git a/pugl/detail/mac.m b/pugl/detail/mac.m
new file mode 100644
index 0000000..b1bf4fd
--- /dev/null
+++ b/pugl/detail/mac.m
@@ -0,0 +1,860 @@
+/*
+ Copyright 2012-2019 David Robillard <http://drobilla.net>
+ Copyright 2017 Hanspeter Portner <dev@open-music-kontrollers.ch>
+
+ Permission to use, copy, modify, and/or distribute this software for any
+ purpose with or without fee is hereby granted, provided that the above
+ copyright notice and this permission notice appear in all copies.
+
+ THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+*/
+
+/**
+ @file mac.m MacOS implementation.
+*/
+
+#define GL_SILENCE_DEPRECATION 1
+
+#include "pugl/detail/implementation.h"
+#include "pugl/detail/mac.h"
+#include "pugl/pugl.h"
+
+#import <Cocoa/Cocoa.h>
+
+#include <mach/mach_time.h>
+
+#include <stdlib.h>
+
+@implementation PuglWindow
+
+- (id) initWithContentRect:(NSRect)contentRect
+ styleMask:(NSWindowStyleMask)aStyle
+ backing:(NSBackingStoreType)bufferingType
+ defer:(BOOL)flag
+{
+ (void)flag;
+
+ NSWindow* result = [super initWithContentRect:contentRect
+ styleMask:aStyle
+ backing:bufferingType
+ defer:NO];
+
+ [result setAcceptsMouseMovedEvents:YES];
+ return (PuglWindow*)result;
+}
+
+- (void)setPuglview:(PuglView*)view
+{
+ puglview = view;
+ [self setContentSize:NSMakeSize(view->width, view->height)];
+}
+
+- (BOOL) canBecomeKeyWindow
+{
+ return YES;
+}
+
+- (BOOL) canBecomeMainWindow
+{
+ return YES;
+}
+
+@end
+
+@implementation PuglWrapperView
+
+- (void) resizeWithOldSuperviewSize:(NSSize)oldSize
+{
+ [super resizeWithOldSuperviewSize:oldSize];
+
+ const NSRect bounds = [self bounds];
+ puglview->backend->resize(puglview, bounds.size.width, bounds.size.height);
+}
+
+- (void) drawRect:(NSRect)rect
+{
+ const PuglEventExpose ev = {
+ PUGL_EXPOSE,
+ 0,
+ rect.origin.x,
+ rect.origin.y,
+ rect.size.width,
+ rect.size.height,
+ 0
+ };
+
+ puglDispatchEvent(puglview, (const PuglEvent*)&ev);
+}
+
+- (BOOL) isFlipped
+{
+ return YES;
+}
+
+- (BOOL) acceptsFirstResponder
+{
+ return YES;
+}
+
+- (void) dispatchConfigure:(NSRect)bounds
+{
+ const PuglEventConfigure ev = {
+ PUGL_CONFIGURE,
+ 0,
+ bounds.origin.x,
+ bounds.origin.y,
+ bounds.size.width,
+ bounds.size.height,
+ };
+
+ puglDispatchEvent(puglview, (const PuglEvent*)&ev);
+}
+
+static uint32_t
+getModifiers(const NSEvent* const ev)
+{
+ const NSEventModifierFlags modifierFlags = [ev modifierFlags];
+
+ return (((modifierFlags & NSShiftKeyMask) ? PUGL_MOD_SHIFT : 0) |
+ ((modifierFlags & NSControlKeyMask) ? PUGL_MOD_CTRL : 0) |
+ ((modifierFlags & NSAlternateKeyMask) ? PUGL_MOD_ALT : 0) |
+ ((modifierFlags & NSCommandKeyMask) ? PUGL_MOD_SUPER : 0));
+}
+
+static PuglKey
+keySymToSpecial(const NSEvent* const ev)
+{
+ NSString* chars = [ev charactersIgnoringModifiers];
+ if ([chars length] == 1) {
+ switch ([chars characterAtIndex:0]) {
+ case NSF1FunctionKey: return PUGL_KEY_F1;
+ case NSF2FunctionKey: return PUGL_KEY_F2;
+ case NSF3FunctionKey: return PUGL_KEY_F3;
+ case NSF4FunctionKey: return PUGL_KEY_F4;
+ case NSF5FunctionKey: return PUGL_KEY_F5;
+ case NSF6FunctionKey: return PUGL_KEY_F6;
+ case NSF7FunctionKey: return PUGL_KEY_F7;
+ case NSF8FunctionKey: return PUGL_KEY_F8;
+ case NSF9FunctionKey: return PUGL_KEY_F9;
+ case NSF10FunctionKey: return PUGL_KEY_F10;
+ case NSF11FunctionKey: return PUGL_KEY_F11;
+ case NSF12FunctionKey: return PUGL_KEY_F12;
+ case NSDeleteCharacter: return PUGL_KEY_BACKSPACE;
+ case NSDeleteFunctionKey: return PUGL_KEY_DELETE;
+ case NSLeftArrowFunctionKey: return PUGL_KEY_LEFT;
+ case NSUpArrowFunctionKey: return PUGL_KEY_UP;
+ case NSRightArrowFunctionKey: return PUGL_KEY_RIGHT;
+ case NSDownArrowFunctionKey: return PUGL_KEY_DOWN;
+ case NSPageUpFunctionKey: return PUGL_KEY_PAGE_UP;
+ case NSPageDownFunctionKey: return PUGL_KEY_PAGE_DOWN;
+ case NSHomeFunctionKey: return PUGL_KEY_HOME;
+ case NSEndFunctionKey: return PUGL_KEY_END;
+ case NSInsertFunctionKey: return PUGL_KEY_INSERT;
+ case NSMenuFunctionKey: return PUGL_KEY_MENU;
+ case NSScrollLockFunctionKey: return PUGL_KEY_SCROLL_LOCK;
+ case NSClearLineFunctionKey: return PUGL_KEY_NUM_LOCK;
+ case NSPrintScreenFunctionKey: return PUGL_KEY_PRINT_SCREEN;
+ case NSPauseFunctionKey: return PUGL_KEY_PAUSE;
+ }
+ // SHIFT, CTRL, ALT, and SUPER are handled in [flagsChanged]
+ }
+ return (PuglKey)0;
+}
+
+- (void) updateTrackingAreas
+{
+ if (trackingArea != nil) {
+ [self removeTrackingArea:trackingArea];
+ [trackingArea release];
+ }
+
+ const int opts = (NSTrackingMouseEnteredAndExited |
+ NSTrackingMouseMoved |
+ NSTrackingActiveAlways);
+ trackingArea = [[NSTrackingArea alloc] initWithRect:[self bounds]
+ options:opts
+ owner:self
+ userInfo:nil];
+ [self addTrackingArea:trackingArea];
+ [super updateTrackingAreas];
+}
+
+- (NSPoint) eventLocation:(NSEvent*)event
+{
+ return [self convertPoint:[event locationInWindow] fromView:nil];
+}
+
+static void
+handleCrossing(PuglWrapperView* view, NSEvent* event, const PuglEventType type)
+{
+ const NSPoint wloc = [view eventLocation:event];
+ const NSPoint rloc = [NSEvent mouseLocation];
+ const PuglEventCrossing ev = {
+ type,
+ 0,
+ [event timestamp],
+ wloc.x,
+ wloc.y,
+ rloc.x,
+ [[NSScreen mainScreen] frame].size.height - rloc.y,
+ getModifiers(event),
+ PUGL_CROSSING_NORMAL
+ };
+ puglDispatchEvent(view->puglview, (const PuglEvent*)&ev);
+}
+
+- (void) mouseEntered:(NSEvent*)event
+{
+ handleCrossing(self, event, PUGL_ENTER_NOTIFY);
+}
+
+- (void) mouseExited:(NSEvent*)event
+{
+ handleCrossing(self, event, PUGL_LEAVE_NOTIFY);
+}
+
+- (void) mouseMoved:(NSEvent*)event
+{
+ const NSPoint wloc = [self eventLocation:event];
+ const NSPoint rloc = [NSEvent mouseLocation];
+ const PuglEventMotion ev = {
+ PUGL_MOTION_NOTIFY,
+ 0,
+ [event timestamp],
+ wloc.x,
+ wloc.y,
+ rloc.x,
+ [[NSScreen mainScreen] frame].size.height - rloc.y,
+ getModifiers(event),
+ 0,
+ 1
+ };
+ puglDispatchEvent(puglview, (const PuglEvent*)&ev);
+}
+
+- (void) mouseDragged:(NSEvent*)event
+{
+ [self mouseMoved: event];
+}
+
+- (void) rightMouseDragged:(NSEvent*)event
+{
+ [self mouseMoved: event];
+}
+
+- (void) otherMouseDragged:(NSEvent*)event
+{
+ [self mouseMoved: event];
+}
+
+- (void) mouseDown:(NSEvent*)event
+{
+ const NSPoint wloc = [self eventLocation:event];
+ const NSPoint rloc = [NSEvent mouseLocation];
+ const PuglEventButton ev = {
+ PUGL_BUTTON_PRESS,
+ 0,
+ [event timestamp],
+ wloc.x,
+ wloc.y,
+ rloc.x,
+ [[NSScreen mainScreen] frame].size.height - rloc.y,
+ getModifiers(event),
+ (uint32_t)[event buttonNumber] + 1
+ };
+ puglDispatchEvent(puglview, (const PuglEvent*)&ev);
+}
+
+- (void) mouseUp:(NSEvent*)event
+{
+ const NSPoint wloc = [self eventLocation:event];
+ const NSPoint rloc = [NSEvent mouseLocation];
+ const PuglEventButton ev = {
+ PUGL_BUTTON_RELEASE,
+ 0,
+ [event timestamp],
+ wloc.x,
+ wloc.y,
+ rloc.x,
+ [[NSScreen mainScreen] frame].size.height - rloc.y,
+ getModifiers(event),
+ (uint32_t)[event buttonNumber] + 1
+ };
+ puglDispatchEvent(puglview, (const PuglEvent*)&ev);
+}
+
+- (void) rightMouseDown:(NSEvent*)event
+{
+ [self mouseDown: event];
+}
+
+- (void) rightMouseUp:(NSEvent*)event
+{
+ [self mouseUp: event];
+}
+
+- (void) otherMouseDown:(NSEvent*)event
+{
+ [self mouseDown: event];
+}
+
+- (void) otherMouseUp:(NSEvent*)event
+{
+ [self mouseUp: event];
+}
+
+- (void) scrollWheel:(NSEvent*)event
+{
+ const NSPoint wloc = [self eventLocation:event];
+ const NSPoint rloc = [NSEvent mouseLocation];
+ const PuglEventScroll ev = {
+ PUGL_SCROLL,
+ 0,
+ [event timestamp],
+ wloc.x,
+ wloc.y,
+ rloc.x,
+ [[NSScreen mainScreen] frame].size.height - rloc.y,
+ getModifiers(event),
+ [event scrollingDeltaX],
+ [event scrollingDeltaY]
+ };
+ puglDispatchEvent(puglview, (const PuglEvent*)&ev);
+}
+
+- (void) keyDown:(NSEvent*)event
+{
+ if (puglview->hints.ignoreKeyRepeat && [event isARepeat]) {
+ return;
+ }
+
+ const NSPoint wloc = [self eventLocation:event];
+ const NSPoint rloc = [NSEvent mouseLocation];
+ const PuglKey spec = keySymToSpecial(event);
+ const NSString* chars = [event charactersIgnoringModifiers];
+ const char* str = [[chars lowercaseString] UTF8String];
+ const uint32_t code = (
+ spec ? spec : puglDecodeUTF8((const uint8_t*)str));
+
+ const PuglEventKey ev = {
+ PUGL_KEY_PRESS,
+ 0,
+ [event timestamp],
+ wloc.x,
+ wloc.y,
+ rloc.x,
+ [[NSScreen mainScreen] frame].size.height - rloc.y,
+ getModifiers(event),
+ [event keyCode],
+ (code != 0xFFFD) ? code : 0
+ };
+
+ puglDispatchEvent(puglview, (const PuglEvent*)&ev);
+
+ if (!spec) {
+ [self interpretKeyEvents:@[event]];
+ }
+}
+
+- (void) keyUp:(NSEvent*)event
+{
+ const NSPoint wloc = [self eventLocation:event];
+ const NSPoint rloc = [NSEvent mouseLocation];
+ const PuglKey spec = keySymToSpecial(event);
+ const NSString* chars = [event charactersIgnoringModifiers];
+ const char* str = [[chars lowercaseString] UTF8String];
+ const uint32_t code =
+ (spec ? spec : puglDecodeUTF8((const uint8_t*)str));
+
+ const PuglEventKey ev = {
+ PUGL_KEY_RELEASE,
+ 0,
+ [event timestamp],
+ wloc.x,
+ wloc.y,
+ rloc.x,
+ [[NSScreen mainScreen] frame].size.height - rloc.y,
+ getModifiers(event),
+ [event keyCode],
+ (code != 0xFFFD) ? code : 0
+ };
+ puglDispatchEvent(puglview, (const PuglEvent*)&ev);
+}
+
+- (BOOL) hasMarkedText
+{
+ return [markedText length] > 0;
+}
+
+- (NSRange) markedRange
+{
+ return (([markedText length] > 0)
+ ? NSMakeRange(0, [markedText length] - 1)
+ : NSMakeRange(NSNotFound, 0));
+}
+
+- (NSRange) selectedRange
+{
+ return NSMakeRange(NSNotFound, 0);
+}
+
+- (void)setMarkedText:(id)string
+ selectedRange:(NSRange)selected
+ replacementRange:(NSRange)replacement
+{
+ (void)selected;
+ (void)replacement;
+ [markedText release];
+ markedText = (
+ [string isKindOfClass:[NSAttributedString class]]
+ ? [[NSMutableAttributedString alloc] initWithAttributedString:string]
+ : [[NSMutableAttributedString alloc] initWithString:string]);
+}
+
+- (void) unmarkText
+{
+ [[markedText mutableString] setString:@""];
+}
+
+- (NSArray*) validAttributesForMarkedText
+{
+ return @[];
+}
+
+- (NSAttributedString*)
+ attributedSubstringForProposedRange:(NSRange)range
+ actualRange:(NSRangePointer)actual
+{
+ (void)range;
+ (void)actual;
+ return nil;
+}
+
+- (NSUInteger) characterIndexForPoint:(NSPoint)point
+{
+ (void)point;
+ return 0;
+}
+
+- (NSRect) firstRectForCharacterRange:(NSRange)range
+ actualRange:(NSRangePointer)actual
+{
+ (void)range;
+ (void)actual;
+
+ const NSRect frame = [(id)puglview bounds];
+ return NSMakeRect(frame.origin.x, frame.origin.y, 0.0, 0.0);
+}
+
+- (void) doCommandBySelector:(SEL)selector
+{
+ (void)selector;
+}
+
+- (void) insertText:(id)string
+ replacementRange:(NSRange)replacement
+{
+ (void)replacement;
+
+ NSEvent* const event = [NSApp currentEvent];
+ NSString* const characters =
+ ([string isKindOfClass:[NSAttributedString class]]
+ ? [string string]
+ : (NSString*)string);
+
+ const NSPoint wloc = [self eventLocation:event];
+ const NSPoint rloc = [NSEvent mouseLocation];
+ for (size_t i = 0; i < [characters length]; ++i) {
+ const uint32_t code = [characters characterAtIndex:i];
+ char utf8[8] = {0};
+ NSUInteger len = 0;
+
+ [characters getBytes:utf8
+ maxLength:sizeof(utf8)
+ usedLength:&len
+ encoding:NSUTF8StringEncoding
+ options:0
+ range:NSMakeRange(i, i + 1)
+ remainingRange:nil];
+
+ PuglEventText ev = { PUGL_TEXT,
+ 0,
+ [event timestamp],
+ wloc.x,
+ wloc.y,
+ rloc.x,
+ [[NSScreen mainScreen] frame].size.height - rloc.y,
+ getModifiers(event),
+ [event keyCode],
+ code,
+ { 0, 0, 0, 0, 0, 0, 0, 0 } };
+
+ memcpy(ev.string, utf8, len);
+ puglDispatchEvent(puglview, (const PuglEvent*)&ev);
+ }
+}
+
+- (void) flagsChanged:(NSEvent*)event
+{
+ const uint32_t mods = getModifiers(event);
+ PuglEventType type = PUGL_NOTHING;
+ PuglKey special = 0;
+
+ if ((mods & PUGL_MOD_SHIFT) != (puglview->impl->mods & PUGL_MOD_SHIFT)) {
+ type = mods & PUGL_MOD_SHIFT ? PUGL_KEY_PRESS : PUGL_KEY_RELEASE;
+ special = PUGL_KEY_SHIFT;
+ } else if ((mods & PUGL_MOD_CTRL) != (puglview->impl->mods & PUGL_MOD_CTRL)) {
+ type = mods & PUGL_MOD_CTRL ? PUGL_KEY_PRESS : PUGL_KEY_RELEASE;
+ special = PUGL_KEY_CTRL;
+ } else if ((mods & PUGL_MOD_ALT) != (puglview->impl->mods & PUGL_MOD_ALT)) {
+ type = mods & PUGL_MOD_ALT ? PUGL_KEY_PRESS : PUGL_KEY_RELEASE;
+ special = PUGL_KEY_ALT;
+ } else if ((mods & PUGL_MOD_SUPER) != (puglview->impl->mods & PUGL_MOD_SUPER)) {
+ type = mods & PUGL_MOD_SUPER ? PUGL_KEY_PRESS : PUGL_KEY_RELEASE;
+ special = PUGL_KEY_SUPER;
+ }
+
+ if (special != 0) {
+ const NSPoint wloc = [self eventLocation:event];
+ const NSPoint rloc = [NSEvent mouseLocation];
+ PuglEventKey ev = {
+ type,
+ 0,
+ [event timestamp],
+ wloc.x,
+ wloc.y,
+ rloc.x,
+ [[NSScreen mainScreen] frame].size.height - rloc.y,
+ mods,
+ [event keyCode],
+ special
+ };
+ puglDispatchEvent(puglview, (const PuglEvent*)&ev);
+ }
+
+ puglview->impl->mods = mods;
+}
+
+- (BOOL) preservesContentInLiveResize
+{
+ return NO;
+}
+
+- (void) viewWillStartLiveResize
+{
+ timer = [NSTimer timerWithTimeInterval:(1 / 60.0)
+ target:self
+ selector:@selector(resizeTick)
+ userInfo:nil
+ repeats:YES];
+ [[NSRunLoop currentRunLoop] addTimer:timer
+ forMode:NSRunLoopCommonModes];
+
+ [super viewWillStartLiveResize];
+}
+
+- (void) resizeTick
+{
+ puglPostRedisplay(puglview);
+}
+
+- (void) urgentTick
+{
+ [NSApp requestUserAttention:NSInformationalRequest];
+}
+
+- (void) viewDidEndLiveResize
+{
+ [super viewDidEndLiveResize];
+ [timer invalidate];
+ timer = NULL;
+}
+
+@end
+
+@interface PuglWindowDelegate : NSObject<NSWindowDelegate>
+{
+ PuglWindow* window;
+}
+
+- (instancetype) initWithPuglWindow:(PuglWindow*)window;
+
+@end
+
+@implementation PuglWindowDelegate
+
+- (instancetype) initWithPuglWindow:(PuglWindow*)puglWindow
+{
+ if ((self = [super init])) {
+ window = puglWindow;
+ }
+
+ return self;
+}
+
+- (BOOL) windowShouldClose:(id)sender
+{
+ (void)sender;
+
+ PuglEvent ev = { 0 };
+ ev.type = PUGL_CLOSE;
+ puglDispatchEvent(window->puglview, &ev);
+ return YES;
+}
+
+- (void) windowDidBecomeKey:(NSNotification*)notification
+{
+ (void)notification;
+
+ PuglWrapperView* wrapperView = window->puglview->impl->wrapperView;
+ if (wrapperView->urgentTimer) {
+ [wrapperView->urgentTimer invalidate];
+ wrapperView->urgentTimer = NULL;
+ }
+
+ PuglEvent ev = { 0 };
+ ev.type = PUGL_FOCUS_IN;
+ ev.focus.grab = false;
+ puglDispatchEvent(window->puglview, &ev);
+}
+
+- (void) windowDidResignKey:(NSNotification*)notification
+{
+ (void)notification;
+
+ PuglEvent ev = { 0 };
+ ev.type = PUGL_FOCUS_OUT;
+ ev.focus.grab = false;
+ puglDispatchEvent(window->puglview, &ev);
+}
+
+@end
+
+PuglInternals*
+puglInitInternals(void)
+{
+ return (PuglInternals*)calloc(1, sizeof(PuglInternals));
+}
+
+static NSLayoutConstraint*
+puglConstraint(id item, NSLayoutAttribute attribute, float constant)
+{
+ return [NSLayoutConstraint
+ constraintWithItem: item
+ attribute: attribute
+ relatedBy: NSLayoutRelationGreaterThanOrEqual
+ toItem: nil
+ attribute: NSLayoutAttributeNotAnAttribute
+ multiplier: 1.0
+ constant: constant];
+}
+
+int
+puglCreateWindow(PuglView* view, const char* title)
+{
+ PuglInternals* impl = view->impl;
+
+ [NSAutoreleasePool new];
+ impl->app = [NSApplication sharedApplication];
+
+ // Create wrapper view to handle input
+ impl->wrapperView = [PuglWrapperView alloc];
+ impl->wrapperView->puglview = view;
+ impl->wrapperView->markedText = [[NSMutableAttributedString alloc] init];
+ [impl->wrapperView setAutoresizesSubviews:YES];
+ [impl->wrapperView initWithFrame:NSMakeRect(0, 0, view->width, view->height)];
+ [impl->wrapperView addConstraint:
+ puglConstraint(impl->wrapperView, NSLayoutAttributeWidth, view->min_width)];
+ [impl->wrapperView addConstraint:
+ puglConstraint(impl->wrapperView, NSLayoutAttributeHeight, view->min_height)];
+
+ // Create draw view to be rendered to
+ int st = 0;
+ if ((st = view->backend->configure(view)) ||
+ (st = view->backend->create(view))) {
+ return st;
+ }
+
+ // Add draw view to wraper view
+ [impl->wrapperView addSubview:impl->drawView];
+ [impl->wrapperView setHidden:NO];
+ [impl->drawView setHidden:NO];
+
+ if (view->parent) {
+ NSView* pview = (NSView*)view->parent;
+ [pview addSubview:impl->wrapperView];
+ [impl->drawView setHidden:NO];
+ [[impl->drawView window] makeFirstResponder:impl->wrapperView];
+ } else {
+ NSString* titleString = [[NSString alloc]
+ initWithBytes:title
+ length:strlen(title)
+ encoding:NSUTF8StringEncoding];
+ NSRect frame = NSMakeRect(0, 0, view->min_width, view->min_height);
+ unsigned style = (NSClosableWindowMask |
+ NSTitledWindowMask |
+ NSMiniaturizableWindowMask );
+ if (view->hints.resizable) {
+ style |= NSResizableWindowMask;
+ }
+
+ id window = [[[PuglWindow alloc]
+ initWithContentRect:frame
+ styleMask:style
+ backing:NSBackingStoreBuffered
+ defer:NO
+ ] retain];
+ [window setPuglview:view];
+ [window setTitle:titleString];
+ if (view->min_width || view->min_height) {
+ [window setContentMinSize:NSMakeSize(view->min_width,
+ view->min_height)];
+ }
+ impl->window = window;
+
+ ((NSWindow*)window).delegate = [[PuglWindowDelegate alloc]
+ initWithPuglWindow:window];
+
+ if (view->min_aspect_x && view->min_aspect_y) {
+ [window setContentAspectRatio:NSMakeSize(view->min_aspect_x,
+ view->min_aspect_y)];
+ }
+
+ [window setContentView:impl->wrapperView];
+ [impl->app activateIgnoringOtherApps:YES];
+ [window makeFirstResponder:impl->wrapperView];
+ [window makeKeyAndOrderFront:window];
+ }
+
+ [impl->wrapperView updateTrackingAreas];
+
+ return 0;
+}
+
+void
+puglShowWindow(PuglView* view)
+{
+ [view->impl->window setIsVisible:YES];
+ view->visible = true;
+}
+
+void
+puglHideWindow(PuglView* view)
+{
+ [view->impl->window setIsVisible:NO];
+ view->visible = false;
+}
+
+void
+puglDestroy(PuglView* view)
+{
+ view->backend->destroy(view);
+ [view->impl->wrapperView removeFromSuperview];
+ view->impl->wrapperView->puglview = NULL;
+ if (view->impl->window) {
+ [view->impl->window close];
+ }
+ [view->impl->wrapperView release];
+ if (view->impl->window) {
+ [view->impl->window release];
+ }
+ free(view->windowClass);
+ free(view->impl);
+ free(view);
+}
+
+void
+puglGrabFocus(PuglView* view)
+{
+ [view->impl->window makeKeyWindow];
+}
+
+void
+puglRequestAttention(PuglView* view)
+{
+ if (![view->impl->window isKeyWindow]) {
+ [NSApp requestUserAttention:NSInformationalRequest];
+ view->impl->wrapperView->urgentTimer =
+ [NSTimer scheduledTimerWithTimeInterval:2.0
+ target:view->impl->wrapperView
+ selector:@selector(urgentTick)
+ userInfo:nil
+ repeats:YES];
+ }
+}
+
+PuglStatus
+puglWaitForEvent(PuglView* view)
+{
+ /* Note that dequeue:NO is broken (it blocks forever even when events are
+ pending), so we work around this by dequeueing the event here and
+ storing it in view->impl->nextEvent for later processing. */
+ if (!view->impl->nextEvent) {
+ view->impl->nextEvent =
+ [view->impl->window nextEventMatchingMask:NSAnyEventMask];
+ }
+
+ return PUGL_SUCCESS;
+}
+
+PuglStatus
+puglProcessEvents(PuglView* view)
+{
+ if (view->impl->nextEvent) {
+ // Process event that was dequeued earier by puglWaitForEvent
+ [view->impl->app sendEvent: view->impl->nextEvent];
+ view->impl->nextEvent = NULL;
+ }
+
+ // Process all pending events
+ for (NSEvent* ev = NULL;
+ (ev = [view->impl->window nextEventMatchingMask:NSAnyEventMask
+ untilDate:nil
+ inMode:NSDefaultRunLoopMode
+ dequeue:YES]);) {
+ [view->impl->app sendEvent: ev];
+ }
+
+ return PUGL_SUCCESS;
+}
+
+PuglGlFunc
+puglGetProcAddress(const char *name)
+{
+ CFBundleRef framework =
+ CFBundleGetBundleWithIdentifier(CFSTR("com.apple.opengl"));
+
+ CFStringRef symbol = CFStringCreateWithCString(
+ kCFAllocatorDefault, name, kCFStringEncodingASCII);
+
+ PuglGlFunc func = (PuglGlFunc)CFBundleGetFunctionPointerForName(
+ framework, symbol);
+
+ CFRelease(symbol);
+
+ return func;
+}
+
+double
+puglGetTime(PuglView* view)
+{
+ return (mach_absolute_time() / 1e9) - view->start_time;
+}
+
+void
+puglPostRedisplay(PuglView* view)
+{
+ [view->impl->wrapperView setNeedsDisplay: YES];
+}
+
+PuglNativeWindow
+puglGetNativeWindow(PuglView* view)
+{
+ return (PuglNativeWindow)view->impl->wrapperView;
+}
diff --git a/pugl/detail/mac_cairo.m b/pugl/detail/mac_cairo.m
new file mode 100644
index 0000000..b0a8db5
--- /dev/null
+++ b/pugl/detail/mac_cairo.m
@@ -0,0 +1,166 @@
+/*
+ Copyright 2019 David Robillard <http://drobilla.net>
+
+ Permission to use, copy, modify, and/or distribute this software for any
+ purpose with or without fee is hereby granted, provided that the above
+ copyright notice and this permission notice appear in all copies.
+
+ THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+*/
+
+/**
+ @file mac_cairo.m Cairo graphics backend for MacOS.
+*/
+
+#include "pugl/detail/implementation.h"
+#include "pugl/detail/mac.h"
+#include "pugl/pugl_cairo_backend.h"
+
+#include <cairo-quartz.h>
+
+#import <Cocoa/Cocoa.h>
+
+#include <assert.h>
+
+@interface PuglCairoView : NSView
+{
+@public
+ PuglView* puglview;
+ cairo_surface_t* surface;
+ cairo_t* cr;
+}
+
+@end
+
+@implementation PuglCairoView
+
+- (id) initWithFrame:(NSRect)frame
+{
+ return (self = [super initWithFrame:frame]);
+}
+
+- (void) resizeWithOldSuperviewSize:(NSSize)oldSize
+{
+ PuglWrapperView* wrapper = (PuglWrapperView*)[self superview];
+
+ [super resizeWithOldSuperviewSize:oldSize];
+ [wrapper dispatchConfigure:[self bounds]];
+}
+
+@end
+
+static int
+puglMacCairoConfigure(PuglView* PUGL_UNUSED(view))
+{
+ return 0;
+}
+
+static int
+puglMacCairoCreate(PuglView* view)
+{
+ PuglInternals* impl = view->impl;
+ PuglCairoView* drawView = [PuglCairoView alloc];
+
+ drawView->puglview = view;
+ [drawView initWithFrame:NSMakeRect(0, 0, view->width, view->height)];
+ if (view->hints.resizable) {
+ [drawView setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable];
+ } else {
+ [drawView setAutoresizingMask:NSViewNotSizable];
+ }
+
+ impl->drawView = drawView;
+ return 0;
+}
+
+static int
+puglMacCairoDestroy(PuglView* view)
+{
+ PuglCairoView* const drawView = (PuglCairoView*)view->impl->drawView;
+
+ [drawView removeFromSuperview];
+ [drawView release];
+
+ view->impl->drawView = nil;
+ return 0;
+}
+
+static int
+puglMacCairoEnter(PuglView* view, bool drawing)
+{
+ PuglCairoView* const drawView = (PuglCairoView*)view->impl->drawView;
+ if (!drawing) {
+ return 0;
+ }
+
+ assert(!drawView->surface);
+ assert(!drawView->cr);
+
+ CGContextRef context = [[NSGraphicsContext currentContext] graphicsPort];
+
+ drawView->surface = cairo_quartz_surface_create_for_cg_context(
+ context, view->width, view->height);
+
+ drawView->cr = cairo_create(drawView->surface);
+
+ return 0;
+}
+
+static int
+puglMacCairoLeave(PuglView* view, bool drawing)
+{
+ PuglCairoView* const drawView = (PuglCairoView*)view->impl->drawView;
+ if (!drawing) {
+ return 0;
+ }
+
+ assert(drawView->surface);
+ assert(drawView->cr);
+
+ CGContextRef context = cairo_quartz_surface_get_cg_context(drawView->surface);
+
+ cairo_destroy(drawView->cr);
+ cairo_surface_destroy(drawView->surface);
+ drawView->cr = NULL;
+ drawView->surface = NULL;
+
+ CGContextFlush(context);
+
+ return 0;
+}
+
+static int
+puglMacCairoResize(PuglView* PUGL_UNUSED(view),
+ int PUGL_UNUSED(width),
+ int PUGL_UNUSED(height))
+{
+ // No need to resize, the surface is created for the drawing context
+ return 0;
+}
+
+static void*
+puglMacCairoGetContext(PuglView* view)
+{
+ return ((PuglCairoView*)view->impl->drawView)->cr;
+}
+
+const PuglBackend* puglCairoBackend(void)
+{
+ static const PuglBackend backend = {
+ puglMacCairoConfigure,
+ puglMacCairoCreate,
+ puglMacCairoDestroy,
+ puglMacCairoEnter,
+ puglMacCairoLeave,
+ puglMacCairoResize,
+ puglMacCairoGetContext
+ };
+
+ return &backend;
+}
diff --git a/pugl/detail/mac_gl.m b/pugl/detail/mac_gl.m
new file mode 100644
index 0000000..4a6e39a
--- /dev/null
+++ b/pugl/detail/mac_gl.m
@@ -0,0 +1,184 @@
+/*
+ Copyright 2019 David Robillard <http://drobilla.net>
+
+ Permission to use, copy, modify, and/or distribute this software for any
+ purpose with or without fee is hereby granted, provided that the above
+ copyright notice and this permission notice appear in all copies.
+
+ THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+*/
+
+/**
+ @file mac_gl.m OpenGL graphics backend for MacOS.
+*/
+
+#include "pugl/detail/implementation.h"
+#include "pugl/detail/mac.h"
+#include "pugl/pugl_gl_backend.h"
+
+#ifndef __MAC_10_10
+#define NSOpenGLProfileVersion4_1Core NSOpenGLProfileVersion3_2Core
+typedef NSUInteger NSEventModifierFlags;
+typedef NSUInteger NSWindowStyleMask;
+#endif
+
+@interface PuglOpenGLView : NSOpenGLView
+{
+@public
+ PuglView* puglview;
+}
+
+@end
+
+@implementation PuglOpenGLView
+
+- (id) initWithFrame:(NSRect)frame
+{
+ const int major = puglview->hints.context_version_major;
+ const int profile = ((puglview->hints.use_compat_profile || major < 3)
+ ? NSOpenGLProfileVersionLegacy
+ : puglview->hints.context_version_major >= 4
+ ? NSOpenGLProfileVersion4_1Core
+ : NSOpenGLProfileVersion3_2Core);
+
+ NSOpenGLPixelFormatAttribute pixelAttribs[16] = {
+ NSOpenGLPFADoubleBuffer,
+ NSOpenGLPFAAccelerated,
+ NSOpenGLPFAOpenGLProfile, profile,
+ NSOpenGLPFAColorSize, 32,
+ NSOpenGLPFADepthSize, 32,
+ NSOpenGLPFAMultisample, puglview->hints.samples ? 1 : 0,
+ NSOpenGLPFASampleBuffers, puglview->hints.samples ? 1 : 0,
+ NSOpenGLPFASamples, puglview->hints.samples,
+ 0};
+
+ NSOpenGLPixelFormat* pixelFormat = [
+ [NSOpenGLPixelFormat alloc] initWithAttributes:pixelAttribs];
+
+ if (pixelFormat) {
+ self = [super initWithFrame:frame pixelFormat:pixelFormat];
+ [pixelFormat release];
+ } else {
+ self = [super initWithFrame:frame];
+ }
+
+ if (self) {
+ [[self openGLContext] makeCurrentContext];
+ [self reshape];
+ }
+ return self;
+}
+
+- (void) reshape
+{
+ PuglWrapperView* wrapper = (PuglWrapperView*)[self superview];
+
+ [super reshape];
+ [wrapper dispatchConfigure:[self bounds]];
+}
+
+- (void) update
+{
+ PuglWrapperView* wrapper = (PuglWrapperView*)[self superview];
+
+ [super update];
+ [wrapper dispatchConfigure:[self bounds]];
+}
+
+@end
+
+static int
+puglMacGlConfigure(PuglView* PUGL_UNUSED(view))
+{
+ return 0;
+}
+
+static int
+puglMacGlCreate(PuglView* view)
+{
+ PuglInternals* impl = view->impl;
+ PuglOpenGLView* drawView = [PuglOpenGLView alloc];
+
+ drawView->puglview = view;
+ [drawView initWithFrame:NSMakeRect(0, 0, view->width, view->height)];
+ if (view->hints.resizable) {
+ [drawView setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable];
+ } else {
+ [drawView setAutoresizingMask:NSViewNotSizable];
+ }
+
+ impl->drawView = drawView;
+ return 0;
+}
+
+static int
+puglMacGlDestroy(PuglView* view)
+{
+ PuglOpenGLView* const drawView = (PuglOpenGLView*)view->impl->drawView;
+
+ [drawView removeFromSuperview];
+ [drawView release];
+
+ view->impl->drawView = nil;
+ return 0;
+}
+
+static int
+puglMacGlEnter(PuglView* view, bool PUGL_UNUSED(drawing))
+{
+ PuglOpenGLView* const drawView = (PuglOpenGLView*)view->impl->drawView;
+
+ [[drawView openGLContext] makeCurrentContext];
+ return 0;
+}
+
+static int
+puglMacGlLeave(PuglView* view, bool drawing)
+{
+ PuglOpenGLView* const drawView = (PuglOpenGLView*)view->impl->drawView;
+
+ if (drawing) {
+ [[drawView openGLContext] flushBuffer];
+ }
+
+ [NSOpenGLContext clearCurrentContext];
+
+ return 0;
+}
+
+static int
+puglMacGlResize(PuglView* view, int PUGL_UNUSED(width), int PUGL_UNUSED(height))
+{
+ PuglOpenGLView* const drawView = (PuglOpenGLView*)view->impl->drawView;
+
+ [drawView reshape];
+
+ return 0;
+}
+
+static void*
+puglMacGlGetContext(PuglView* PUGL_UNUSED(view))
+{
+ return NULL;
+}
+
+const PuglBackend* puglGlBackend(void)
+{
+ static const PuglBackend backend = {
+ puglMacGlConfigure,
+ puglMacGlCreate,
+ puglMacGlDestroy,
+ puglMacGlEnter,
+ puglMacGlLeave,
+ puglMacGlResize,
+ puglMacGlGetContext
+ };
+
+ return &backend;
+}
diff --git a/pugl/detail/types.h b/pugl/detail/types.h
new file mode 100644
index 0000000..60f7682
--- /dev/null
+++ b/pugl/detail/types.h
@@ -0,0 +1,107 @@
+/*
+ Copyright 2012-2019 David Robillard <http://drobilla.net>
+
+ Permission to use, copy, modify, and/or distribute this software for any
+ purpose with or without fee is hereby granted, provided that the above
+ copyright notice and this permission notice appear in all copies.
+
+ THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+*/
+
+/**
+ @file types.h Shared internal type definitions.
+*/
+
+#ifndef PUGL_DETAIL_TYPES_H
+#define PUGL_DETAIL_TYPES_H
+
+#include "pugl/pugl.h"
+
+#include <stdbool.h>
+#include <stdint.h>
+
+// Unused parameter macro to suppresses warnings and make it impossible to use
+#if defined(__cplusplus) || defined(_MSC_VER)
+# define PUGL_UNUSED(name)
+#elif defined(__GNUC__)
+# define PUGL_UNUSED(name) name##_unused __attribute__((__unused__))
+#else
+# define PUGL_UNUSED(name)
+#endif
+
+/** Platform-specific internals. */
+typedef struct PuglInternalsImpl PuglInternals;
+
+/** View hints. */
+typedef struct {
+ int context_version_major;
+ int context_version_minor;
+ int red_bits;
+ int green_bits;
+ int blue_bits;
+ int alpha_bits;
+ int depth_bits;
+ int stencil_bits;
+ int samples;
+ int double_buffer;
+ bool use_compat_profile;
+ bool resizable;
+ bool ignoreKeyRepeat;
+} PuglHints;
+
+/** Cross-platform view definition. */
+struct PuglViewImpl {
+ const PuglBackend* backend;
+ PuglInternals* impl;
+ PuglHandle handle;
+ PuglEventFunc eventFunc;
+ char* windowClass;
+ PuglNativeWindow parent;
+ double start_time;
+ uintptr_t transient_parent;
+ PuglHints hints;
+ int width;
+ int height;
+ int min_width;
+ int min_height;
+ int min_aspect_x;
+ int min_aspect_y;
+ int max_aspect_x;
+ int max_aspect_y;
+ bool visible;
+};
+
+/** Opaque surface used by graphics backend. */
+typedef void PuglSurface;
+
+/** Graphics backend interface. */
+struct PuglBackendImpl {
+ /** Get visual information from display and setup view as necessary. */
+ int (*configure)(PuglView*);
+
+ /** Create surface and drawing context. */
+ int (*create)(PuglView*);
+
+ /** Destroy surface and drawing context. */
+ int (*destroy)(PuglView*);
+
+ /** Enter drawing context, for drawing if parameter is true. */
+ int (*enter)(PuglView*, bool);
+
+ /** Leave drawing context, after drawing if parameter is true. */
+ int (*leave)(PuglView*, bool);
+
+ /** Resize drawing context to the given width and height. */
+ int (*resize)(PuglView*, int, int);
+
+ /** Return the puglGetContext() handle for the application, if any. */
+ void* (*getContext)(PuglView*);
+};
+
+#endif // PUGL_DETAIL_TYPES_H
diff --git a/pugl/detail/win.c b/pugl/detail/win.c
new file mode 100644
index 0000000..93dce9d
--- /dev/null
+++ b/pugl/detail/win.c
@@ -0,0 +1,697 @@
+/*
+ Copyright 2012-2019 David Robillard <http://drobilla.net>
+
+ Permission to use, copy, modify, and/or distribute this software for any
+ purpose with or without fee is hereby granted, provided that the above
+ copyright notice and this permission notice appear in all copies.
+
+ THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+*/
+
+/**
+ @file win.c Windows implementation.
+*/
+
+#include "pugl/detail/implementation.h"
+#include "pugl/detail/win.h"
+#include "pugl/pugl.h"
+
+#include <windows.h>
+#include <windowsx.h>
+
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <wctype.h>
+
+#ifndef WM_MOUSEWHEEL
+# define WM_MOUSEWHEEL 0x020A
+#endif
+#ifndef WM_MOUSEHWHEEL
+# define WM_MOUSEHWHEEL 0x020E
+#endif
+#ifndef WHEEL_DELTA
+# define WHEEL_DELTA 120
+#endif
+#ifndef GWLP_USERDATA
+# define GWLP_USERDATA (-21)
+#endif
+
+#define PUGL_LOCAL_CLOSE_MSG (WM_USER + 50)
+#define PUGL_RESIZE_TIMER_ID 9461
+#define PUGL_URGENT_TIMER_ID 9462
+
+typedef BOOL (WINAPI *PFN_SetProcessDPIAware)(void);
+
+static const TCHAR* DEFAULT_CLASSNAME = "Pugl";
+
+static wchar_t*
+puglUtf8ToWideChar(const char* const utf8)
+{
+ const int len = MultiByteToWideChar(CP_UTF8, 0, utf8, -1, NULL, 0);
+ if (len > 0) {
+ wchar_t* result = (wchar_t*)calloc((size_t)len, sizeof(wchar_t));
+ MultiByteToWideChar(CP_UTF8, 0, utf8, -1, result, len);
+ return result;
+ }
+
+ return NULL;
+}
+
+LRESULT CALLBACK
+wndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam);
+
+PuglInternals*
+puglInitInternals(void)
+{
+ PuglInternals* impl = (PuglInternals*)calloc(1, sizeof(PuglInternals));
+
+ HMODULE user32 = LoadLibrary("user32.dll");
+ if (user32) {
+ PFN_SetProcessDPIAware SetProcessDPIAware =
+ (PFN_SetProcessDPIAware)GetProcAddress(
+ user32, "SetProcessDPIAware");
+ if (SetProcessDPIAware) {
+ SetProcessDPIAware();
+ }
+ }
+
+ LARGE_INTEGER frequency;
+ QueryPerformanceFrequency(&frequency);
+ impl->timerFrequency = (double)frequency.QuadPart;
+
+ return impl;
+}
+
+int
+puglCreateWindow(PuglView* view, const char* title)
+{
+ PuglInternals* impl = view->impl;
+
+ const char* className = view->windowClass ? view->windowClass : DEFAULT_CLASSNAME;
+
+ title = title ? title : "Window";
+
+ // Get refresh rate for resize draw timer
+ DEVMODEA devMode = {0};
+ EnumDisplaySettingsA(NULL, ENUM_CURRENT_SETTINGS, &devMode);
+ view->impl->refreshRate = devMode.dmDisplayFrequency;
+
+ // Register window class
+ WNDCLASSEX wc;
+ memset(&wc, 0, sizeof(wc));
+ wc.cbSize = sizeof(wc);
+ wc.style = CS_OWNDC;
+ wc.lpfnWndProc = wndProc;
+ wc.hInstance = GetModuleHandle(NULL);
+ wc.hIcon = LoadIcon(NULL, IDI_APPLICATION); // TODO: user-specified icon
+ wc.hCursor = LoadCursor(NULL, IDC_ARROW);
+ wc.hbrBackground = (HBRUSH)GetStockObject(BLACK_BRUSH);
+ wc.lpszClassName = className;
+ if (!RegisterClassEx(&wc)) {
+ return 1;
+ }
+
+ if (!view->backend || !view->backend->configure) {
+ return 1;
+ }
+
+ int st = view->backend->configure(view);
+ if (st || !impl->surface) {
+ return 2;
+ } else if ((st = view->backend->create(view))) {
+ return 3;
+ }
+
+ wchar_t* wtitle = puglUtf8ToWideChar(title);
+ if (wtitle) {
+ SetWindowTextW(impl->hwnd, wtitle);
+ free(wtitle);
+ }
+
+ SetWindowLongPtr(impl->hwnd, GWLP_USERDATA, (LONG_PTR)view);
+
+ return 0;
+}
+
+void
+puglShowWindow(PuglView* view)
+{
+ PuglInternals* impl = view->impl;
+
+ ShowWindow(impl->hwnd, SW_SHOWNORMAL);
+ SetFocus(impl->hwnd);
+ view->visible = true;
+}
+
+void
+puglHideWindow(PuglView* view)
+{
+ PuglInternals* impl = view->impl;
+
+ ShowWindow(impl->hwnd, SW_HIDE);
+ view->visible = false;
+}
+
+void
+puglDestroy(PuglView* view)
+{
+ if (view) {
+ view->backend->destroy(view);
+ ReleaseDC(view->impl->hwnd, view->impl->hdc);
+ DestroyWindow(view->impl->hwnd);
+ UnregisterClass(view->windowClass ? view->windowClass : DEFAULT_CLASSNAME, NULL);
+ free(view->windowClass);
+ free(view->impl);
+ free(view);
+ }
+}
+
+static PuglKey
+keySymToSpecial(WPARAM sym)
+{
+ switch (sym) {
+ case VK_F1: return PUGL_KEY_F1;
+ case VK_F2: return PUGL_KEY_F2;
+ case VK_F3: return PUGL_KEY_F3;
+ case VK_F4: return PUGL_KEY_F4;
+ case VK_F5: return PUGL_KEY_F5;
+ case VK_F6: return PUGL_KEY_F6;
+ case VK_F7: return PUGL_KEY_F7;
+ case VK_F8: return PUGL_KEY_F8;
+ case VK_F9: return PUGL_KEY_F9;
+ case VK_F10: return PUGL_KEY_F10;
+ case VK_F11: return PUGL_KEY_F11;
+ case VK_F12: return PUGL_KEY_F12;
+ case VK_BACK: return PUGL_KEY_BACKSPACE;
+ case VK_DELETE: return PUGL_KEY_DELETE;
+ case VK_LEFT: return PUGL_KEY_LEFT;
+ case VK_UP: return PUGL_KEY_UP;
+ case VK_RIGHT: return PUGL_KEY_RIGHT;
+ case VK_DOWN: return PUGL_KEY_DOWN;
+ case VK_PRIOR: return PUGL_KEY_PAGE_UP;
+ case VK_NEXT: return PUGL_KEY_PAGE_DOWN;
+ case VK_HOME: return PUGL_KEY_HOME;
+ case VK_END: return PUGL_KEY_END;
+ case VK_INSERT: return PUGL_KEY_INSERT;
+ case VK_SHIFT:
+ case VK_LSHIFT: return PUGL_KEY_SHIFT_L;
+ case VK_RSHIFT: return PUGL_KEY_SHIFT_R;
+ case VK_CONTROL:
+ case VK_LCONTROL: return PUGL_KEY_CTRL_L;
+ case VK_RCONTROL: return PUGL_KEY_CTRL_R;
+ case VK_MENU:
+ case VK_LMENU: return PUGL_KEY_ALT_L;
+ case VK_RMENU: return PUGL_KEY_ALT_R;
+ case VK_LWIN: return PUGL_KEY_SUPER_L;
+ case VK_RWIN: return PUGL_KEY_SUPER_R;
+ case VK_CAPITAL: return PUGL_KEY_CAPS_LOCK;
+ case VK_SCROLL: return PUGL_KEY_SCROLL_LOCK;
+ case VK_NUMLOCK: return PUGL_KEY_NUM_LOCK;
+ case VK_SNAPSHOT: return PUGL_KEY_PRINT_SCREEN;
+ case VK_PAUSE: return PUGL_KEY_PAUSE;
+ }
+ return (PuglKey)0;
+}
+
+static uint32_t
+getModifiers(void)
+{
+ return (((GetKeyState(VK_SHIFT) < 0) ? PUGL_MOD_SHIFT : 0u) |
+ ((GetKeyState(VK_CONTROL) < 0) ? PUGL_MOD_CTRL : 0u) |
+ ((GetKeyState(VK_MENU) < 0) ? PUGL_MOD_ALT : 0u) |
+ ((GetKeyState(VK_LWIN) < 0) ? PUGL_MOD_SUPER : 0u) |
+ ((GetKeyState(VK_RWIN) < 0) ? PUGL_MOD_SUPER : 0u));
+}
+
+static void
+initMouseEvent(PuglEvent* event,
+ PuglView* view,
+ int button,
+ bool press,
+ LPARAM lParam)
+{
+ POINT pt = { GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam) };
+ ClientToScreen(view->impl->hwnd, &pt);
+
+ if (press) {
+ SetCapture(view->impl->hwnd);
+ } else {
+ ReleaseCapture();
+ }
+
+ event->button.time = GetMessageTime() / 1e3;
+ event->button.type = press ? PUGL_BUTTON_PRESS : PUGL_BUTTON_RELEASE;
+ event->button.x = GET_X_LPARAM(lParam);
+ event->button.y = GET_Y_LPARAM(lParam);
+ event->button.x_root = pt.x;
+ event->button.y_root = pt.y;
+ event->button.state = getModifiers();
+ event->button.button = (uint32_t)button;
+}
+
+static void
+initScrollEvent(PuglEvent* event, PuglView* view, LPARAM lParam)
+{
+ POINT pt = { GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam) };
+ ScreenToClient(view->impl->hwnd, &pt);
+
+ event->scroll.time = GetMessageTime() / 1e3;
+ event->scroll.type = PUGL_SCROLL;
+ event->scroll.x = pt.x;
+ event->scroll.y = pt.y;
+ event->scroll.x_root = GET_X_LPARAM(lParam);
+ event->scroll.y_root = GET_Y_LPARAM(lParam);
+ event->scroll.state = getModifiers();
+ event->scroll.dx = 0;
+ event->scroll.dy = 0;
+}
+
+/** Return the code point for buf, or the replacement character on error. */
+static uint32_t
+puglDecodeUTF16(const wchar_t* buf, const int len)
+{
+ const uint32_t c0 = buf[0];
+ const uint32_t c1 = buf[0];
+ if (c0 >= 0xD800 && c0 < 0xDC00) {
+ if (len < 2) {
+ return 0xFFFD; // Surrogate, but length is only 1
+ } else if (c1 >= 0xDC00 && c1 <= 0xDFFF) {
+ return ((c0 & 0x03FF) << 10) + (c1 & 0x03FF) + 0x10000;
+ }
+
+ return 0xFFFD; // Unpaired surrogates
+ }
+
+ return c0;
+}
+
+static void
+initKeyEvent(PuglEventKey* event,
+ PuglView* view,
+ bool press,
+ WPARAM wParam,
+ LPARAM lParam)
+{
+ POINT rpos = { 0, 0 };
+ GetCursorPos(&rpos);
+
+ POINT cpos = { rpos.x, rpos.y };
+ ScreenToClient(view->impl->hwnd, &rpos);
+
+ const unsigned scode = (uint32_t)((lParam & 0xFF0000) >> 16);
+ const unsigned vkey = ((wParam == VK_SHIFT)
+ ? MapVirtualKey(scode, MAPVK_VSC_TO_VK_EX)
+ : (unsigned)wParam);
+
+ const unsigned vcode = MapVirtualKey(vkey, MAPVK_VK_TO_VSC);
+ const unsigned kchar = MapVirtualKey(vkey, MAPVK_VK_TO_CHAR);
+ const bool dead = kchar >> (sizeof(UINT) * 8 - 1) & 1;
+ const bool ext = lParam & 0x01000000;
+
+ event->type = press ? PUGL_KEY_PRESS : PUGL_KEY_RELEASE;
+ event->time = GetMessageTime() / 1e3;
+ event->state = getModifiers();
+ event->x_root = rpos.x;
+ event->y_root = rpos.y;
+ event->x = cpos.x;
+ event->y = cpos.y;
+ event->keycode = (uint32_t)((lParam & 0xFF0000) >> 16);
+ event->key = 0;
+
+ const PuglKey special = keySymToSpecial(vkey);
+ if (special) {
+ if (ext && (special == PUGL_KEY_CTRL || special == PUGL_KEY_ALT)) {
+ event->key = special + 1u; // Right hand key
+ } else {
+ event->key = special;
+ }
+ } else if (!dead) {
+ // Translate unshifted key
+ BYTE keyboardState[256] = {0};
+ wchar_t buf[5] = {0};
+ const int ulen = ToUnicode(vkey, vcode, keyboardState, buf, 4, 1<<2);
+ event->key = puglDecodeUTF16(buf, ulen);
+ }
+}
+
+static void
+initCharEvent(PuglEvent* event, PuglView* view, WPARAM wParam, LPARAM lParam)
+{
+ const wchar_t utf16[2] = { wParam & 0xFFFF, (wParam >> 16) & 0xFFFF };
+
+ initKeyEvent(&event->key, view, true, wParam, lParam);
+ event->type = PUGL_TEXT;
+ event->text.character = puglDecodeUTF16(utf16, 2);
+
+ if (!WideCharToMultiByte(
+ CP_UTF8, 0, utf16, 2, event->text.string, 8, NULL, NULL)) {
+ memset(event->text.string, 0, 8);
+ }
+}
+
+static bool
+ignoreKeyEvent(PuglView* view, LPARAM lParam)
+{
+ return view->hints.ignoreKeyRepeat && (lParam & (1 << 30));
+}
+
+static RECT
+handleConfigure(PuglView* view, PuglEvent* event)
+{
+ RECT rect;
+ GetClientRect(view->impl->hwnd, &rect);
+ view->width = rect.right - rect.left;
+ view->height = rect.bottom - rect.top;
+
+ event->configure.type = PUGL_CONFIGURE;
+ event->configure.x = rect.left;
+ event->configure.y = rect.top;
+ event->configure.width = view->width;
+ event->configure.height = view->height;
+
+ view->backend->resize(view, view->width, view->height);
+ return rect;
+}
+
+static void
+handleCrossing(PuglView* view, const PuglEventType type, POINT pos)
+{
+ POINT root_pos = pos;
+ ClientToScreen(view->impl->hwnd, &root_pos);
+
+ const PuglEventCrossing ev = {
+ type,
+ 0,
+ GetMessageTime() / 1e3,
+ (double)pos.x,
+ (double)pos.y,
+ (double)root_pos.x,
+ (double)root_pos.y,
+ getModifiers(),
+ PUGL_CROSSING_NORMAL
+ };
+ puglDispatchEvent(view, (const PuglEvent*)&ev);
+}
+
+static void
+stopFlashing(PuglView* view)
+{
+ if (view->impl->flashing) {
+ KillTimer(view->impl->hwnd, PUGL_URGENT_TIMER_ID);
+ FlashWindow(view->impl->hwnd, FALSE);
+ }
+}
+
+static void
+constrainAspect(const PuglView* const view,
+ RECT* const size,
+ const WPARAM wParam)
+{
+ const float minA = (float)view->min_aspect_x / (float)view->min_aspect_y;
+ const float maxA = (float)view->max_aspect_x / (float)view->max_aspect_y;
+ const int w = size->right - size->left;
+ const int h = size->bottom - size->top;
+ const float a = (float)w / (float)h;
+
+ switch (wParam) {
+ case WMSZ_TOP:
+ size->top = (a < minA ? (LONG)(size->bottom - w * minA) :
+ a > maxA ? (LONG)(size->bottom - w * maxA) :
+ size->top);
+ break;
+ case WMSZ_TOPRIGHT:
+ case WMSZ_RIGHT:
+ case WMSZ_BOTTOMRIGHT:
+ size->right = (a < minA ? (LONG)(size->left + h * minA) :
+ a > maxA ? (LONG)(size->left + h * maxA) :
+ size->right);
+ break;
+ case WMSZ_BOTTOM:
+ size->bottom = (a < minA ? (LONG)(size->top + w * minA) :
+ a > maxA ? (LONG)(size->top + w * maxA) :
+ size->bottom);
+ break;
+ case WMSZ_BOTTOMLEFT:
+ case WMSZ_LEFT:
+ case WMSZ_TOPLEFT:
+ size->left = (a < minA ? (LONG)(size->right - h * minA) :
+ a > maxA ? (LONG)(size->right - h * maxA) :
+ size->left);
+ break;
+ }
+}
+
+static LRESULT
+handleMessage(PuglView* view, UINT message, WPARAM wParam, LPARAM lParam)
+{
+ PuglEvent event;
+ void* dummy_ptr = NULL;
+ RECT rect;
+ MINMAXINFO* mmi;
+ POINT pt;
+
+ memset(&event, 0, sizeof(event));
+
+ event.any.type = PUGL_NOTHING;
+ if (InSendMessageEx(dummy_ptr)) {
+ event.any.flags |= PUGL_IS_SEND_EVENT;
+ }
+
+ switch (message) {
+ case WM_SHOWWINDOW:
+ rect = handleConfigure(view, &event);
+ puglPostRedisplay(view);
+ break;
+ case WM_SIZE:
+ rect = handleConfigure(view, &event);
+ RedrawWindow(view->impl->hwnd, NULL, NULL,
+ RDW_INVALIDATE|RDW_ALLCHILDREN|RDW_INTERNALPAINT|
+ RDW_UPDATENOW);
+ break;
+ case WM_SIZING:
+ if (view->min_aspect_x) {
+ constrainAspect(view, (RECT*)lParam, wParam);
+ return TRUE;
+ }
+ break;
+ case WM_ENTERSIZEMOVE:
+ view->impl->resizing = true;
+ SetTimer(view->impl->hwnd,
+ PUGL_RESIZE_TIMER_ID,
+ 1000 / view->impl->refreshRate,
+ NULL);
+ break;
+ case WM_TIMER:
+ if (wParam == PUGL_RESIZE_TIMER_ID) {
+ RedrawWindow(view->impl->hwnd, NULL, NULL,
+ RDW_INVALIDATE|RDW_ALLCHILDREN|RDW_INTERNALPAINT);
+ } else if (wParam == PUGL_URGENT_TIMER_ID) {
+ FlashWindow(view->impl->hwnd, TRUE);
+ }
+ break;
+ case WM_EXITSIZEMOVE:
+ KillTimer(view->impl->hwnd, PUGL_RESIZE_TIMER_ID);
+ view->impl->resizing = false;
+ puglPostRedisplay(view);
+ break;
+ case WM_GETMINMAXINFO:
+ mmi = (MINMAXINFO*)lParam;
+ mmi->ptMinTrackSize.x = view->min_width;
+ mmi->ptMinTrackSize.y = view->min_height;
+ break;
+ case WM_PAINT:
+ GetUpdateRect(view->impl->hwnd, &rect, false);
+ event.expose.type = PUGL_EXPOSE;
+ event.expose.x = rect.left;
+ event.expose.y = rect.top;
+ event.expose.width = rect.right - rect.left;
+ event.expose.height = rect.bottom - rect.top;
+ event.expose.count = 0;
+ break;
+ case WM_ERASEBKGND:
+ return true;
+ case WM_MOUSEMOVE:
+ pt.x = GET_X_LPARAM(lParam);
+ pt.y = GET_Y_LPARAM(lParam);
+
+ if (!view->impl->mouseTracked) {
+ TRACKMOUSEEVENT tme = {0};
+ tme.cbSize = sizeof(tme);
+ tme.dwFlags = TME_LEAVE;
+ tme.hwndTrack = view->impl->hwnd;
+ TrackMouseEvent(&tme);
+
+ stopFlashing(view);
+ handleCrossing(view, PUGL_ENTER_NOTIFY, pt);
+ view->impl->mouseTracked = true;
+ }
+
+ ClientToScreen(view->impl->hwnd, &pt);
+ event.motion.type = PUGL_MOTION_NOTIFY;
+ event.motion.time = GetMessageTime() / 1e3;
+ event.motion.x = GET_X_LPARAM(lParam);
+ event.motion.y = GET_Y_LPARAM(lParam);
+ event.motion.x_root = pt.x;
+ event.motion.y_root = pt.y;
+ event.motion.state = getModifiers();
+ event.motion.is_hint = false;
+ break;
+ case WM_MOUSELEAVE:
+ GetCursorPos(&pt);
+ ScreenToClient(view->impl->hwnd, &pt);
+ handleCrossing(view, PUGL_LEAVE_NOTIFY, pt);
+ view->impl->mouseTracked = false;
+ break;
+ case WM_LBUTTONDOWN:
+ initMouseEvent(&event, view, 1, true, lParam);
+ break;
+ case WM_MBUTTONDOWN:
+ initMouseEvent(&event, view, 2, true, lParam);
+ break;
+ case WM_RBUTTONDOWN:
+ initMouseEvent(&event, view, 3, true, lParam);
+ break;
+ case WM_LBUTTONUP:
+ initMouseEvent(&event, view, 1, false, lParam);
+ break;
+ case WM_MBUTTONUP:
+ initMouseEvent(&event, view, 2, false, lParam);
+ break;
+ case WM_RBUTTONUP:
+ initMouseEvent(&event, view, 3, false, lParam);
+ break;
+ case WM_MOUSEWHEEL:
+ initScrollEvent(&event, view, lParam);
+ event.scroll.dy = GET_WHEEL_DELTA_WPARAM(wParam) / (float)WHEEL_DELTA;
+ break;
+ case WM_MOUSEHWHEEL:
+ initScrollEvent(&event, view, lParam);
+ event.scroll.dx = GET_WHEEL_DELTA_WPARAM(wParam) / (float)WHEEL_DELTA;
+ break;
+ case WM_KEYDOWN:
+ if (!ignoreKeyEvent(view, lParam)) {
+ initKeyEvent(&event.key, view, true, wParam, lParam);
+ }
+ break;
+ case WM_KEYUP:
+ initKeyEvent(&event.key, view, false, wParam, lParam);
+ break;
+ case WM_CHAR:
+ initCharEvent(&event, view, wParam, lParam);
+ break;
+ case WM_SETFOCUS:
+ stopFlashing(view);
+ event.type = PUGL_FOCUS_IN;
+ break;
+ case WM_KILLFOCUS:
+ event.type = PUGL_FOCUS_OUT;
+ break;
+ case WM_SYSKEYDOWN:
+ initKeyEvent(&event.key, view, true, wParam, lParam);
+ break;
+ case WM_SYSKEYUP:
+ initKeyEvent(&event.key, view, false, wParam, lParam);
+ break;
+ case WM_SYSCHAR:
+ return TRUE;
+ case WM_QUIT:
+ case PUGL_LOCAL_CLOSE_MSG:
+ event.close.type = PUGL_CLOSE;
+ break;
+ default:
+ return DefWindowProc(view->impl->hwnd, message, wParam, lParam);
+ }
+
+ puglDispatchEvent(view, &event);
+
+ return 0;
+}
+
+void
+puglGrabFocus(PuglView* view)
+{
+ SetFocus(view->impl->hwnd);
+}
+
+void
+puglRequestAttention(PuglView* view)
+{
+ if (!view->impl->mouseTracked || GetFocus() != view->impl->hwnd) {
+ FlashWindow(view->impl->hwnd, TRUE);
+ SetTimer(view->impl->hwnd, PUGL_URGENT_TIMER_ID, 500, NULL);
+ view->impl->flashing = true;
+ }
+}
+
+PuglStatus
+puglWaitForEvent(PuglView* PUGL_UNUSED(view))
+{
+ WaitMessage();
+ return PUGL_SUCCESS;
+}
+
+PuglStatus
+puglProcessEvents(PuglView* view)
+{
+ MSG msg;
+ while (PeekMessage(&msg, view->impl->hwnd, 0, 0, PM_REMOVE)) {
+ TranslateMessage(&msg);
+ DispatchMessage(&msg);
+ }
+
+ return PUGL_SUCCESS;
+}
+
+LRESULT CALLBACK
+wndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
+{
+ PuglView* view = (PuglView*)GetWindowLongPtr(hwnd, GWLP_USERDATA);
+
+ switch (message) {
+ case WM_CREATE:
+ PostMessage(hwnd, WM_SHOWWINDOW, TRUE, 0);
+ return 0;
+ case WM_CLOSE:
+ PostMessage(hwnd, PUGL_LOCAL_CLOSE_MSG, wParam, lParam);
+ return 0;
+ case WM_DESTROY:
+ return 0;
+ default:
+ if (view && hwnd == view->impl->hwnd) {
+ return handleMessage(view, message, wParam, lParam);
+ } else {
+ return DefWindowProc(hwnd, message, wParam, lParam);
+ }
+ }
+}
+
+double
+puglGetTime(PuglView* view)
+{
+ LARGE_INTEGER count;
+ QueryPerformanceCounter(&count);
+ const double now = (double)count.QuadPart / view->impl->timerFrequency;
+ return now - view->start_time;
+}
+
+void
+puglPostRedisplay(PuglView* view)
+{
+ RedrawWindow(view->impl->hwnd, NULL, NULL,
+ RDW_INVALIDATE|RDW_ALLCHILDREN|RDW_INTERNALPAINT);
+ UpdateWindow(view->impl->hwnd);
+}
+
+PuglNativeWindow
+puglGetNativeWindow(PuglView* view)
+{
+ return (PuglNativeWindow)view->impl->hwnd;
+}
diff --git a/pugl/detail/win.h b/pugl/detail/win.h
new file mode 100644
index 0000000..9af5cbb
--- /dev/null
+++ b/pugl/detail/win.h
@@ -0,0 +1,100 @@
+/*
+ Copyright 2012-2019 David Robillard <http://drobilla.net>
+
+ Permission to use, copy, modify, and/or distribute this software for any
+ purpose with or without fee is hereby granted, provided that the above
+ copyright notice and this permission notice appear in all copies.
+
+ THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+*/
+
+/**
+ @file win.h Shared definitions for Windows implementation.
+*/
+
+#include "pugl/detail/implementation.h"
+
+#include <windows.h>
+
+#include <stdbool.h>
+
+typedef PIXELFORMATDESCRIPTOR PuglWinPFD;
+
+struct PuglInternalsImpl {
+ PuglWinPFD pfd;
+ int pfId;
+ HWND hwnd;
+ HDC hdc;
+ PuglSurface* surface;
+ DWORD refreshRate;
+ double timerFrequency;
+ bool flashing;
+ bool resizing;
+ bool mouseTracked;
+};
+
+static inline PuglWinPFD
+puglWinGetPixelFormatDescriptor(const PuglHints* const hints)
+{
+ const int rgbBits = hints->red_bits + hints->green_bits + hints->blue_bits;
+
+ PuglWinPFD pfd;
+ ZeroMemory(&pfd, sizeof(pfd));
+ pfd.nSize = sizeof(pfd);
+ pfd.nVersion = 1;
+ pfd.dwFlags = PFD_DRAW_TO_WINDOW|PFD_SUPPORT_OPENGL;
+ pfd.dwFlags |= hints->double_buffer ? PFD_DOUBLEBUFFER : 0;
+ pfd.iPixelType = PFD_TYPE_RGBA;
+ pfd.cColorBits = (BYTE)rgbBits;
+ pfd.cRedBits = (BYTE)hints->red_bits;
+ pfd.cGreenBits = (BYTE)hints->green_bits;
+ pfd.cBlueBits = (BYTE)hints->blue_bits;
+ pfd.cAlphaBits = (BYTE)hints->alpha_bits;
+ pfd.cDepthBits = (BYTE)hints->depth_bits;
+ pfd.cStencilBits = (BYTE)hints->stencil_bits;
+ pfd.iLayerType = PFD_MAIN_PLANE;
+ return pfd;
+}
+
+static inline PuglStatus
+puglWinCreateWindow(const PuglView* const view,
+ const char* const title,
+ HWND* const hwnd,
+ HDC* const hdc)
+{
+ const char* className = view->windowClass ? view->windowClass : "Pugl";
+
+ const unsigned winFlags =
+ (WS_CLIPCHILDREN | WS_CLIPSIBLINGS |
+ (view->parent
+ ? WS_CHILD
+ : (WS_POPUPWINDOW | WS_CAPTION | WS_MINIMIZEBOX |
+ (view->hints.resizable ? (WS_SIZEBOX | WS_MAXIMIZEBOX) : 0))));
+
+ const unsigned winExFlags =
+ WS_EX_NOINHERITLAYOUT | (view->parent ? 0u : WS_EX_APPWINDOW);
+
+ // Calculate total window size to accommodate requested view size
+ RECT wr = { 0, 0, view->width, view->height };
+ AdjustWindowRectEx(&wr, winFlags, FALSE, winExFlags);
+
+ // Create window and get drawing context
+ if (!(*hwnd = CreateWindowEx(winExFlags, className, title, winFlags,
+ CW_USEDEFAULT, CW_USEDEFAULT,
+ wr.right-wr.left, wr.bottom-wr.top,
+ (HWND)view->parent, NULL, NULL, NULL))) {
+ return PUGL_ERR_CREATE_WINDOW;
+ } else if (!(*hdc = GetDC(*hwnd))) {
+ DestroyWindow(*hwnd);
+ *hwnd = NULL;
+ return PUGL_ERR_CREATE_WINDOW;
+ }
+
+ return PUGL_SUCCESS;
+}
diff --git a/pugl/detail/win_cairo.c b/pugl/detail/win_cairo.c
new file mode 100644
index 0000000..49175c1
--- /dev/null
+++ b/pugl/detail/win_cairo.c
@@ -0,0 +1,199 @@
+/*
+ Copyright 2012-2019 David Robillard <http://drobilla.net>
+
+ Permission to use, copy, modify, and/or distribute this software for any
+ purpose with or without fee is hereby granted, provided that the above
+ copyright notice and this permission notice appear in all copies.
+
+ THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+*/
+
+/**
+ @file win_cairo.c Cairo graphics backend for Windows.
+*/
+
+#include "pugl/detail/types.h"
+#include "pugl/detail/win.h"
+#include "pugl/pugl_cairo_backend.h"
+
+#include <cairo-win32.h>
+#include <cairo.h>
+
+#include <stdlib.h>
+
+typedef struct {
+ cairo_surface_t* surface;
+ cairo_t* cr;
+ HDC drawDc;
+ HBITMAP drawBitmap;
+} PuglWinCairoSurface;
+
+static int
+puglWinCairoCreateDrawContext(PuglView* view)
+{
+ PuglInternals* const impl = view->impl;
+ PuglWinCairoSurface* const surface = (PuglWinCairoSurface*)impl->surface;
+
+ surface->drawDc = CreateCompatibleDC(impl->hdc);
+ surface->drawBitmap = CreateCompatibleBitmap(
+ impl->hdc, view->width, view->height);
+
+ DeleteObject(SelectObject(surface->drawDc, surface->drawBitmap));
+
+ cairo_status_t st = CAIRO_STATUS_SUCCESS;
+ if (!(surface->surface = cairo_win32_surface_create(surface->drawDc)) ||
+ (st = cairo_surface_status(surface->surface)) ||
+ !(surface->cr = cairo_create(surface->surface)) ||
+ (st = cairo_status(surface->cr))) {
+ return PUGL_ERR_CREATE_CONTEXT;
+ }
+
+ cairo_save(surface->cr);
+ return 0;
+}
+
+static int
+puglWinCairoDestroyDrawContext(PuglView* view)
+{
+ PuglInternals* const impl = view->impl;
+ PuglWinCairoSurface* const surface = (PuglWinCairoSurface*)impl->surface;
+
+ DeleteDC(surface->drawDc);
+ DeleteObject(surface->drawBitmap);
+ cairo_destroy(surface->cr);
+ cairo_surface_destroy(surface->surface);
+
+ surface->surface = NULL;
+ surface->cr = NULL;
+ surface->drawDc = NULL;
+ surface->drawBitmap = NULL;
+
+ return 0;
+}
+
+static int
+puglWinCairoConfigure(PuglView* view)
+{
+ PuglInternals* const impl = view->impl;
+ PuglStatus st = PUGL_SUCCESS;
+
+ if ((st = puglWinCreateWindow(view, "Pugl", &impl->hwnd, &impl->hdc))) {
+ return st;
+ }
+
+ impl->pfd = puglWinGetPixelFormatDescriptor(&view->hints);
+ impl->pfId = ChoosePixelFormat(impl->hdc, &impl->pfd);
+
+ if (!SetPixelFormat(impl->hdc, impl->pfId, &impl->pfd)) {
+ ReleaseDC(impl->hwnd, impl->hdc);
+ DestroyWindow(impl->hwnd);
+ impl->hwnd = NULL;
+ impl->hdc = NULL;
+ return PUGL_ERR_SET_FORMAT;
+ }
+
+ impl->surface = (PuglWinCairoSurface*)calloc(
+ 1, sizeof(PuglWinCairoSurface));
+
+ return 0;
+}
+
+static int
+puglWinCairoCreate(PuglView* view)
+{
+ return puglWinCairoCreateDrawContext(view);
+}
+
+static int
+puglWinCairoDestroy(PuglView* view)
+{
+ PuglInternals* const impl = view->impl;
+ PuglWinCairoSurface* const surface = (PuglWinCairoSurface*)impl->surface;
+
+ puglWinCairoDestroyDrawContext(view);
+ free(surface);
+ impl->surface = NULL;
+
+ return 0;
+}
+
+static int
+puglWinCairoEnter(PuglView* view, bool drawing)
+{
+ PuglInternals* const impl = view->impl;
+ PuglWinCairoSurface* const surface = (PuglWinCairoSurface*)impl->surface;
+ if (!drawing) {
+ return 0;
+ }
+
+ PAINTSTRUCT ps;
+ BeginPaint(view->impl->hwnd, &ps);
+ cairo_save(surface->cr);
+
+ return 0;
+}
+
+static int
+puglWinCairoLeave(PuglView* view, bool drawing)
+{
+ PuglInternals* const impl = view->impl;
+ PuglWinCairoSurface* const surface = (PuglWinCairoSurface*)impl->surface;
+ if (!drawing) {
+ return 0;
+ }
+
+ cairo_restore(surface->cr);
+ cairo_surface_flush(surface->surface);
+ BitBlt(impl->hdc, 0, 0, view->width, view->height,
+ surface->drawDc, 0, 0, SRCCOPY);
+
+ PAINTSTRUCT ps;
+ EndPaint(view->impl->hwnd, &ps);
+ SwapBuffers(view->impl->hdc);
+
+ return 0;
+}
+
+static int
+puglWinCairoResize(PuglView* view,
+ int width,
+ int height)
+{
+ view->width = width;
+ view->height = height;
+ int st = 0;
+ if ((st = puglWinCairoDestroyDrawContext(view)) ||
+ (st = puglWinCairoCreateDrawContext(view))) {
+ return st;
+ }
+
+ return 0;
+}
+
+static void*
+puglWinCairoGetContext(PuglView* view)
+{
+ return ((PuglWinCairoSurface*)view->impl->surface)->cr;
+}
+
+const PuglBackend*
+puglCairoBackend()
+{
+ static const PuglBackend backend = {
+ puglWinCairoConfigure,
+ puglWinCairoCreate,
+ puglWinCairoDestroy,
+ puglWinCairoEnter,
+ puglWinCairoLeave,
+ puglWinCairoResize,
+ puglWinCairoGetContext
+ };
+
+ return &backend;
+}
diff --git a/pugl/detail/win_gl.c b/pugl/detail/win_gl.c
new file mode 100644
index 0000000..17ee68d
--- /dev/null
+++ b/pugl/detail/win_gl.c
@@ -0,0 +1,306 @@
+/*
+ Copyright 2012-2019 David Robillard <http://drobilla.net>
+
+ Permission to use, copy, modify, and/or distribute this software for any
+ purpose with or without fee is hereby granted, provided that the above
+ copyright notice and this permission notice appear in all copies.
+
+ THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+*/
+
+/**
+ @file win_gl.c OpenGL graphics backend for Windows.
+*/
+
+#include "pugl/detail/types.h"
+#include "pugl/detail/win.h"
+#include "pugl/pugl_gl_backend.h"
+
+#include <windows.h>
+
+#include <GL/gl.h>
+
+#include <stdbool.h>
+#include <stdlib.h>
+
+#define WGL_DRAW_TO_WINDOW_ARB 0x2001
+#define WGL_ACCELERATION_ARB 0x2003
+#define WGL_SUPPORT_OPENGL_ARB 0x2010
+#define WGL_DOUBLE_BUFFER_ARB 0x2011
+#define WGL_PIXEL_TYPE_ARB 0x2013
+#define WGL_COLOR_BITS_ARB 0x2014
+#define WGL_RED_BITS_ARB 0x2015
+#define WGL_GREEN_BITS_ARB 0x2017
+#define WGL_BLUE_BITS_ARB 0x2019
+#define WGL_ALPHA_BITS_ARB 0x201b
+#define WGL_DEPTH_BITS_ARB 0x2022
+#define WGL_STENCIL_BITS_ARB 0x2023
+#define WGL_FULL_ACCELERATION_ARB 0x2027
+#define WGL_TYPE_RGBA_ARB 0x202b
+#define WGL_SAMPLE_BUFFERS_ARB 0x2041
+#define WGL_SAMPLES_ARB 0x2042
+
+#define WGL_CONTEXT_MAJOR_VERSION_ARB 0x2091
+#define WGL_CONTEXT_MINOR_VERSION_ARB 0x2092
+#define WGL_CONTEXT_LAYER_PLANE_ARB 0x2093
+#define WGL_CONTEXT_FLAGS_ARB 0x2094
+#define WGL_CONTEXT_PROFILE_MASK_ARB 0x9126
+
+#define WGL_CONTEXT_CORE_PROFILE_BIT_ARB 0x00000001
+#define WGL_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB 0x00000002
+
+typedef HGLRC (*WglCreateContextAttribs)(HDC, HGLRC, const int*);
+typedef BOOL (*WglSwapInterval)(int);
+typedef BOOL (*WglChoosePixelFormat)(
+ HDC, const int*, const FLOAT*, UINT, int*, UINT*);
+
+typedef struct {
+ WglChoosePixelFormat wglChoosePixelFormat;
+ WglCreateContextAttribs wglCreateContextAttribs;
+ WglSwapInterval wglSwapInterval;
+} PuglWinGlProcs;
+
+typedef struct {
+ PuglWinGlProcs procs;
+ HGLRC hglrc;
+} PuglWinGlSurface;
+
+// Struct to manage the fake window used during configuration
+typedef struct {
+ HWND hwnd;
+ HDC hdc;
+} PuglFakeWindow;
+
+static int
+puglWinError(PuglFakeWindow* fakeWin, const int status)
+{
+ if (fakeWin->hwnd) {
+ ReleaseDC(fakeWin->hwnd, fakeWin->hdc);
+ DestroyWindow(fakeWin->hwnd);
+ }
+
+ return status;
+}
+
+static PuglWinGlProcs puglWinGlGetProcs(void)
+{
+ const PuglWinGlProcs procs = {
+ (WglChoosePixelFormat)(
+ wglGetProcAddress("wglChoosePixelFormatARB")),
+ (WglCreateContextAttribs)(
+ wglGetProcAddress("wglCreateContextAttribsARB")),
+ (WglSwapInterval)(
+ wglGetProcAddress("wglSwapIntervalEXT"))
+ };
+
+ return procs;
+}
+
+static int
+puglWinGlConfigure(PuglView* view)
+{
+ PuglInternals* impl = view->impl;
+
+ const int pixelAttrs[] = {
+ WGL_DRAW_TO_WINDOW_ARB, GL_TRUE,
+ WGL_ACCELERATION_ARB, WGL_FULL_ACCELERATION_ARB,
+ WGL_SUPPORT_OPENGL_ARB, GL_TRUE,
+ WGL_DOUBLE_BUFFER_ARB, view->hints.double_buffer,
+ WGL_PIXEL_TYPE_ARB, WGL_TYPE_RGBA_ARB,
+ WGL_SAMPLE_BUFFERS_ARB, view->hints.samples ? 1 : 0,
+ WGL_SAMPLES_ARB, view->hints.samples,
+ WGL_RED_BITS_ARB, view->hints.red_bits,
+ WGL_GREEN_BITS_ARB, view->hints.green_bits,
+ WGL_BLUE_BITS_ARB, view->hints.blue_bits,
+ WGL_ALPHA_BITS_ARB, view->hints.alpha_bits,
+ WGL_DEPTH_BITS_ARB, view->hints.depth_bits,
+ WGL_STENCIL_BITS_ARB, view->hints.stencil_bits,
+ 0,
+ };
+
+ PuglWinGlSurface* const surface =
+ (PuglWinGlSurface*)calloc(1, sizeof(PuglWinGlSurface));
+ impl->surface = surface;
+
+ // Create fake window for getting at GL context
+ PuglStatus st = PUGL_SUCCESS;
+ PuglFakeWindow fakeWin = { 0, 0 };
+ if ((st = puglWinCreateWindow(view, "Pugl Configuration",
+ &fakeWin.hwnd, &fakeWin.hdc))) {
+ return puglWinError(&fakeWin, st);
+ }
+
+ // Set pixel format for fake window
+ const PuglWinPFD fakePfd = puglWinGetPixelFormatDescriptor(&view->hints);
+ const int fakePfId = ChoosePixelFormat(fakeWin.hdc, &fakePfd);
+ if (!fakePfId) {
+ return puglWinError(&fakeWin, PUGL_ERR_SET_FORMAT);
+ } else if (!SetPixelFormat(fakeWin.hdc, fakePfId, &fakePfd)) {
+ return puglWinError(&fakeWin, PUGL_ERR_SET_FORMAT);
+ }
+
+ // Create fake GL context to get at the functions we need
+ HGLRC fakeRc = wglCreateContext(fakeWin.hdc);
+ if (!fakeRc) {
+ return puglWinError(&fakeWin, PUGL_ERR_CREATE_CONTEXT);
+ }
+
+ // Enter fake context and get extension functions
+ wglMakeCurrent(fakeWin.hdc, fakeRc);
+ surface->procs = puglWinGlGetProcs();
+
+ if (surface->procs.wglChoosePixelFormat) {
+ // Choose pixel format based on attributes
+ UINT numFormats = 0;
+ if (!surface->procs.wglChoosePixelFormat(
+ fakeWin.hdc, pixelAttrs, NULL, 1u, &impl->pfId, &numFormats)) {
+ return puglWinError(&fakeWin, PUGL_ERR_SET_FORMAT);
+ }
+
+ DescribePixelFormat(
+ impl->hdc, impl->pfId, sizeof(impl->pfd), &impl->pfd);
+ } else {
+ // Modern extensions not available, use basic pixel format
+ impl->pfd = fakePfd;
+ impl->pfId = fakePfId;
+ }
+
+ // Dispose of fake window and context
+ wglMakeCurrent(NULL, NULL);
+ wglDeleteContext(fakeRc);
+ ReleaseDC(fakeWin.hwnd, fakeWin.hdc);
+ DestroyWindow(fakeWin.hwnd);
+
+ return 0;
+}
+
+static int
+puglWinGlCreate(PuglView* view)
+{
+ PuglInternals* const impl = view->impl;
+ PuglWinGlSurface* const surface = (PuglWinGlSurface*)impl->surface;
+ PuglStatus st = PUGL_SUCCESS;
+
+ const int contextAttribs[] = {
+ WGL_CONTEXT_MAJOR_VERSION_ARB, view->hints.context_version_major,
+ WGL_CONTEXT_MINOR_VERSION_ARB, view->hints.context_version_minor,
+ WGL_CONTEXT_PROFILE_MASK_ARB,
+ (view->hints.use_compat_profile
+ ? WGL_CONTEXT_CORE_PROFILE_BIT_ARB
+ : WGL_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB),
+ 0
+ };
+
+ // Create real window with desired pixel format
+ if ((st = puglWinCreateWindow(view, "Pugl", &impl->hwnd, &impl->hdc))) {
+ return st;
+ } else if (!SetPixelFormat(impl->hdc, impl->pfId, &impl->pfd)) {
+ ReleaseDC(impl->hwnd, impl->hdc);
+ DestroyWindow(impl->hwnd);
+ impl->hwnd = NULL;
+ impl->hdc = NULL;
+ return PUGL_ERR_SET_FORMAT;
+ }
+
+ // Create GL context
+ if (surface->procs.wglCreateContextAttribs &&
+ !(surface->hglrc = surface->procs.wglCreateContextAttribs(
+ impl->hdc, 0, contextAttribs))) {
+ return PUGL_ERR_CREATE_CONTEXT;
+ } else if (!(surface->hglrc = wglCreateContext(impl->hdc))) {
+ return PUGL_ERR_CREATE_CONTEXT;
+ }
+
+ // Enter context and set swap interval
+ wglMakeCurrent(impl->hdc, surface->hglrc);
+ if (surface->procs.wglSwapInterval) {
+ surface->procs.wglSwapInterval(1);
+ }
+
+ return 0;
+}
+
+static int
+puglWinGlDestroy(PuglView* view)
+{
+ PuglWinGlSurface* surface = (PuglWinGlSurface*)view->impl->surface;
+ if (surface) {
+ wglMakeCurrent(NULL, NULL);
+ wglDeleteContext(surface->hglrc);
+ free(surface);
+ view->impl->surface = NULL;
+ }
+
+ return 0;
+}
+
+static int
+puglWinGlEnter(PuglView* view, bool drawing)
+{
+ PuglWinGlSurface* surface = (PuglWinGlSurface*)view->impl->surface;
+
+ wglMakeCurrent(view->impl->hdc, surface->hglrc);
+
+ if (drawing) {
+ PAINTSTRUCT ps;
+ BeginPaint(view->impl->hwnd, &ps);
+ }
+
+ return 0;
+}
+
+static int
+puglWinGlLeave(PuglView* view, bool drawing)
+{
+ if (drawing) {
+ PAINTSTRUCT ps;
+ EndPaint(view->impl->hwnd, &ps);
+ SwapBuffers(view->impl->hdc);
+ }
+
+ wglMakeCurrent(NULL, NULL);
+
+ return 0;
+}
+
+static int
+puglWinGlResize(PuglView* PUGL_UNUSED(view),
+ int PUGL_UNUSED(width),
+ int PUGL_UNUSED(height))
+{
+ return 0;
+}
+
+static void*
+puglWinGlGetContext(PuglView* PUGL_UNUSED(view))
+{
+ return NULL;
+}
+
+PuglGlFunc
+puglGetProcAddress(const char* name)
+{
+ return (PuglGlFunc)wglGetProcAddress(name);
+}
+
+const PuglBackend*
+puglGlBackend()
+{
+ static const PuglBackend backend = {
+ puglWinGlConfigure,
+ puglWinGlCreate,
+ puglWinGlDestroy,
+ puglWinGlEnter,
+ puglWinGlLeave,
+ puglWinGlResize,
+ puglWinGlGetContext
+ };
+
+ return &backend;
+}
diff --git a/pugl/detail/x11.c b/pugl/detail/x11.c
new file mode 100644
index 0000000..d6461a2
--- /dev/null
+++ b/pugl/detail/x11.c
@@ -0,0 +1,588 @@
+/*
+ Copyright 2012-2019 David Robillard <http://drobilla.net>
+ Copyright 2013 Robin Gareus <robin@gareus.org>
+ Copyright 2011-2012 Ben Loftis, Harrison Consoles
+
+ Permission to use, copy, modify, and/or distribute this software for any
+ purpose with or without fee is hereby granted, provided that the above
+ copyright notice and this permission notice appear in all copies.
+
+ THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+*/
+
+/**
+ @file x11.c X11 implementation.
+*/
+
+#define _POSIX_C_SOURCE 199309L
+
+#include "pugl/detail/implementation.h"
+#include "pugl/detail/types.h"
+#include "pugl/detail/x11.h"
+#include "pugl/pugl.h"
+
+#include <X11/X.h>
+#include <X11/Xlib.h>
+#include <X11/Xutil.h>
+#include <X11/keysym.h>
+
+#include <sys/time.h>
+
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+
+#ifndef MIN
+# define MIN(a, b) (((a) < (b)) ? (a) : (b))
+#endif
+
+#ifndef MAX
+# define MAX(a, b) (((a) > (b)) ? (a) : (b))
+#endif
+
+enum WmClientStateMessageAction {
+ WM_STATE_REMOVE,
+ WM_STATE_ADD,
+ WM_STATE_TOGGLE
+};
+
+static const long eventMask =
+ (ExposureMask | StructureNotifyMask | FocusChangeMask |
+ EnterWindowMask | LeaveWindowMask | PointerMotionMask |
+ ButtonPressMask | ButtonReleaseMask | KeyPressMask | KeyReleaseMask);
+
+PuglInternals*
+puglInitInternals(void)
+{
+ return (PuglInternals*)calloc(1, sizeof(PuglInternals));
+}
+
+int
+puglCreateWindow(PuglView* view, const char* title)
+{
+ PuglInternals* const impl = view->impl;
+ Display* const display = XOpenDisplay(0);
+
+ impl->display = display;
+ impl->screen = DefaultScreen(display);
+
+ // Intern the various atoms we will need
+ impl->atoms.UTF8_STRING = XInternAtom(display, "UTF8_STRING", 0);
+ impl->atoms.WM_PROTOCOLS = XInternAtom(display, "WM_PROTOCOLS", 0);
+ impl->atoms.WM_DELETE_WINDOW = XInternAtom(display, "WM_DELETE_WINDOW", 0);
+ impl->atoms.NET_WM_NAME = XInternAtom(display, "_NET_WM_NAME", 0);
+ impl->atoms.NET_WM_STATE = XInternAtom(display, "_NET_WM_STATE", 0);
+ impl->atoms.NET_WM_STATE_DEMANDS_ATTENTION =
+ XInternAtom(display, "_NET_WM_STATE_DEMANDS_ATTENTION", 0);
+
+ if (!view->backend || !view->backend->configure) {
+ return 1;
+ } else if (view->backend->configure(view) || !impl->vi) {
+ view->backend->destroy(view);
+ return 2;
+ }
+
+ Window xParent = view->parent ? (Window)view->parent
+ : RootWindow(display, impl->screen);
+
+ Colormap cmap = XCreateColormap(
+ display, xParent, impl->vi->visual, AllocNone);
+
+ XSetWindowAttributes attr = {0};
+ attr.colormap = cmap;
+ attr.event_mask = eventMask;
+
+ const Window win = impl->win = XCreateWindow(
+ display, xParent,
+ 0, 0, view->width, view->height, 0, impl->vi->depth, InputOutput,
+ impl->vi->visual, CWColormap | CWEventMask, &attr);
+
+ if (view->backend->create(view)) {
+ return 3;
+ }
+
+ XSizeHints sizeHints = {0};
+ if (!view->hints.resizable) {
+ sizeHints.flags = PMinSize|PMaxSize;
+ sizeHints.min_width = view->width;
+ sizeHints.min_height = view->height;
+ sizeHints.max_width = view->width;
+ sizeHints.max_height = view->height;
+ } else {
+ if (view->min_width || view->min_height) {
+ sizeHints.flags = PMinSize;
+ sizeHints.min_width = view->min_width;
+ sizeHints.min_height = view->min_height;
+ }
+ if (view->min_aspect_x) {
+ sizeHints.flags |= PAspect;
+ sizeHints.min_aspect.x = view->min_aspect_x;
+ sizeHints.min_aspect.y = view->min_aspect_y;
+ sizeHints.max_aspect.x = view->max_aspect_x;
+ sizeHints.max_aspect.y = view->max_aspect_y;
+ }
+ }
+ XSetNormalHints(display, win, &sizeHints);
+
+ if (title) {
+ XStoreName(display, win, title);
+ XChangeProperty(display, win,
+ impl->atoms.NET_WM_NAME, impl->atoms.UTF8_STRING, 8,
+ PropModeReplace, (const uint8_t*)title, strlen(title));
+ }
+
+ if (!view->parent) {
+ XSetWMProtocols(display, win, &view->impl->atoms.WM_DELETE_WINDOW, 1);
+ }
+
+ if (view->transient_parent) {
+ XSetTransientForHint(display, win, (Window)(view->transient_parent));
+ }
+
+ XSetLocaleModifiers("");
+ if (!(impl->xim = XOpenIM(display, NULL, NULL, NULL))) {
+ XSetLocaleModifiers("@im=");
+ if (!(impl->xim = XOpenIM(display, NULL, NULL, NULL))) {
+ fprintf(stderr, "warning: XOpenIM failed\n");
+ }
+ }
+
+ const XIMStyle im_style = XIMPreeditNothing | XIMStatusNothing;
+ if (!(impl->xic = XCreateIC(impl->xim,
+ XNInputStyle, im_style,
+ XNClientWindow, win,
+ XNFocusWindow, win,
+ NULL))) {
+ fprintf(stderr, "warning: XCreateIC failed\n");
+ }
+
+ return 0;
+}
+
+void
+puglShowWindow(PuglView* view)
+{
+ XMapRaised(view->impl->display, view->impl->win);
+ view->visible = true;
+}
+
+void
+puglHideWindow(PuglView* view)
+{
+ XUnmapWindow(view->impl->display, view->impl->win);
+ view->visible = false;
+}
+
+void
+puglDestroy(PuglView* view)
+{
+ if (view) {
+ if (view->impl->xic) {
+ XDestroyIC(view->impl->xic);
+ }
+ if (view->impl->xim) {
+ XCloseIM(view->impl->xim);
+ }
+ view->backend->destroy(view);
+ XDestroyWindow(view->impl->display, view->impl->win);
+ XCloseDisplay(view->impl->display);
+ XFree(view->impl->vi);
+ free(view->windowClass);
+ free(view->impl);
+ free(view);
+ }
+}
+
+static PuglKey
+keySymToSpecial(KeySym sym)
+{
+ switch (sym) {
+ case XK_F1: return PUGL_KEY_F1;
+ case XK_F2: return PUGL_KEY_F2;
+ case XK_F3: return PUGL_KEY_F3;
+ case XK_F4: return PUGL_KEY_F4;
+ case XK_F5: return PUGL_KEY_F5;
+ case XK_F6: return PUGL_KEY_F6;
+ case XK_F7: return PUGL_KEY_F7;
+ case XK_F8: return PUGL_KEY_F8;
+ case XK_F9: return PUGL_KEY_F9;
+ case XK_F10: return PUGL_KEY_F10;
+ case XK_F11: return PUGL_KEY_F11;
+ case XK_F12: return PUGL_KEY_F12;
+ case XK_Left: return PUGL_KEY_LEFT;
+ case XK_Up: return PUGL_KEY_UP;
+ case XK_Right: return PUGL_KEY_RIGHT;
+ case XK_Down: return PUGL_KEY_DOWN;
+ case XK_Page_Up: return PUGL_KEY_PAGE_UP;
+ case XK_Page_Down: return PUGL_KEY_PAGE_DOWN;
+ case XK_Home: return PUGL_KEY_HOME;
+ case XK_End: return PUGL_KEY_END;
+ case XK_Insert: return PUGL_KEY_INSERT;
+ case XK_Shift_L: return PUGL_KEY_SHIFT_L;
+ case XK_Shift_R: return PUGL_KEY_SHIFT_R;
+ case XK_Control_L: return PUGL_KEY_CTRL_L;
+ case XK_Control_R: return PUGL_KEY_CTRL_R;
+ case XK_Alt_L: return PUGL_KEY_ALT_L;
+ case XK_ISO_Level3_Shift:
+ case XK_Alt_R: return PUGL_KEY_ALT_R;
+ case XK_Super_L: return PUGL_KEY_SUPER_L;
+ case XK_Super_R: return PUGL_KEY_SUPER_R;
+ case XK_Menu: return PUGL_KEY_MENU;
+ case XK_Caps_Lock: return PUGL_KEY_CAPS_LOCK;
+ case XK_Scroll_Lock: return PUGL_KEY_SCROLL_LOCK;
+ case XK_Num_Lock: return PUGL_KEY_NUM_LOCK;
+ case XK_Print: return PUGL_KEY_PRINT_SCREEN;
+ case XK_Pause: return PUGL_KEY_PAUSE;
+ default: break;
+ }
+ return (PuglKey)0;
+}
+
+static int
+lookupString(XIC xic, XEvent* xevent, char* str, KeySym* sym)
+{
+ Status status = 0;
+
+#ifdef X_HAVE_UTF8_STRING
+ const int n = Xutf8LookupString(xic, &xevent->xkey, str, 7, sym, &status);
+#else
+ const int n = XmbLookupString(xic, &xevent->xkey, str, 7, sym, &status);
+#endif
+
+ return status == XBufferOverflow ? 0 : n;
+}
+
+static void
+translateKey(PuglView* view, XEvent* xevent, PuglEvent* event)
+{
+ const unsigned state = xevent->xkey.state;
+ const bool filter = XFilterEvent(xevent, None);
+
+ event->key.keycode = xevent->xkey.keycode;
+ xevent->xkey.state = 0;
+
+ // Lookup unshifted key
+ char ustr[8] = {0};
+ KeySym sym = 0;
+ const int ufound = XLookupString(&xevent->xkey, ustr, 8, &sym, NULL);
+ const PuglKey special = keySymToSpecial(sym);
+
+ event->key.key = ((special || ufound <= 0)
+ ? special
+ : puglDecodeUTF8((const uint8_t*)ustr));
+
+ if (xevent->type == KeyPress && !filter && !special) {
+ // Lookup shifted key for possible text event
+ xevent->xkey.state = state;
+
+ char sstr[8] = {0};
+ const int sfound = lookupString(view->impl->xic, xevent, sstr, &sym);
+ if (sfound > 0) {
+ // Dispatch key event now
+ puglDispatchEvent(view, event);
+
+ // "Return" a text event in its place
+ event->text.type = PUGL_TEXT;
+ event->text.character = puglDecodeUTF8((const uint8_t*)sstr);
+ memcpy(event->text.string, sstr, sizeof(sstr));
+ }
+ }
+}
+
+static uint32_t
+translateModifiers(const unsigned xstate)
+{
+ return (((xstate & ShiftMask) ? PUGL_MOD_SHIFT : 0) |
+ ((xstate & ControlMask) ? PUGL_MOD_CTRL : 0) |
+ ((xstate & Mod1Mask) ? PUGL_MOD_ALT : 0) |
+ ((xstate & Mod4Mask) ? PUGL_MOD_SUPER : 0));
+}
+
+static PuglEvent
+translateEvent(PuglView* view, XEvent xevent)
+{
+ PuglEvent event = {0};
+ event.any.flags = xevent.xany.send_event ? PUGL_IS_SEND_EVENT : 0;
+
+ switch (xevent.type) {
+ case ClientMessage:
+ if (xevent.xclient.message_type == view->impl->atoms.WM_PROTOCOLS) {
+ const Atom protocol = (Atom)xevent.xclient.data.l[0];
+ if (protocol == view->impl->atoms.WM_DELETE_WINDOW) {
+ event.type = PUGL_CLOSE;
+ }
+ }
+ break;
+ case MapNotify: {
+ XWindowAttributes attrs = {0};
+ XGetWindowAttributes(view->impl->display, view->impl->win, &attrs);
+ event.type = PUGL_CONFIGURE;
+ event.configure.x = attrs.x;
+ event.configure.y = attrs.y;
+ event.configure.width = attrs.width;
+ event.configure.height = attrs.height;
+ break;
+ }
+ case ConfigureNotify:
+ event.type = PUGL_CONFIGURE;
+ event.configure.x = xevent.xconfigure.x;
+ event.configure.y = xevent.xconfigure.y;
+ event.configure.width = xevent.xconfigure.width;
+ event.configure.height = xevent.xconfigure.height;
+ break;
+ case Expose:
+ event.type = PUGL_EXPOSE;
+ event.expose.x = xevent.xexpose.x;
+ event.expose.y = xevent.xexpose.y;
+ event.expose.width = xevent.xexpose.width;
+ event.expose.height = xevent.xexpose.height;
+ event.expose.count = xevent.xexpose.count;
+ break;
+ case MotionNotify:
+ event.type = PUGL_MOTION_NOTIFY;
+ event.motion.time = xevent.xmotion.time / 1e3;
+ event.motion.x = xevent.xmotion.x;
+ event.motion.y = xevent.xmotion.y;
+ event.motion.x_root = xevent.xmotion.x_root;
+ event.motion.y_root = xevent.xmotion.y_root;
+ event.motion.state = translateModifiers(xevent.xmotion.state);
+ event.motion.is_hint = (xevent.xmotion.is_hint == NotifyHint);
+ break;
+ case ButtonPress:
+ if (xevent.xbutton.button >= 4 && xevent.xbutton.button <= 7) {
+ event.type = PUGL_SCROLL;
+ event.scroll.time = xevent.xbutton.time / 1e3;
+ event.scroll.x = xevent.xbutton.x;
+ event.scroll.y = xevent.xbutton.y;
+ event.scroll.x_root = xevent.xbutton.x_root;
+ event.scroll.y_root = xevent.xbutton.y_root;
+ event.scroll.state = translateModifiers(xevent.xbutton.state);
+ event.scroll.dx = 0.0;
+ event.scroll.dy = 0.0;
+ switch (xevent.xbutton.button) {
+ case 4: event.scroll.dy = 1.0; break;
+ case 5: event.scroll.dy = -1.0; break;
+ case 6: event.scroll.dx = -1.0; break;
+ case 7: event.scroll.dx = 1.0; break;
+ }
+ // fallthru
+ }
+ // fallthru
+ case ButtonRelease:
+ if (xevent.xbutton.button < 4 || xevent.xbutton.button > 7) {
+ event.button.type = ((xevent.type == ButtonPress)
+ ? PUGL_BUTTON_PRESS
+ : PUGL_BUTTON_RELEASE);
+ event.button.time = xevent.xbutton.time / 1e3;
+ event.button.x = xevent.xbutton.x;
+ event.button.y = xevent.xbutton.y;
+ event.button.x_root = xevent.xbutton.x_root;
+ event.button.y_root = xevent.xbutton.y_root;
+ event.button.state = translateModifiers(xevent.xbutton.state);
+ event.button.button = xevent.xbutton.button;
+ }
+ break;
+ case KeyPress:
+ case KeyRelease:
+ event.type = ((xevent.type == KeyPress)
+ ? PUGL_KEY_PRESS
+ : PUGL_KEY_RELEASE);
+ event.key.time = xevent.xkey.time / 1e3;
+ event.key.x = xevent.xkey.x;
+ event.key.y = xevent.xkey.y;
+ event.key.x_root = xevent.xkey.x_root;
+ event.key.y_root = xevent.xkey.y_root;
+ event.key.state = translateModifiers(xevent.xkey.state);
+ translateKey(view, &xevent, &event);
+ break;
+ case EnterNotify:
+ case LeaveNotify:
+ event.type = ((xevent.type == EnterNotify)
+ ? PUGL_ENTER_NOTIFY
+ : PUGL_LEAVE_NOTIFY);
+ event.crossing.time = xevent.xcrossing.time / 1e3;
+ event.crossing.x = xevent.xcrossing.x;
+ event.crossing.y = xevent.xcrossing.y;
+ event.crossing.x_root = xevent.xcrossing.x_root;
+ event.crossing.y_root = xevent.xcrossing.y_root;
+ event.crossing.state = translateModifiers(xevent.xcrossing.state);
+ event.crossing.mode = PUGL_CROSSING_NORMAL;
+ if (xevent.xcrossing.mode == NotifyGrab) {
+ event.crossing.mode = PUGL_CROSSING_GRAB;
+ } else if (xevent.xcrossing.mode == NotifyUngrab) {
+ event.crossing.mode = PUGL_CROSSING_UNGRAB;
+ }
+ break;
+
+ case FocusIn:
+ case FocusOut:
+ event.type = (xevent.type == FocusIn) ? PUGL_FOCUS_IN : PUGL_FOCUS_OUT;
+ event.focus.grab = (xevent.xfocus.mode != NotifyNormal);
+ break;
+
+ default:
+ break;
+ }
+
+ return event;
+}
+
+void
+puglGrabFocus(PuglView* view)
+{
+ XSetInputFocus(
+ view->impl->display, view->impl->win, RevertToPointerRoot, CurrentTime);
+}
+
+void
+puglRequestAttention(PuglView* view)
+{
+ PuglInternals* const impl = view->impl;
+ XEvent event = {0};
+ event.type = ClientMessage;
+ event.xclient.window = impl->win;
+ event.xclient.format = 32;
+ event.xclient.message_type = impl->atoms.NET_WM_STATE;
+ event.xclient.data.l[0] = WM_STATE_ADD;
+ event.xclient.data.l[1] = impl->atoms.NET_WM_STATE_DEMANDS_ATTENTION;
+ event.xclient.data.l[2] = 0;
+ event.xclient.data.l[3] = 1;
+ event.xclient.data.l[4] = 0;
+
+ const Window root = RootWindow(impl->display, impl->screen);
+ XSendEvent(impl->display,
+ root,
+ False,
+ SubstructureNotifyMask | SubstructureRedirectMask,
+ &event);
+}
+
+PuglStatus
+puglWaitForEvent(PuglView* view)
+{
+ XEvent xevent;
+ XPeekEvent(view->impl->display, &xevent);
+ return PUGL_SUCCESS;
+}
+
+static void
+merge_expose_events(PuglEvent* dst, const PuglEvent* src)
+{
+ if (!dst->type) {
+ *dst = *src;
+ } else {
+ const double max_x = MAX(dst->expose.x + dst->expose.width,
+ src->expose.x + src->expose.width);
+ const double max_y = MAX(dst->expose.y + dst->expose.height,
+ src->expose.y + src->expose.height);
+
+ dst->expose.x = MIN(dst->expose.x, src->expose.x);
+ dst->expose.y = MIN(dst->expose.y, src->expose.y);
+ dst->expose.width = max_x - dst->expose.x;
+ dst->expose.height = max_y - dst->expose.y;
+ dst->expose.count = MIN(dst->expose.count, src->expose.count);
+ }
+}
+
+PuglStatus
+puglProcessEvents(PuglView* view)
+{
+ /* Maintain a single expose/configure event to execute after all pending
+ events. This avoids redundant drawing/configuration which prevents a
+ series of window resizes in the same loop from being laggy. */
+ PuglInternals* const impl = view->impl;
+ PuglEvent expose_event = { 0 };
+ PuglEvent config_event = { 0 };
+ XEvent xevent;
+ while (XPending(impl->display) > 0) {
+ XNextEvent(impl->display, &xevent);
+ if (xevent.type == KeyRelease) {
+ // Ignore key repeat if necessary
+ if (view->hints.ignoreKeyRepeat &&
+ XEventsQueued(impl->display, QueuedAfterReading)) {
+ XEvent next;
+ XPeekEvent(impl->display, &next);
+ if (next.type == KeyPress &&
+ next.xkey.time == xevent.xkey.time &&
+ next.xkey.keycode == xevent.xkey.keycode) {
+ XNextEvent(impl->display, &xevent);
+ continue;
+ }
+ }
+ } else if (xevent.type == FocusIn) {
+ XSetICFocus(impl->xic);
+ } else if (xevent.type == FocusOut) {
+ XUnsetICFocus(impl->xic);
+ }
+
+ // Translate X11 event to Pugl event
+ const PuglEvent event = translateEvent(view, xevent);
+
+ if (event.type == PUGL_EXPOSE) {
+ // Expand expose event to be dispatched after loop
+ merge_expose_events(&expose_event, &event);
+ } else if (event.type == PUGL_CONFIGURE) {
+ // Expand configure event to be dispatched after loop
+ config_event = event;
+ } else {
+ // Dispatch event to application immediately
+ puglDispatchEvent(view, &event);
+ }
+ }
+
+ if (config_event.type || expose_event.type) {
+ const bool draw = expose_event.type && expose_event.expose.count == 0;
+
+ puglEnterContext(view, draw);
+
+ if (config_event.type) {
+ view->width = (int)config_event.configure.width;
+ view->height = (int)config_event.configure.height;
+ view->backend->resize(view, view->width, view->height);
+ view->eventFunc(view, (const PuglEvent*)&config_event);
+ }
+
+ if (draw) {
+ view->eventFunc(view, (const PuglEvent*)&expose_event);
+ }
+
+ puglLeaveContext(view, draw);
+ }
+
+ return PUGL_SUCCESS;
+}
+
+double
+puglGetTime(PuglView* view)
+{
+ struct timespec ts;
+ clock_gettime(CLOCK_MONOTONIC, &ts);
+ return ((double)ts.tv_sec + ts.tv_nsec / 1000000000.0) - view->start_time;
+}
+
+void
+puglPostRedisplay(PuglView* view)
+{
+ XExposeEvent ev = {Expose, 0, True,
+ view->impl->display, view->impl->win,
+ 0, 0,
+ view->width, view->height,
+ 0};
+
+ XSendEvent(view->impl->display, view->impl->win, False, 0, (XEvent*)&ev);
+}
+
+PuglNativeWindow
+puglGetNativeWindow(PuglView* view)
+{
+ return (PuglNativeWindow)view->impl->win;
+}
diff --git a/pugl/detail/x11.h b/pugl/detail/x11.h
new file mode 100644
index 0000000..1ead119
--- /dev/null
+++ b/pugl/detail/x11.h
@@ -0,0 +1,43 @@
+/*
+ Copyright 2012-2019 David Robillard <http://drobilla.net>
+
+ Permission to use, copy, modify, and/or distribute this software for any
+ purpose with or without fee is hereby granted, provided that the above
+ copyright notice and this permission notice appear in all copies.
+
+ THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+*/
+
+/**
+ @file x11.h Shared definitions for X11 implementation.
+*/
+
+#include "pugl/detail/implementation.h"
+
+#include <X11/Xlib.h>
+#include <X11/Xutil.h>
+
+struct PuglInternalsImpl {
+ Display* display;
+ int screen;
+ XVisualInfo* vi;
+ Window win;
+ XIM xim;
+ XIC xic;
+ PuglSurface* surface;
+
+ struct {
+ Atom UTF8_STRING;
+ Atom WM_PROTOCOLS;
+ Atom WM_DELETE_WINDOW;
+ Atom NET_WM_NAME;
+ Atom NET_WM_STATE;
+ Atom NET_WM_STATE_DEMANDS_ATTENTION;
+ } atoms;
+};
diff --git a/pugl/detail/x11_cairo.c b/pugl/detail/x11_cairo.c
new file mode 100644
index 0000000..d5e2ed7
--- /dev/null
+++ b/pugl/detail/x11_cairo.c
@@ -0,0 +1,176 @@
+/*
+ Copyright 2012-2019 David Robillard <http://drobilla.net>
+
+ Permission to use, copy, modify, and/or distribute this software for any
+ purpose with or without fee is hereby granted, provided that the above
+ copyright notice and this permission notice appear in all copies.
+
+ THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+*/
+
+/**
+ @file x11_cairo.c Cairo graphics backend for X11.
+*/
+
+#include "pugl/detail/types.h"
+#include "pugl/detail/x11.h"
+#include "pugl/pugl.h"
+#include "pugl/pugl_cairo_backend.h"
+
+#include <X11/Xutil.h>
+#include <cairo-xlib.h>
+#include <cairo.h>
+
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+typedef struct {
+ cairo_surface_t* back;
+ cairo_t* backCr;
+ cairo_surface_t* front;
+ cairo_t* frontCr;
+} PuglX11CairoSurface;
+
+static int
+puglX11CairoConfigure(PuglView* view)
+{
+ PuglInternals* const impl = view->impl;
+
+ XVisualInfo pat;
+ int n;
+ pat.screen = impl->screen;
+ impl->vi = XGetVisualInfo(impl->display, VisualScreenMask, &pat, &n);
+
+ return 0;
+}
+
+static int
+puglX11CairoCreate(PuglView* view)
+{
+ PuglInternals* const impl = view->impl;
+ const int width = view->width;
+ const int height = view->height;
+ PuglX11CairoSurface surface = { 0 };
+
+ surface.back = cairo_xlib_surface_create(
+ impl->display, impl->win, impl->vi->visual, width, height);
+ surface.front = cairo_surface_create_similar(
+ surface.back, CAIRO_CONTENT_COLOR, width, height);
+ surface.backCr = cairo_create(surface.back);
+ surface.frontCr = cairo_create(surface.front);
+
+ cairo_status_t st = CAIRO_STATUS_SUCCESS;
+ if (!surface.back || !surface.backCr ||
+ !surface.front || !surface.frontCr ||
+ (st = cairo_surface_status(surface.back)) ||
+ (st = cairo_surface_status(surface.front)) ||
+ (st = cairo_status(surface.backCr)) ||
+ (st = cairo_status(surface.frontCr))) {
+ cairo_destroy(surface.frontCr);
+ cairo_destroy(surface.backCr);
+ cairo_surface_destroy(surface.front);
+ cairo_surface_destroy(surface.back);
+ return PUGL_ERR_CREATE_CONTEXT;
+ }
+
+ impl->surface = calloc(1, sizeof(PuglX11CairoSurface));
+ *(PuglX11CairoSurface*)impl->surface = surface;
+
+ return 0;
+}
+
+static int
+puglX11CairoDestroy(PuglView* view)
+{
+ PuglInternals* const impl = view->impl;
+ PuglX11CairoSurface* const surface = (PuglX11CairoSurface*)impl->surface;
+
+ cairo_destroy(surface->frontCr);
+ cairo_destroy(surface->backCr);
+ cairo_surface_destroy(surface->front);
+ cairo_surface_destroy(surface->back);
+ free(surface);
+ impl->surface = NULL;
+ return 0;
+}
+
+static int
+puglX11CairoEnter(PuglView* view, bool drawing)
+{
+ PuglInternals* const impl = view->impl;
+ PuglX11CairoSurface* const surface = (PuglX11CairoSurface*)impl->surface;
+
+ if (drawing) {
+ cairo_save(surface->frontCr);
+ }
+
+ return 0;
+}
+
+static int
+puglX11CairoLeave(PuglView* view, bool drawing)
+{
+ PuglInternals* const impl = view->impl;
+ PuglX11CairoSurface* const surface = (PuglX11CairoSurface*)impl->surface;
+
+ if (drawing) {
+ cairo_set_source_surface(surface->backCr, surface->front, 0, 0);
+ cairo_paint(surface->backCr);
+ cairo_restore(surface->frontCr);
+ }
+
+ return 0;
+}
+
+static int
+puglX11CairoResize(PuglView* view, int width, int height)
+{
+ PuglInternals* const impl = view->impl;
+ PuglX11CairoSurface* const surface = (PuglX11CairoSurface*)impl->surface;
+
+ cairo_xlib_surface_set_size(surface->back, width, height);
+
+ cairo_destroy(surface->frontCr);
+ cairo_surface_destroy(surface->front);
+ if (!(surface->front = cairo_surface_create_similar(
+ surface->back, CAIRO_CONTENT_COLOR, width, height))) {
+ return PUGL_ERR_CREATE_CONTEXT;
+ }
+
+ surface->frontCr = cairo_create(surface->front);
+ cairo_save(surface->frontCr);
+
+ return 0;
+}
+
+static void*
+puglX11CairoGetContext(PuglView* view)
+{
+ PuglInternals* const impl = view->impl;
+ PuglX11CairoSurface* const surface = (PuglX11CairoSurface*)impl->surface;
+
+ return surface->frontCr;
+}
+
+const PuglBackend*
+puglCairoBackend(void)
+{
+ static const PuglBackend backend = {
+ puglX11CairoConfigure,
+ puglX11CairoCreate,
+ puglX11CairoDestroy,
+ puglX11CairoEnter,
+ puglX11CairoLeave,
+ puglX11CairoResize,
+ puglX11CairoGetContext
+ };
+
+ return &backend;
+}
diff --git a/pugl/detail/x11_gl.c b/pugl/detail/x11_gl.c
new file mode 100644
index 0000000..85c18b4
--- /dev/null
+++ b/pugl/detail/x11_gl.c
@@ -0,0 +1,218 @@
+/*
+ Copyright 2012-2019 David Robillard <http://drobilla.net>
+
+ Permission to use, copy, modify, and/or distribute this software for any
+ purpose with or without fee is hereby granted, provided that the above
+ copyright notice and this permission notice appear in all copies.
+
+ THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+*/
+
+/**
+ @file x11_gl.c OpenGL graphics backend for X11.
+*/
+
+#include "pugl/detail/types.h"
+#include "pugl/detail/x11.h"
+#include "pugl/pugl.h"
+#include "pugl/pugl_gl_backend.h"
+
+#include <GL/gl.h>
+#include <GL/glx.h>
+#include <X11/X.h>
+#include <X11/Xlib.h>
+#include <X11/Xutil.h>
+
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+typedef struct {
+ GLXFBConfig fb_config;
+ GLXContext ctx;
+ int double_buffered;
+} PuglX11GlSurface;
+
+static int
+puglX11GlHintValue(const int value)
+{
+ return value == PUGL_DONT_CARE ? (int)GLX_DONT_CARE : value;
+}
+
+static int
+puglX11GlGetAttrib(Display* const display,
+ const GLXFBConfig fb_config,
+ const int attrib)
+{
+ int value = 0;
+ glXGetFBConfigAttrib(display, fb_config, attrib, &value);
+ return value;
+}
+
+static int
+puglX11GlConfigure(PuglView* view)
+{
+ PuglInternals* const impl = view->impl;
+ const int screen = impl->screen;
+ Display* const display = impl->display;
+
+ PuglX11GlSurface* const surface =
+ (PuglX11GlSurface*)calloc(1, sizeof(PuglX11GlSurface));
+ impl->surface = surface;
+
+ const int attrs[] = {
+ GLX_X_RENDERABLE, True,
+ GLX_X_VISUAL_TYPE, GLX_TRUE_COLOR,
+ GLX_DRAWABLE_TYPE, GLX_WINDOW_BIT,
+ GLX_RENDER_TYPE, GLX_RGBA_BIT,
+ GLX_SAMPLES, view->hints.samples,
+ GLX_RED_SIZE, puglX11GlHintValue(view->hints.red_bits),
+ GLX_GREEN_SIZE, puglX11GlHintValue(view->hints.green_bits),
+ GLX_BLUE_SIZE, puglX11GlHintValue(view->hints.blue_bits),
+ GLX_ALPHA_SIZE, puglX11GlHintValue(view->hints.alpha_bits),
+ GLX_DEPTH_SIZE, puglX11GlHintValue(view->hints.depth_bits),
+ GLX_STENCIL_SIZE, puglX11GlHintValue(view->hints.stencil_bits),
+ GLX_DOUBLEBUFFER, puglX11GlHintValue(view->hints.double_buffer),
+ None
+ };
+
+ int n_fbc = 0;
+ GLXFBConfig* fbc = glXChooseFBConfig(display, screen, attrs, &n_fbc);
+ if (n_fbc <= 0) {
+ fprintf(stderr, "error: Failed to create GL context\n");
+ return 1;
+ }
+
+ surface->fb_config = fbc[0];
+ impl->vi = glXGetVisualFromFBConfig(impl->display, fbc[0]);
+
+ printf("Using visual 0x%lX: R=%d G=%d B=%d A=%d D=%d"
+ " DOUBLE=%d SAMPLES=%d\n",
+ impl->vi->visualid,
+ puglX11GlGetAttrib(display, fbc[0], GLX_RED_SIZE),
+ puglX11GlGetAttrib(display, fbc[0], GLX_GREEN_SIZE),
+ puglX11GlGetAttrib(display, fbc[0], GLX_BLUE_SIZE),
+ puglX11GlGetAttrib(display, fbc[0], GLX_ALPHA_SIZE),
+ puglX11GlGetAttrib(display, fbc[0], GLX_DEPTH_SIZE),
+ puglX11GlGetAttrib(display, fbc[0], GLX_DOUBLEBUFFER),
+ puglX11GlGetAttrib(display, fbc[0], GLX_SAMPLES));
+
+ XFree(fbc);
+
+ return 0;
+}
+
+static int
+puglX11GlCreate(PuglView* view)
+{
+ PuglInternals* const impl = view->impl;
+ PuglX11GlSurface* const surface = (PuglX11GlSurface*)impl->surface;
+ Display* const display = impl->display;
+ const GLXFBConfig fb_config = surface->fb_config;
+
+ const int ctx_attrs[] = {
+ GLX_CONTEXT_MAJOR_VERSION_ARB, view->hints.context_version_major,
+ GLX_CONTEXT_MINOR_VERSION_ARB, view->hints.context_version_minor,
+ GLX_CONTEXT_PROFILE_MASK_ARB, (view->hints.use_compat_profile
+ ? GLX_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB
+ : GLX_CONTEXT_CORE_PROFILE_BIT_ARB),
+ 0};
+
+ typedef GLXContext (*CreateContextAttribs)(
+ Display*, GLXFBConfig, GLXContext, Bool, const int*);
+
+ CreateContextAttribs create_context =
+ (CreateContextAttribs)glXGetProcAddress(
+ (const GLubyte*)"glXCreateContextAttribsARB");
+
+ impl->surface = surface;
+ surface->ctx = create_context(display, fb_config, 0, GL_TRUE, ctx_attrs);
+ if (!surface->ctx) {
+ surface->ctx =
+ glXCreateNewContext(display, fb_config, GLX_RGBA_TYPE, 0, True);
+ }
+
+ glXGetConfig(impl->display,
+ impl->vi,
+ GLX_DOUBLEBUFFER,
+ &surface->double_buffered);
+
+ return 0;
+}
+
+static int
+puglX11GlDestroy(PuglView* view)
+{
+ PuglX11GlSurface* surface = (PuglX11GlSurface*)view->impl->surface;
+ if (surface) {
+ glXDestroyContext(view->impl->display, surface->ctx);
+ free(surface);
+ view->impl->surface = NULL;
+ }
+ return 0;
+}
+
+static int
+puglX11GlEnter(PuglView* view, bool PUGL_UNUSED(drawing))
+{
+ PuglX11GlSurface* surface = (PuglX11GlSurface*)view->impl->surface;
+ glXMakeCurrent(view->impl->display, view->impl->win, surface->ctx);
+ return 0;
+}
+
+static int
+puglX11GlLeave(PuglView* view, bool drawing)
+{
+ PuglX11GlSurface* surface = (PuglX11GlSurface*)view->impl->surface;
+
+ if (drawing && surface->double_buffered) {
+ glXSwapBuffers(view->impl->display, view->impl->win);
+ } else if (drawing) {
+ glFlush();
+ }
+
+ glXMakeCurrent(view->impl->display, None, NULL);
+
+ return 0;
+}
+
+static int
+puglX11GlResize(PuglView* PUGL_UNUSED(view),
+ int PUGL_UNUSED(width),
+ int PUGL_UNUSED(height))
+{
+ return 0;
+}
+
+static void*
+puglX11GlGetContext(PuglView* PUGL_UNUSED(view))
+{
+ return NULL;
+}
+
+PuglGlFunc
+puglGetProcAddress(const char* name)
+{
+ return glXGetProcAddress((const GLubyte*)name);
+}
+
+const PuglBackend* puglGlBackend(void)
+{
+ static const PuglBackend backend = {
+ puglX11GlConfigure,
+ puglX11GlCreate,
+ puglX11GlDestroy,
+ puglX11GlEnter,
+ puglX11GlLeave,
+ puglX11GlResize,
+ puglX11GlGetContext
+ };
+
+ return &backend;
+}
diff --git a/pugl/gl.h b/pugl/gl.h
index 9a6aeef..55a55c4 100644
--- a/pugl/gl.h
+++ b/pugl/gl.h
@@ -29,4 +29,3 @@
# endif
# include "GL/gl.h"
#endif
-
diff --git a/pugl/glew.h b/pugl/glew.h
index 5374406..f26ff93 100644
--- a/pugl/glew.h
+++ b/pugl/glew.h
@@ -15,7 +15,7 @@
*/
/**
- @file gl.h Portable header wrapper for glew.h.
+ @file glew.h Portable header wrapper for glew.h.
Unfortunately, GL includes vary across platforms so this header allows for
pure portable programs.
@@ -29,4 +29,3 @@
# endif
# include "GL/glew.h"
#endif
-
diff --git a/pugl/glu.h b/pugl/glu.h
index 0d3e8e1..0ade70c 100644
--- a/pugl/glu.h
+++ b/pugl/glu.h
@@ -15,7 +15,7 @@
*/
/**
- @file gl.h Portable header wrapper for glu.h.
+ @file glu.h Portable header wrapper for glu.h.
Unfortunately, GL includes vary across platforms so this header allows for
pure portable programs.
@@ -29,4 +29,3 @@
# endif
# include "GL/glu.h"
#endif
-
diff --git a/pugl/pugl.h b/pugl/pugl.h
index dbbad90..d04f1aa 100644
--- a/pugl/pugl.h
+++ b/pugl/pugl.h
@@ -1,5 +1,5 @@
/*
- Copyright 2012-2016 David Robillard <http://drobilla.net>
+ Copyright 2012-2019 David Robillard <http://drobilla.net>
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
@@ -15,12 +15,13 @@
*/
/**
- @file pugl.h API for Pugl, a minimal portable API for OpenGL.
+ @file pugl.h Public Pugl API.
*/
#ifndef PUGL_H_INCLUDED
#define PUGL_H_INCLUDED
+#include <stdbool.h>
#include <stdint.h>
#ifdef PUGL_SHARED
@@ -40,10 +41,16 @@
# define PUGL_API
#endif
+#if defined(__clang__)
+# define PUGL_DEPRECATED_BY(name) __attribute__((deprecated("", name)))
+#elif defined(__GNUC__)
+# define PUGL_DEPRECATED_BY(name) __attribute__((deprecated("Use " name)))
+#else
+# define PUGL_DEPRECATED_BY(name)
+#endif
+
#ifdef __cplusplus
extern "C" {
-#else
-# include <stdbool.h>
#endif
/**
@@ -58,6 +65,11 @@ extern "C" {
typedef struct PuglViewImpl PuglView;
/**
+ Graphics backend interface.
+*/
+typedef struct PuglBackendImpl PuglBackend;
+
+/**
A native window handle.
On X11, this is a Window.
@@ -75,26 +87,39 @@ typedef void* PuglHandle;
Return status code.
*/
typedef enum {
- PUGL_SUCCESS = 0
+ PUGL_SUCCESS,
+ PUGL_ERR_CREATE_WINDOW,
+ PUGL_ERR_SET_FORMAT,
+ PUGL_ERR_CREATE_CONTEXT,
} PuglStatus;
/**
- Drawing context type.
+ Window hint.
*/
typedef enum {
- PUGL_GL = 0x1,
- PUGL_CAIRO = 0x2,
- PUGL_CAIRO_GL = 0x3
-} PuglContextType;
-
-/**
- Convenience symbols for ASCII control characters.
+ PUGL_USE_COMPAT_PROFILE, /**< Use compatible (not core) OpenGL profile */
+ PUGL_CONTEXT_VERSION_MAJOR, /**< OpenGL context major version */
+ PUGL_CONTEXT_VERSION_MINOR, /**< OpenGL context minor version */
+ PUGL_RED_BITS, /**< Number of bits for red channel */
+ PUGL_GREEN_BITS, /**< Number of bits for green channel */
+ PUGL_BLUE_BITS, /**< Number of bits for blue channel */
+ PUGL_ALPHA_BITS, /**< Number of bits for alpha channel */
+ PUGL_DEPTH_BITS, /**< Number of bits for depth buffer */
+ PUGL_STENCIL_BITS, /**< Number of bits for stencil buffer */
+ PUGL_SAMPLES, /**< Number of samples per pixel (AA) */
+ PUGL_DOUBLE_BUFFER, /**< True if double buffering should be used */
+ PUGL_RESIZABLE, /**< True if window should be resizable */
+ PUGL_IGNORE_KEY_REPEAT, /**< True if key repeat events are ignored */
+} PuglWindowHint;
+
+/**
+ Special window hint value.
*/
typedef enum {
- PUGL_CHAR_BACKSPACE = 0x08,
- PUGL_CHAR_ESCAPE = 0x1B,
- PUGL_CHAR_DELETE = 0x7F
-} PuglChar;
+ PUGL_DONT_CARE = -1, /**< Use best available value */
+ PUGL_FALSE = 0, /**< Explicitly false */
+ PUGL_TRUE = 1 /**< Explicitly true */
+} PuglWindowHintValue;
/**
Keyboard modifier flags.
@@ -107,14 +132,24 @@ typedef enum {
} PuglMod;
/**
- Special (non-Unicode) keyboard keys.
+ Special keyboard keys.
+
+ All keys, special or not, are expressed as a Unicode code point. This
+ enumeration defines constants for special keys that do not have a standard
+ code point, and some convenience constants for control characters.
- The numerical values of these symbols occupy a reserved range of Unicode
- points, so it is possible to express either a PuglKey value or a Unicode
- character in the same variable. This is sometimes useful for interfacing
- with APIs that do not make this distinction.
+ Keys that do not have a standard code point use values in the Private Use
+ Area in the Basic Multilingual Plane (U+E000 to U+F8FF). Applications must
+ take care to not interpret these values beyond key detection, the mapping
+ used here is arbitrary and specific to Pugl.
*/
typedef enum {
+ // ASCII control codes
+ PUGL_KEY_BACKSPACE = 0x08,
+ PUGL_KEY_ESCAPE = 0x1B,
+ PUGL_KEY_DELETE = 0x7F,
+
+ // Unicode Private Use Area
PUGL_KEY_F1 = 0xE000,
PUGL_KEY_F2,
PUGL_KEY_F3,
@@ -137,9 +172,23 @@ typedef enum {
PUGL_KEY_END,
PUGL_KEY_INSERT,
PUGL_KEY_SHIFT,
+ PUGL_KEY_SHIFT_L = PUGL_KEY_SHIFT,
+ PUGL_KEY_SHIFT_R,
PUGL_KEY_CTRL,
+ PUGL_KEY_CTRL_L = PUGL_KEY_CTRL,
+ PUGL_KEY_CTRL_R,
PUGL_KEY_ALT,
- PUGL_KEY_SUPER
+ PUGL_KEY_ALT_L = PUGL_KEY_ALT,
+ PUGL_KEY_ALT_R,
+ PUGL_KEY_SUPER,
+ PUGL_KEY_SUPER_L = PUGL_KEY_SUPER,
+ PUGL_KEY_SUPER_R,
+ PUGL_KEY_MENU,
+ PUGL_KEY_CAPS_LOCK,
+ PUGL_KEY_SCROLL_LOCK,
+ PUGL_KEY_NUM_LOCK,
+ PUGL_KEY_PRINT_SCREEN,
+ PUGL_KEY_PAUSE
} PuglKey;
/**
@@ -154,6 +203,7 @@ typedef enum {
PUGL_CLOSE, /**< Close view */
PUGL_KEY_PRESS, /**< Key press */
PUGL_KEY_RELEASE, /**< Key release */
+ PUGL_TEXT, /**< Character entry */
PUGL_ENTER_NOTIFY, /**< Pointer entered view */
PUGL_LEAVE_NOTIFY, /**< Pointer left view */
PUGL_MOTION_NOTIFY, /**< Pointer motion */
@@ -180,7 +230,6 @@ typedef enum {
*/
typedef struct {
PuglEventType type; /**< Event type. */
- PuglView* view; /**< View that received this event. */
uint32_t flags; /**< Bitwise OR of PuglEventFlag values. */
} PuglEventAny;
@@ -191,15 +240,14 @@ typedef struct {
*/
typedef struct {
PuglEventType type; /**< PUGL_BUTTON_PRESS or PUGL_BUTTON_RELEASE. */
- PuglView* view; /**< View that received this event. */
uint32_t flags; /**< Bitwise OR of PuglEventFlag values. */
- uint32_t time; /**< Time in milliseconds. */
+ double time; /**< Time in seconds. */
double x; /**< View-relative X coordinate. */
double y; /**< View-relative Y coordinate. */
double x_root; /**< Root-relative X coordinate. */
double y_root; /**< Root-relative Y coordinate. */
- unsigned state; /**< Bitwise OR of PuglMod flags. */
- unsigned button; /**< 1-relative button number. */
+ uint32_t state; /**< Bitwise OR of PuglMod flags. */
+ uint32_t button; /**< 1-relative button number. */
} PuglEventButton;
/**
@@ -207,7 +255,6 @@ typedef struct {
*/
typedef struct {
PuglEventType type; /**< PUGL_CONFIGURE. */
- PuglView* view; /**< View that received this event. */
uint32_t flags; /**< Bitwise OR of PuglEventFlag values. */
double x; /**< New parent-relative X coordinate. */
double y; /**< New parent-relative Y coordinate. */
@@ -220,7 +267,6 @@ typedef struct {
*/
typedef struct {
PuglEventType type; /**< PUGL_EXPOSE. */
- PuglView* view; /**< View that received this event. */
uint32_t flags; /**< Bitwise OR of PuglEventFlag values. */
double x; /**< View-relative X coordinate. */
double y; /**< View-relative Y coordinate. */
@@ -234,61 +280,66 @@ typedef struct {
*/
typedef struct {
PuglEventType type; /**< PUGL_CLOSE. */
- PuglView* view; /**< View that received this event. */
uint32_t flags; /**< Bitwise OR of PuglEventFlag values. */
} PuglEventClose;
/**
Key press/release event.
- Keys that correspond to a Unicode character have `character` and `utf8` set.
- Other keys will have `character` 0, but `special` may be set if this is a
- known special key.
-
- A key press may be part of a multi-key sequence to generate a wide
- character. If `filter` is set, this event is part of a multi-key sequence
- and should be ignored if the application is reading textual input.
- Following the series of filtered press events, a press event with
- `character` and `utf8` (but `keycode` 0) will be sent. This event will have
- no corresponding release event.
+ This represents low-level key press and release events. This event type
+ should be used for "raw" keyboard handing (key bindings, for example), but
+ must not be interpreted as text input.
- Generally, an application should either work with raw keyboard press/release
- events based on `keycode` (ignoring events with `keycode` 0), or
- read textual input based on `character` or `utf8` (ignoring releases and
- events with `filter` 1). Note that blindly appending `utf8` will yield
- incorrect text, since press events are sent for both individually composed
- keys and the resulting synthetic multi-byte press.
+ Keys are represented as Unicode code points, using the "natural" code point
+ for the key wherever possible (see @ref PuglKey for details). The `key`
+ field will be set to the code for the pressed key, without any modifiers
+ applied (by the shift or control keys).
*/
typedef struct {
PuglEventType type; /**< PUGL_KEY_PRESS or PUGL_KEY_RELEASE. */
- PuglView* view; /**< View that received this event. */
uint32_t flags; /**< Bitwise OR of PuglEventFlag values. */
- uint32_t time; /**< Time in milliseconds. */
+ double time; /**< Time in seconds. */
double x; /**< View-relative X coordinate. */
double y; /**< View-relative Y coordinate. */
double x_root; /**< Root-relative X coordinate. */
double y_root; /**< Root-relative Y coordinate. */
- unsigned state; /**< Bitwise OR of PuglMod flags. */
- unsigned keycode; /**< Raw key code. */
- uint32_t character; /**< Unicode character code, or 0. */
- PuglKey special; /**< Special key, or 0. */
- uint8_t utf8[8]; /**< UTF-8 string. */
- bool filter; /**< True if part of a multi-key sequence. */
+ uint32_t state; /**< Bitwise OR of PuglMod flags. */
+ uint32_t keycode; /**< Raw key code. */
+ uint32_t key; /**< Unshifted Unicode character code, or 0. */
} PuglEventKey;
/**
+ Character input event.
+
+ This represents text input, usually as the result of a key press. The text
+ is given both as a Unicode character code and a UTF-8 string.
+*/
+typedef struct {
+ PuglEventType type; /**< PUGL_CHAR. */
+ uint32_t flags; /**< Bitwise OR of PuglEventFlag values. */
+ double time; /**< Time in seconds. */
+ double x; /**< View-relative X coordinate. */
+ double y; /**< View-relative Y coordinate. */
+ double x_root; /**< Root-relative X coordinate. */
+ double y_root; /**< Root-relative Y coordinate. */
+ uint32_t state; /**< Bitwise OR of PuglMod flags. */
+ uint32_t keycode; /**< Raw key code. */
+ uint32_t character; /**< Unicode character code */
+ char string[8]; /**< UTF-8 string. */
+} PuglEventText;
+
+/**
Pointer crossing event (enter and leave).
*/
typedef struct {
PuglEventType type; /**< PUGL_ENTER_NOTIFY or PUGL_LEAVE_NOTIFY. */
- PuglView* view; /**< View that received this event. */
uint32_t flags; /**< Bitwise OR of PuglEventFlag values. */
- uint32_t time; /**< Time in milliseconds. */
+ double time; /**< Time in seconds. */
double x; /**< View-relative X coordinate. */
double y; /**< View-relative Y coordinate. */
double x_root; /**< Root-relative X coordinate. */
double y_root; /**< Root-relative Y coordinate. */
- unsigned state; /**< Bitwise OR of PuglMod flags. */
+ uint32_t state; /**< Bitwise OR of PuglMod flags. */
PuglCrossingMode mode; /**< Reason for crossing. */
} PuglEventCrossing;
@@ -297,14 +348,13 @@ typedef struct {
*/
typedef struct {
PuglEventType type; /**< PUGL_MOTION_NOTIFY. */
- PuglView* view; /**< View that received this event. */
uint32_t flags; /**< Bitwise OR of PuglEventFlag values. */
- uint32_t time; /**< Time in milliseconds. */
+ double time; /**< Time in seconds. */
double x; /**< View-relative X coordinate. */
double y; /**< View-relative Y coordinate. */
double x_root; /**< Root-relative X coordinate. */
double y_root; /**< Root-relative Y coordinate. */
- unsigned state; /**< Bitwise OR of PuglMod flags. */
+ uint32_t state; /**< Bitwise OR of PuglMod flags. */
bool is_hint; /**< True iff this event is a motion hint. */
bool focus; /**< True iff this is the focused window. */
} PuglEventMotion;
@@ -320,14 +370,13 @@ typedef struct {
*/
typedef struct {
PuglEventType type; /**< PUGL_SCROLL. */
- PuglView* view; /**< View that received this event. */
uint32_t flags; /**< Bitwise OR of PuglEventFlag values. */
- uint32_t time; /**< Time in milliseconds. */
+ double time; /**< Time in seconds. */
double x; /**< View-relative X coordinate. */
double y; /**< View-relative Y coordinate. */
double x_root; /**< Root-relative X coordinate. */
double y_root; /**< Root-relative Y coordinate. */
- unsigned state; /**< Bitwise OR of PuglMod flags. */
+ uint32_t state; /**< Bitwise OR of PuglMod flags. */
double dx; /**< Scroll X distance in lines. */
double dy; /**< Scroll Y distance in lines. */
} PuglEventScroll;
@@ -337,7 +386,6 @@ typedef struct {
*/
typedef struct {
PuglEventType type; /**< PUGL_FOCUS_IN or PUGL_FOCUS_OUT. */
- PuglView* view; /**< View that received this event. */
uint32_t flags; /**< Bitwise OR of PuglEventFlag values. */
bool grab; /**< True iff this is a grab/ungrab event. */
} PuglEventFocus;
@@ -357,6 +405,7 @@ typedef union {
PuglEventExpose expose; /**< PUGL_EXPOSE. */
PuglEventClose close; /**< PUGL_CLOSE. */
PuglEventKey key; /**< PUGL_KEY_PRESS, PUGL_KEY_RELEASE. */
+ PuglEventText text; /**< PUGL_TEXT. */
PuglEventCrossing crossing; /**< PUGL_ENTER_NOTIFY, PUGL_LEAVE_NOTIFY. */
PuglEventMotion motion; /**< PUGL_MOTION_NOTIFY. */
PuglEventScroll scroll; /**< PUGL_SCROLL. */
@@ -383,6 +432,12 @@ PUGL_API PuglView*
puglInit(int* pargc, char** argv);
/**
+ Set a hint before creating a window.
+*/
+PUGL_API void
+puglInitWindowHint(PuglView* view, PuglWindowHint hint, int value);
+
+/**
Set the window class name before creating a window.
*/
PUGL_API void
@@ -411,6 +466,10 @@ puglInitWindowMinSize(PuglView* view, int width, int height);
The x and y values here represent a ratio of width to height. To set a
fixed aspect ratio, set the minimum and maximum values to the same ratio.
+
+ Note that setting different minimum and maximum constraints does not
+ currenty work on MacOS (the minimum is used), so only setting a fixed aspect
+ ratio works properly across all platforms.
*/
PUGL_API void
puglInitWindowAspectRatio(PuglView* view,
@@ -421,8 +480,10 @@ puglInitWindowAspectRatio(PuglView* view,
/**
Enable or disable resizing before creating a window.
+
+ @deprecated Use puglInitWindowHint() with @ref PUGL_RESIZABLE.
*/
-PUGL_API void
+PUGL_API PUGL_DEPRECATED_BY("puglInitWindowHint") void
puglInitResizable(PuglView* view, bool resizable);
/**
@@ -435,16 +496,18 @@ PUGL_API void
puglInitTransientFor(PuglView* view, uintptr_t parent);
/**
- Set the context type before creating a window.
-*/
-PUGL_API void
-puglInitContextType(PuglView* view, PuglContextType type);
+ Set the graphics backend to use.
-/**
- @}
+ This needs to be called once before creating the window to set the graphics
+ backend. There are two backend accessors included with pugl:
+ puglGlBackend() and puglCairoBackend(), declared in pugl_gl_backend.h and
+ pugl_cairo_backend.h, respectively.
*/
+PUGL_API int
+puglInitBackend(PuglView* view, const PuglBackend* backend);
/**
+ @}
@name Windows
Functions for creating and managing a visible window for a view.
@{
@@ -516,37 +579,42 @@ puglGetSize(PuglView* view, int* width, int* height);
/**
Get the drawing context.
- For PUGL_GL contexts, this is unused and returns NULL.
- For PUGL_CAIRO contexts, this returns a pointer to a cairo_t.
+ The context is only guaranteed to be available during an expose.
+
+ For OpenGL backends, this is unused and returns NULL.
+ For Cairo backends, this returns a pointer to a `cairo_t`.
*/
PUGL_API void*
puglGetContext(PuglView* view);
-
/**
Enter the drawing context.
- This must be called before any code that accesses the drawing context,
- including any GL functions. This is only necessary for code that does so
- outside the usual draw callback or handling of an expose event.
+ Note that pugl automatically enters and leaves the drawing context during
+ configure and expose events, so it is not normally necessary to call this.
+ However, it can be used to enter the drawing context elsewhere, for example
+ to call any GL functions during setup.
+
+ @param view The view being entered.
+ @param drawing If true, prepare for drawing.
*/
PUGL_API void
-puglEnterContext(PuglView* view);
+puglEnterContext(PuglView* view, bool drawing);
/**
Leave the drawing context.
- This must be called after puglEnterContext and applies the results of the
- drawing code (for example, by swapping buffers).
+ This must be called after puglEnterContext() with a matching `drawing`
+ parameter.
+
+ @param view The view being left.
+ @param drawing If true, finish drawing, for example by swapping buffers.
*/
PUGL_API void
-puglLeaveContext(PuglView* view, bool flush);
+puglLeaveContext(PuglView* view, bool drawing);
/**
@}
-*/
-
-/**
@name Event Handling
@{
*/
@@ -564,27 +632,27 @@ puglSetEventFunc(PuglView* view, PuglEventFunc eventFunc);
/**
Ignore synthetic repeated key events.
+
+ @deprecated Use puglInitWindowHint() with @ref PUGL_IGNORE_KEY_REPEAT.
*/
-PUGL_API void
+PUGL_API PUGL_DEPRECATED_BY("puglInitWindowHint") void
puglIgnoreKeyRepeat(PuglView* view, bool ignore);
/**
- Copy selection to clipboard.
+ Grab the input focus.
*/
PUGL_API void
-puglCopyToClipboard(PuglView* view, const char* selection, size_t len);
+puglGrabFocus(PuglView* view);
/**
- Paste selection from clipboard.
-*/
-PUGL_API const char*
-puglPasteFromClipboard(PuglView* view, size_t* len);
+ Request user attention.
-/**
- Grab the input focus.
+ This hints to the system that the window or application requires attention
+ from the user. The exact effect depends on the platform, but is usually
+ something like flashing a task bar entry.
*/
PUGL_API void
-puglGrabFocus(PuglView* view);
+puglRequestAttention(PuglView* view);
/**
Block and wait for an event to be ready.
@@ -612,6 +680,27 @@ puglProcessEvents(PuglView* view);
*/
/**
+ OpenGL extension function.
+*/
+typedef void (*PuglGlFunc)(void);
+
+/**
+ Return the address of an OpenGL extension function.
+*/
+PUGL_API PuglGlFunc
+puglGetProcAddress(const char* name);
+
+/**
+ Return the time in seconds.
+
+ This is a monotonically increasing clock with high resolution. The returned
+ time is only useful to compare against other times returned by this
+ function, its absolute value has no meaning.
+*/
+PUGL_API double
+puglGetTime(PuglView* view);
+
+/**
Request a redisplay on the next call to puglProcessEvents().
*/
PUGL_API void
diff --git a/pugl/pugl.hpp b/pugl/pugl.hpp
index 7d3ea9e..c2602dd 100644
--- a/pugl/pugl.hpp
+++ b/pugl/pugl.hpp
@@ -1,5 +1,5 @@
/*
- Copyright 2012-2015 David Robillard <http://drobilla.net>
+ Copyright 2012-2019 David Robillard <http://drobilla.net>
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
@@ -15,7 +15,7 @@
*/
/**
- @file pugl.hpp C++ API for Pugl, a minimal portable API for OpenGL.
+ @file pugl.hpp Public Pugl C++ API wrapper.
*/
#ifndef PUGL_HPP_INCLUDED
@@ -66,8 +66,8 @@ public:
puglInitTransientFor(_view, parent);
}
- virtual void initContextType(PuglContextType type) {
- puglInitContextType(_view, type);
+ virtual void initBackend(const PuglBackend* backend) {
+ puglInitBackend(_view, backend);
}
virtual void createWindow(const char* title) {
@@ -83,8 +83,7 @@ public:
virtual void* getContext() { return puglGetContext(_view); }
virtual void ignoreKeyRepeat(bool ignore) { puglIgnoreKeyRepeat(_view, ignore); }
virtual void grabFocus() { puglGrabFocus(_view); }
- virtual void copyToClipboard(char* selection) { puglCopyToClipboard(_view, selection); }
- virtual const char* pasteFromClipboard() { return puglPasteFromClipboard(_view); }
+ virtual void requestAttention() { puglRequestAttention(_view); }
virtual PuglStatus waitForEvent() { return puglWaitForEvent(_view); }
virtual PuglStatus processEvents() { return puglProcessEvents(_view); }
virtual void postRedisplay() { puglPostRedisplay(_view); }
diff --git a/pugl/pugl_cairo_backend.h b/pugl/pugl_cairo_backend.h
new file mode 100644
index 0000000..3330c08
--- /dev/null
+++ b/pugl/pugl_cairo_backend.h
@@ -0,0 +1,42 @@
+/*
+ Copyright 2012-2019 David Robillard <http://drobilla.net>
+
+ Permission to use, copy, modify, and/or distribute this software for any
+ purpose with or without fee is hereby granted, provided that the above
+ copyright notice and this permission notice appear in all copies.
+
+ THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+*/
+
+/**
+ @file pugl_cairo_backend.h Declaration of Cairo backend accessor.
+*/
+
+#ifndef PUGL_CAIRO_BACKEND_H
+#define PUGL_CAIRO_BACKEND_H
+
+#include "pugl/pugl.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ Cairo graphics backend accessor.
+
+ Pass the return value to puglInitBackend() to draw to a view with Cairo.
+*/
+PUGL_API const PuglBackend*
+puglCairoBackend(void);
+
+#ifdef __cplusplus
+} /* extern "C" */
+#endif
+
+#endif // PUGL_CAIRO_BACKEND_H
diff --git a/pugl/pugl_gl_backend.h b/pugl/pugl_gl_backend.h
new file mode 100644
index 0000000..5913b95
--- /dev/null
+++ b/pugl/pugl_gl_backend.h
@@ -0,0 +1,42 @@
+/*
+ Copyright 2012-2019 David Robillard <http://drobilla.net>
+
+ Permission to use, copy, modify, and/or distribute this software for any
+ purpose with or without fee is hereby granted, provided that the above
+ copyright notice and this permission notice appear in all copies.
+
+ THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+*/
+
+/**
+ @file pugl_gl_backend.h Declaration of OpenGL backend accessor.
+*/
+
+#ifndef PUGL_GL_BACKEND_H
+#define PUGL_GL_BACKEND_H
+
+#include "pugl/pugl.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ OpenGL graphics backend.
+
+ Pass the return value to puglInitBackend() to draw to a view with OpenGL.
+*/
+PUGL_API const PuglBackend*
+puglGlBackend(void);
+
+#ifdef __cplusplus
+} /* extern "C" */
+#endif
+
+#endif // PUGL_GL_BACKEND_H
diff --git a/pugl/pugl_osx.m b/pugl/pugl_osx.m
deleted file mode 100644
index 014d553..0000000
--- a/pugl/pugl_osx.m
+++ /dev/null
@@ -1,746 +0,0 @@
-/*
- Copyright 2012-2016 David Robillard <http://drobilla.net>
-
- Permission to use, copy, modify, and/or distribute this software for any
- purpose with or without fee is hereby granted, provided that the above
- copyright notice and this permission notice appear in all copies.
-
- THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
- WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
- MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
- ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
- WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
- ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
- OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
-*/
-
-/**
- @file pugl_osx.m OSX/Cocoa Pugl Implementation.
-*/
-
-#include <stdlib.h>
-
-#import <Cocoa/Cocoa.h>
-
-#include "pugl/cairo_gl.h"
-#include "pugl/gl.h"
-#include "pugl/pugl_internal.h"
-
-@class PuglOpenGLView;
-
-struct PuglInternalsImpl {
- NSApplication* app;
- PuglOpenGLView* glview;
- id window;
- NSEvent* nextEvent;
- unsigned mods;
-#ifdef PUGL_HAVE_CAIRO
- cairo_surface_t* surface;
- cairo_t* cr;
- PuglCairoGL cairo_gl;
-#endif
-};
-
-@interface PuglWindow : NSWindow
-{
-@public
- PuglView* puglview;
-}
-
-- (id) initWithContentRect:(NSRect)contentRect
- styleMask:(unsigned int)aStyle
- backing:(NSBackingStoreType)bufferingType
- defer:(BOOL)flag;
-- (void) setPuglview:(PuglView*)view;
-- (BOOL) windowShouldClose:(id)sender;
-- (BOOL) canBecomeKeyWindow:(id)sender;
-@end
-
-@implementation PuglWindow
-
-- (id)initWithContentRect:(NSRect)contentRect
- styleMask:(unsigned int)aStyle
- backing:(NSBackingStoreType)bufferingType
- defer:(BOOL)flag
-{
- if (![super initWithContentRect:contentRect
- styleMask:(NSClosableWindowMask |
- NSTitledWindowMask |
- NSResizableWindowMask)
- backing:NSBackingStoreBuffered defer:NO]) {
- return nil;
- }
-
- [self setAcceptsMouseMovedEvents:YES];
- return (PuglWindow*)self;
-}
-
-- (void)setPuglview:(PuglView*)view
-{
- puglview = view;
- [self setContentSize:NSMakeSize(view->width, view->height)];
-}
-
-- (BOOL)windowShouldClose:(id)sender
-{
- const PuglEventClose ev = {
- PUGL_CLOSE,
- puglview,
- 0
- };
- puglDispatchEvent(puglview, (PuglEvent*)&ev);
-
- return YES;
-}
-
-- (BOOL) canBecomeKeyWindow
-{
- return YES;
-}
-
-- (BOOL) canBecomeMainWindow
-{
- return YES;
-}
-
-- (BOOL) canBecomeKeyWindow:(id)sender
-{
- return NO;
-}
-
-@end
-
-@interface PuglOpenGLView : NSOpenGLView
-{
-@public
- PuglView* puglview;
-
- NSTrackingArea* trackingArea;
-}
-
-- (id) initWithFrame:(NSRect)frame;
-- (void) reshape;
-- (void) drawRect:(NSRect)rect;
-- (NSPoint) eventLocation:(NSEvent*)event;
-- (void) mouseEntered:(NSEvent*)event;
-- (void) mouseExited:(NSEvent*)event;
-- (void) mouseMoved:(NSEvent*)event;
-- (void) mouseDragged:(NSEvent*)event;
-- (void) rightMouseDragged:(NSEvent*)event;
-- (void) mouseDown:(NSEvent*)event;
-- (void) mouseUp:(NSEvent*)event;
-- (void) rightMouseDragged:(NSEvent*)event;
-- (void) rightMouseDown:(NSEvent*)event;
-- (void) rightMouseUp:(NSEvent*)event;
-- (void) otherMouseDragged:(NSEvent*)event;
-- (void) otherMouseDown:(NSEvent*)event;
-- (void) otherMouseUp:(NSEvent*)event;
-- (void) scrollWheel:(NSEvent*)event;
-- (void) keyDown:(NSEvent*)event;
-- (void) keyUp:(NSEvent*)event;
-- (void) flagsChanged:(NSEvent*)event;
-
-@end
-
-@implementation PuglOpenGLView
-
-- (id) initWithFrame:(NSRect)frame
-{
- NSOpenGLPixelFormatAttribute pixelAttribs[16] = {
- NSOpenGLPFADoubleBuffer,
- NSOpenGLPFAAccelerated,
- NSOpenGLPFAColorSize, 32,
- NSOpenGLPFADepthSize, 32,
- NSOpenGLPFAMultisample,
- NSOpenGLPFASampleBuffers, 1,
- NSOpenGLPFASamples, 4,
- 0
- };
-
- NSOpenGLPixelFormat* pixelFormat = [
- [NSOpenGLPixelFormat alloc] initWithAttributes:pixelAttribs];
-
- if (pixelFormat) {
- self = [super initWithFrame:frame pixelFormat:pixelFormat];
- [pixelFormat release];
- } else {
- self = [super initWithFrame:frame];
- }
-
- if (self) {
- [[self openGLContext] makeCurrentContext];
- [self reshape];
- [NSOpenGLContext clearCurrentContext];
- }
- return self;
-}
-
-- (void) reshape
-{
- [[self openGLContext] update];
-
- if (!puglview) {
- return;
- }
-
- const NSRect bounds = [self bounds];
- const PuglEventConfigure ev = {
- PUGL_CONFIGURE,
- puglview,
- 0,
- bounds.origin.x,
- bounds.origin.y,
- bounds.size.width,
- bounds.size.height,
- };
-
-#ifdef PUGL_HAVE_CAIRO
- PuglInternals* impl = puglview->impl;
- if (puglview->ctx_type & PUGL_CAIRO) {
- cairo_surface_destroy(impl->surface);
- cairo_destroy(impl->cr);
- impl->surface = pugl_cairo_gl_create(
- &impl->cairo_gl, ev.width, ev.height, 4);
- impl->cr = cairo_create(impl->surface);
- pugl_cairo_gl_configure(&impl->cairo_gl, ev.width, ev.height);
- }
-#endif
-
- puglDispatchEvent(puglview, (PuglEvent*)&ev);
-}
-
-- (void) drawRect:(NSRect)rect
-{
- const PuglEventExpose ev = {
- PUGL_EXPOSE,
- puglview,
- 0,
- rect.origin.x,
- rect.origin.y,
- rect.size.width,
- rect.size.height,
- 0
- };
-
- puglDispatchEvent(puglview, (const PuglEvent*)&ev);
-
-#ifdef PUGL_HAVE_CAIRO
- if (puglview->ctx_type & PUGL_CAIRO) {
- pugl_cairo_gl_draw(
- &puglview->impl->cairo_gl, puglview->width, puglview->height);
- }
-#endif
-}
-
-- (BOOL) acceptsFirstResponder
-{
- return YES;
-}
-
-static unsigned
-getModifiers(PuglView* view, NSEvent* ev)
-{
- const unsigned modifierFlags = [ev modifierFlags];
-
- unsigned mods = 0;
- mods |= (modifierFlags & NSShiftKeyMask) ? PUGL_MOD_SHIFT : 0;
- mods |= (modifierFlags & NSControlKeyMask) ? PUGL_MOD_CTRL : 0;
- mods |= (modifierFlags & NSAlternateKeyMask) ? PUGL_MOD_ALT : 0;
- mods |= (modifierFlags & NSCommandKeyMask) ? PUGL_MOD_SUPER : 0;
- return mods;
-}
-
-static PuglKey
-keySymToSpecial(PuglView* view, NSEvent* ev)
-{
- NSString* chars = [ev charactersIgnoringModifiers];
- if([chars length] == 1) {
- switch ([chars characterAtIndex:0]) {
- case NSF1FunctionKey: return PUGL_KEY_F1;
- case NSF2FunctionKey: return PUGL_KEY_F2;
- case NSF3FunctionKey: return PUGL_KEY_F3;
- case NSF4FunctionKey: return PUGL_KEY_F4;
- case NSF5FunctionKey: return PUGL_KEY_F5;
- case NSF6FunctionKey: return PUGL_KEY_F6;
- case NSF7FunctionKey: return PUGL_KEY_F7;
- case NSF8FunctionKey: return PUGL_KEY_F8;
- case NSF9FunctionKey: return PUGL_KEY_F9;
- case NSF10FunctionKey: return PUGL_KEY_F10;
- case NSF11FunctionKey: return PUGL_KEY_F11;
- case NSF12FunctionKey: return PUGL_KEY_F12;
- case NSLeftArrowFunctionKey: return PUGL_KEY_LEFT;
- case NSUpArrowFunctionKey: return PUGL_KEY_UP;
- case NSRightArrowFunctionKey: return PUGL_KEY_RIGHT;
- case NSDownArrowFunctionKey: return PUGL_KEY_DOWN;
- case NSPageUpFunctionKey: return PUGL_KEY_PAGE_UP;
- case NSPageDownFunctionKey: return PUGL_KEY_PAGE_DOWN;
- case NSHomeFunctionKey: return PUGL_KEY_HOME;
- case NSEndFunctionKey: return PUGL_KEY_END;
- case NSInsertFunctionKey: return PUGL_KEY_INSERT;
- /* special keys (SHIFT, CTRL, ALT, SUPER) are handled in [flagsChanged] */
- }
- }
- return (PuglKey)0;
-}
-
--(void)updateTrackingAreas
-{
- if (trackingArea != nil) {
- [self removeTrackingArea:trackingArea];
- [trackingArea release];
- }
-
- const int opts = (NSTrackingMouseEnteredAndExited |
- NSTrackingMouseMoved |
- NSTrackingActiveAlways);
- trackingArea = [ [NSTrackingArea alloc] initWithRect:[self bounds]
- options:opts
- owner:self
- userInfo:nil];
- [self addTrackingArea:trackingArea];
-}
-
-- (NSPoint) eventLocation:(NSEvent*)event
-{
- return [self convertPoint:[event locationInWindow] fromView:nil];
-}
-
-- (void)mouseEntered:(NSEvent*)theEvent
-{
- [self updateTrackingAreas];
-}
-
-- (void)mouseExited:(NSEvent*)theEvent
-{
-}
-
-- (void) mouseMoved:(NSEvent*)event
-{
- const NSPoint wloc = [self eventLocation:event];
- const NSPoint rloc = [NSEvent mouseLocation];
- const PuglEventMotion ev = {
- PUGL_MOTION_NOTIFY,
- puglview,
- 0,
- [event timestamp],
- wloc.x,
- puglview->height - wloc.y,
- rloc.x,
- [[NSScreen mainScreen] frame].size.height - rloc.y,
- getModifiers(puglview, event),
- 0,
- 1
- };
- puglDispatchEvent(puglview, (PuglEvent*)&ev);
-}
-
-- (void) mouseDragged:(NSEvent*)event
-{
- [self mouseMoved: event];
-}
-
-- (void) rightMouseDragged:(NSEvent*)event
-{
- [self mouseMoved: event];
-}
-
-- (void) otherMouseDragged:(NSEvent*)event
-{
- [self mouseMoved: event];
-}
-
-- (void) mouseDown:(NSEvent*)event
-{
- const NSPoint wloc = [self eventLocation:event];
- const NSPoint rloc = [NSEvent mouseLocation];
- const PuglEventButton ev = {
- PUGL_BUTTON_PRESS,
- puglview,
- 0,
- [event timestamp],
- wloc.x,
- puglview->height - wloc.y,
- rloc.x,
- [[NSScreen mainScreen] frame].size.height - rloc.y,
- getModifiers(puglview, event),
- [event buttonNumber] + 1
- };
- puglDispatchEvent(puglview, (PuglEvent*)&ev);
-}
-
-- (void) mouseUp:(NSEvent*)event
-{
- const NSPoint wloc = [self eventLocation:event];
- const NSPoint rloc = [NSEvent mouseLocation];
- const PuglEventButton ev = {
- PUGL_BUTTON_RELEASE,
- puglview,
- 0,
- [event timestamp],
- wloc.x,
- puglview->height - wloc.y,
- rloc.x,
- [[NSScreen mainScreen] frame].size.height - rloc.y,
- getModifiers(puglview, event),
- [event buttonNumber] + 1
- };
- puglDispatchEvent(puglview, (PuglEvent*)&ev);
- [self updateTrackingAreas];
-}
-
-- (void) rightMouseDown:(NSEvent*)event
-{
- [self mouseDown: event];
-}
-
-- (void) rightMouseUp:(NSEvent*)event
-{
- [self mouseUp: event];
-}
-
-- (void) otherMouseDown:(NSEvent*)event
-{
- [self mouseDown: event];
-}
-
-- (void) otherMouseUp:(NSEvent*)event
-{
- [self mouseUp: event];
-}
-
-- (void) scrollWheel:(NSEvent*)event
-{
- [self updateTrackingAreas];
-
- const NSPoint wloc = [self eventLocation:event];
- const NSPoint rloc = [NSEvent mouseLocation];
- const PuglEventScroll ev = {
- PUGL_SCROLL,
- puglview,
- 0,
- [event timestamp],
- wloc.x,
- puglview->height - wloc.y,
- rloc.x,
- [[NSScreen mainScreen] frame].size.height - rloc.y,
- getModifiers(puglview, event),
- [event deltaX],
- [event deltaY]
- };
- puglDispatchEvent(puglview, (PuglEvent*)&ev);
- [self updateTrackingAreas];
-}
-
-- (void) keyDown:(NSEvent*)event
-{
- if (puglview->ignoreKeyRepeat && [event isARepeat]) {
- return;
- }
-
- const NSPoint wloc = [self eventLocation:event];
- const NSPoint rloc = [NSEvent mouseLocation];
- const NSString* chars = [event characters];
- const char* str = [chars UTF8String];
- PuglEventKey ev = {
- PUGL_KEY_PRESS,
- puglview,
- 0,
- [event timestamp],
- wloc.x,
- puglview->height - wloc.y,
- rloc.x,
- [[NSScreen mainScreen] frame].size.height - rloc.y,
- getModifiers(puglview, event),
- [event keyCode],
- puglDecodeUTF8((const uint8_t*)str),
- keySymToSpecial(puglview, event),
- { 0, 0, 0, 0, 0, 0, 0, 0 },
- false
- };
- strncpy((char*)ev.utf8, str, 8);
- puglDispatchEvent(puglview, (PuglEvent*)&ev);
-}
-
-- (void) keyUp:(NSEvent*)event
-{
- const NSPoint wloc = [self eventLocation:event];
- const NSPoint rloc = [NSEvent mouseLocation];
- const NSString* chars = [event characters];
- const char* str = [chars UTF8String];
- const PuglEventKey ev = {
- PUGL_KEY_RELEASE,
- puglview,
- 0,
- [event timestamp],
- wloc.x,
- puglview->height - wloc.y,
- rloc.x,
- [[NSScreen mainScreen] frame].size.height - rloc.y,
- getModifiers(puglview, event),
- [event keyCode],
- puglDecodeUTF8((const uint8_t*)str),
- keySymToSpecial(puglview, event),
- { 0, 0, 0, 0, 0, 0, 0, 0 },
- false,
- };
- strncpy((char*)ev.utf8, str, 8);
- puglDispatchEvent(puglview, (PuglEvent*)&ev);
-}
-
-- (void) flagsChanged:(NSEvent*)event
-{
- const unsigned mods = getModifiers(puglview, event);
- PuglEventType type = PUGL_NOTHING;
- PuglKey special = 0;
-
- if ((mods & PUGL_MOD_SHIFT) != (puglview->impl->mods & PUGL_MOD_SHIFT)) {
- type = mods & PUGL_MOD_SHIFT ? PUGL_KEY_PRESS : PUGL_KEY_RELEASE;
- special = PUGL_KEY_SHIFT;
- } else if ((mods & PUGL_MOD_CTRL) != (puglview->impl->mods & PUGL_MOD_CTRL)) {
- type = mods & PUGL_MOD_CTRL ? PUGL_KEY_PRESS : PUGL_KEY_RELEASE;
- special = PUGL_KEY_CTRL;
- } else if ((mods & PUGL_MOD_ALT) != (puglview->impl->mods & PUGL_MOD_ALT)) {
- type = mods & PUGL_MOD_ALT ? PUGL_KEY_PRESS : PUGL_KEY_RELEASE;
- special = PUGL_KEY_ALT;
- } else if ((mods & PUGL_MOD_SUPER) != (puglview->impl->mods & PUGL_MOD_SUPER)) {
- type = mods & PUGL_MOD_SUPER ? PUGL_KEY_PRESS : PUGL_KEY_RELEASE;
- special = PUGL_KEY_SUPER;
- }
-
- if (special != 0) {
- const NSPoint wloc = [self eventLocation:event];
- const NSPoint rloc = [NSEvent mouseLocation];
- PuglEventKey ev = {
- type,
- puglview,
- 0,
- [event timestamp],
- wloc.x,
- puglview->height - wloc.y,
- rloc.x,
- [[NSScreen mainScreen] frame].size.height - rloc.y,
- mods,
- [event keyCode],
- 0,
- special,
- { 0, 0, 0, 0, 0, 0, 0, 0 },
- false
- };
- puglDispatchEvent(puglview, (PuglEvent*)&ev);
- }
-
- puglview->impl->mods = mods;
-}
-
-@end
-
-PuglInternals*
-puglInitInternals(void)
-{
- return (PuglInternals*)calloc(1, sizeof(PuglInternals));
-}
-
-void
-puglEnterContext(PuglView* view)
-{
- [[view->impl->glview openGLContext] makeCurrentContext];
-}
-
-void
-puglLeaveContext(PuglView* view, bool flush)
-{
-#ifdef PUGL_HAVE_CAIRO
- if (view->ctx_type & PUGL_CAIRO) {
- pugl_cairo_gl_draw(&view->impl->cairo_gl, view->width, view->height);
- }
-#endif
-
- if (flush) {
- //[[view->impl->glview openGLContext] flushBuffer];
- glFlush();
- glSwapAPPLE();
- }
- [NSOpenGLContext clearCurrentContext];
-}
-
-int
-puglCreateWindow(PuglView* view, const char* title)
-{
- PuglInternals* impl = view->impl;
-
- [NSAutoreleasePool new];
- impl->app = [NSApplication sharedApplication];
-
- impl->glview = [PuglOpenGLView new];
- impl->glview->puglview = view;
-
- if (view->transient_parent) {
- NSView* pview = (NSView*)view->transient_parent;
- [pview addSubview:impl->glview];
- [impl->glview setHidden:NO];
- } else {
- NSString* titleString = [[NSString alloc]
- initWithBytes:title
- length:strlen(title)
- encoding:NSUTF8StringEncoding];
-
- id window = [[PuglWindow new] retain];
- [window setPuglview:view];
- [window setTitle:titleString];
- if (view->min_width || view->min_height) {
- [window setContentMinSize:NSMakeSize(view->min_width,
- view->min_height)];
- }
- impl->window = window;
-
- [window setContentView:impl->glview];
- [impl->app activateIgnoringOtherApps:YES];
- [window makeFirstResponder:impl->glview];
- [window makeKeyAndOrderFront:window];
-#if 0
- if (resizable) {
- [impl->glview setAutoresizingMask:NSViewWidthSizable|NSViewHeightSizable];
- }
-#endif
- }
-
- return 0;
-}
-
-void
-puglShowWindow(PuglView* view)
-{
- [view->impl->window setIsVisible:YES];
- view->visible = true;
-}
-
-void
-puglHideWindow(PuglView* view)
-{
- [view->impl->window setIsVisible:NO];
- view->visible = false;
-}
-
-void
-puglDestroy(PuglView* view)
-{
-#ifdef PUGL_HAVE_CAIRO
- pugl_cairo_gl_free(&view->impl->cairo_gl);
-#endif
- view->impl->glview->puglview = NULL;
- [view->impl->glview removeFromSuperview];
- if (view->impl->window) {
- [view->impl->window close];
- }
- [view->impl->glview release];
- if (view->impl->window) {
- [view->impl->window release];
- }
- puglClearSelection(view);
- free(view->windowClass);
- free(view->impl);
- free(view);
-}
-
-void
-puglCopyToClipboard(PuglView* view, const char* selection, size_t len)
-{
- PuglInternals* const impl = view->impl;
-
- puglSetSelection(view, selection, len);
-
- NSPasteboard* pasteboard = [NSPasteboard generalPasteboard];
- if(pasteboard) {
- [pasteboard
- declareTypes:[NSArray arrayWithObjects:NSStringPboardType, nil]
- owner:nil];
- [pasteboard
- setString:[NSString stringWithUTF8String:puglGetSelection(view, NULL)]
- forType:NSStringPboardType];
- }
-}
-
-const char*
-puglPasteFromClipboard(PuglView* view, size_t* len)
-{
- PuglInternals* const impl = view->impl;
-
- NSPasteboard* pasteboard = [NSPasteboard generalPasteboard];
- if(pasteboard && [ [pasteboard types] containsObject:NSStringPboardType] ) {
- const NSString* mem = [pasteboard stringForType:NSStringPboardType];
- if(mem) {
- const char *utf8 = [mem UTF8String];
- if(utf8)
- puglSetSelection(view, utf8, strlen(utf8));
- }
- }
-
- return puglGetSelection(view, len);
-}
-
-void
-puglGrabFocus(PuglView* view)
-{
- // TODO
-}
-
-PuglStatus
-puglWaitForEvent(PuglView* view)
-{
- /* OSX supposedly has queue: and untilDate: selectors that can be used for
- a blocking non-queueing event check, but if used here cause an
- unsupported selector error at runtime. I have no idea why, so just get
- the event and keep it around until the call to puglProcessEvents. */
- if (!view->impl->nextEvent) {
- view->impl->nextEvent = [view->impl->window
- nextEventMatchingMask: NSAnyEventMask];
- }
-
- return PUGL_SUCCESS;
-}
-
-PuglStatus
-puglProcessEvents(PuglView* view)
-{
- while (true) {
- // Get the next event, or use the cached one from puglWaitForEvent
- if (!view->impl->nextEvent) {
- view->impl->nextEvent = [view->impl->window
- nextEventMatchingMask: NSAnyEventMask];
- }
-
- if (!view->impl->nextEvent) {
- break; // No events to process, done
- }
-
- // Dispatch event
- [view->impl->app sendEvent: view->impl->nextEvent];
- view->impl->nextEvent = NULL;
- }
-
- return PUGL_SUCCESS;
-}
-
-void
-puglPostRedisplay(PuglView* view)
-{
- //view->redisplay = true; // unused
- [view->impl->glview setNeedsDisplay: YES];
-}
-
-PuglNativeWindow
-puglGetNativeWindow(PuglView* view)
-{
- return (PuglNativeWindow)view->impl->glview;
-}
-
-void*
-puglGetContext(PuglView* view)
-{
-#ifdef PUGL_HAVE_CAIRO
- if (view->ctx_type & PUGL_CAIRO) {
- return view->impl->cr;
- }
-#endif
- return NULL;
-}
diff --git a/pugl/pugl_win.cpp b/pugl/pugl_win.cpp
deleted file mode 100644
index 6468963..0000000
--- a/pugl/pugl_win.cpp
+++ /dev/null
@@ -1,704 +0,0 @@
-/*
- Copyright 2012-2015 David Robillard <http://drobilla.net>
-
- Permission to use, copy, modify, and/or distribute this software for any
- purpose with or without fee is hereby granted, provided that the above
- copyright notice and this permission notice appear in all copies.
-
- THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
- WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
- MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
- ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
- WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
- ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
- OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
-*/
-
-/**
- @file pugl_win.cpp Windows/WGL Pugl Implementation.
-*/
-
-#include <windows.h>
-#include <windowsx.h>
-#include <GL/gl.h>
-
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <wctype.h>
-
-#include "pugl/pugl_internal.h"
-
-#ifndef WM_MOUSEWHEEL
-# define WM_MOUSEWHEEL 0x020A
-#endif
-#ifndef WM_MOUSEHWHEEL
-# define WM_MOUSEHWHEEL 0x020E
-#endif
-#ifndef WHEEL_DELTA
-# define WHEEL_DELTA 120
-#endif
-#ifdef _WIN64
-# ifndef GWLP_USERDATA
-# define GWLP_USERDATA (-21)
-# endif
-#else
-# ifndef GWL_USERDATA
-# define GWL_USERDATA (-21)
-# endif
-#endif
-
-#define PUGL_LOCAL_CLOSE_MSG (WM_USER + 50)
-
-struct PuglInternalsImpl {
- HWND hwnd;
- HDC hdc;
- HGLRC hglrc;
- WNDCLASS wc;
-};
-
-LRESULT CALLBACK
-wndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam);
-
-PuglView*
-puglInit()
-{
- PuglView* view = (PuglView*)calloc(1, sizeof(PuglView));
- PuglInternals* impl = (PuglInternals*)calloc(1, sizeof(PuglInternals));
- if (!view || !impl) {
- return NULL;
- }
-
- view->impl = impl;
- view->width = 640;
- view->height = 480;
-
- return view;
-}
-
-PuglInternals*
-puglInitInternals(void)
-{
- return (PuglInternals*)calloc(1, sizeof(PuglInternals));
-}
-
-void
-puglEnterContext(PuglView* view)
-{
- PAINTSTRUCT ps;
- BeginPaint(view->impl->hwnd, &ps);
-
-#ifdef PUGL_HAVE_GL
- if (view->ctx_type == PUGL_GL) {
- wglMakeCurrent(view->impl->hdc, view->impl->hglrc);
- }
-#endif
-}
-
-void
-puglLeaveContext(PuglView* view, bool flush)
-{
-#ifdef PUGL_HAVE_GL
- if (view->ctx_type == PUGL_GL && flush) {
- glFlush();
- SwapBuffers(view->impl->hdc);
- }
-#endif
-
- PAINTSTRUCT ps;
- EndPaint(view->impl->hwnd, &ps);
-}
-
-int
-puglCreateWindow(PuglView* view, const char* title)
-{
- static const TCHAR* DEFAULT_CLASSNAME = "Pugl";
-
- PuglInternals* impl = view->impl;
-
- if (!title) {
- title = "Window";
- }
-
- WNDCLASSEX wc;
- memset(&wc, 0, sizeof(wc));
- wc.cbSize = sizeof(wc);
- wc.style = CS_OWNDC;
- wc.lpfnWndProc = wndProc;
- wc.hInstance = GetModuleHandle(NULL);
- wc.hIcon = LoadIcon(NULL, IDI_APPLICATION); // TODO: user-specified icon
- wc.hCursor = LoadCursor(NULL, IDC_ARROW);
- wc.hbrBackground = (HBRUSH)GetStockObject(BLACK_BRUSH);
- wc.lpszClassName = view->windowClass ? view->windowClass : DEFAULT_CLASSNAME;
- if (!RegisterClassEx(&wc)) {
- free((void*)impl->wc.lpszClassName);
- free(impl);
- free(view);
- return NULL;
- }
-
- int winFlags = WS_POPUPWINDOW | WS_CAPTION;
- if (view->resizable) {
- winFlags |= WS_SIZEBOX;
- if (view->min_width || view->min_height) {
- // Adjust the minimum window size to accomodate requested view size
- RECT mr = { 0, 0, view->min_width, view->min_height };
- AdjustWindowRectEx(&mr, winFlags, FALSE, WS_EX_TOPMOST);
- view->min_width = mr.right - mr.left;
- view->min_height = mr.bottom - mr.top;
- }
- }
-
- // Adjust the window size to accomodate requested view size
- RECT wr = { 0, 0, view->width, view->height };
- AdjustWindowRectEx(&wr, winFlags, FALSE, WS_EX_TOPMOST);
-
- impl->hwnd = CreateWindowEx(
- WS_EX_TOPMOST,
- wc.lpszClassName, title,
- (view->parent ? WS_CHILD : winFlags),
- CW_USEDEFAULT, CW_USEDEFAULT, wr.right-wr.left, wr.bottom-wr.top,
- (HWND)view->parent, NULL, NULL, NULL);
-
- if (!impl->hwnd) {
- free((void*)impl->wc.lpszClassName);
- free(impl);
- free(view);
- return 1;
- }
-
-#ifdef _WIN64
- SetWindowLongPtr(impl->hwnd, GWLP_USERDATA, (LONG_PTR)view);
-#else
- SetWindowLongPtr(impl->hwnd, GWL_USERDATA, (LONG)view);
-#endif
-
- impl->hdc = GetDC(impl->hwnd);
-
- PIXELFORMATDESCRIPTOR pfd;
- ZeroMemory(&pfd, sizeof(pfd));
- pfd.nSize = sizeof(pfd);
- pfd.nVersion = 1;
- pfd.dwFlags = PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER;
- pfd.iPixelType = PFD_TYPE_RGBA;
- pfd.cColorBits = 24;
- pfd.cDepthBits = 16;
- pfd.iLayerType = PFD_MAIN_PLANE;
-
- int format = ChoosePixelFormat(impl->hdc, &pfd);
- SetPixelFormat(impl->hdc, format, &pfd);
-
- impl->hglrc = wglCreateContext(impl->hdc);
- if (!impl->hglrc) {
- ReleaseDC(impl->hwnd, impl->hdc);
- DestroyWindow(impl->hwnd);
- UnregisterClass(impl->wc.lpszClassName, NULL);
- free((void*)impl->wc.lpszClassName);
- free(impl);
- free(view);
- return NULL;
- }
- wglMakeCurrent(impl->hdc, impl->hglrc);
-
- return 0;
-}
-
-void
-puglShowWindow(PuglView* view)
-{
- PuglInternals* impl = view->impl;
-
- ShowWindow(impl->hwnd, SW_SHOWNORMAL);
- view->visible = true;
-}
-
-void
-puglHideWindow(PuglView* view)
-{
- PuglInternals* impl = view->impl;
-
- ShowWindow(impl->hwnd, SW_HIDE);
- view->visible = false;
-}
-
-void
-puglDestroy(PuglView* view)
-{
- wglMakeCurrent(NULL, NULL);
- wglDeleteContext(view->impl->hglrc);
- ReleaseDC(view->impl->hwnd, view->impl->hdc);
- DestroyWindow(view->impl->hwnd);
- UnregisterClass(view->impl->wc.lpszClassName, NULL);
- puglClearSelection(view);
- free(view->windowClass);
- free(view->impl);
- free(view);
-}
-
-static PuglKey
-keySymToSpecial(int sym)
-{
- switch (sym) {
- case VK_F1: return PUGL_KEY_F1;
- case VK_F2: return PUGL_KEY_F2;
- case VK_F3: return PUGL_KEY_F3;
- case VK_F4: return PUGL_KEY_F4;
- case VK_F5: return PUGL_KEY_F5;
- case VK_F6: return PUGL_KEY_F6;
- case VK_F7: return PUGL_KEY_F7;
- case VK_F8: return PUGL_KEY_F8;
- case VK_F9: return PUGL_KEY_F9;
- case VK_F10: return PUGL_KEY_F10;
- case VK_F11: return PUGL_KEY_F11;
- case VK_F12: return PUGL_KEY_F12;
- case VK_LEFT: return PUGL_KEY_LEFT;
- case VK_UP: return PUGL_KEY_UP;
- case VK_RIGHT: return PUGL_KEY_RIGHT;
- case VK_DOWN: return PUGL_KEY_DOWN;
- case VK_PRIOR: return PUGL_KEY_PAGE_UP;
- case VK_NEXT: return PUGL_KEY_PAGE_DOWN;
- case VK_HOME: return PUGL_KEY_HOME;
- case VK_END: return PUGL_KEY_END;
- case VK_INSERT: return PUGL_KEY_INSERT;
- case VK_SHIFT: return PUGL_KEY_SHIFT;
- case VK_CONTROL: return PUGL_KEY_CTRL;
- case VK_MENU: return PUGL_KEY_ALT;
- case VK_LWIN: return PUGL_KEY_SUPER;
- case VK_RWIN: return PUGL_KEY_SUPER;
- }
- return (PuglKey)0;
-}
-
-static unsigned int
-getModifiers()
-{
- unsigned int mods = 0;
- mods |= (GetKeyState(VK_SHIFT) < 0) ? PUGL_MOD_SHIFT : 0;
- mods |= (GetKeyState(VK_CONTROL) < 0) ? PUGL_MOD_CTRL : 0;
- mods |= (GetKeyState(VK_MENU) < 0) ? PUGL_MOD_ALT : 0;
- mods |= (GetKeyState(VK_LWIN) < 0) ? PUGL_MOD_SUPER : 0;
- mods |= (GetKeyState(VK_RWIN) < 0) ? PUGL_MOD_SUPER : 0;
- return mods;
-}
-
-static void
-initMouseEvent(PuglEvent* event,
- PuglView* view,
- int button,
- bool press,
- LPARAM lParam)
-{
- POINT pt = { GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam) };
- ClientToScreen(view->impl->hwnd, &pt);
-
- if (press) {
- SetCapture(view->impl->hwnd);
- } else {
- ReleaseCapture();
- }
-
- event->button.time = GetMessageTime();
- event->button.type = press ? PUGL_BUTTON_PRESS : PUGL_BUTTON_RELEASE;
- event->button.x = GET_X_LPARAM(lParam);
- event->button.y = GET_Y_LPARAM(lParam);
- event->button.x_root = pt.x;
- event->button.y_root = pt.y;
- event->button.state = getModifiers();
- event->button.button = button;
-}
-
-static void
-initScrollEvent(PuglEvent* event, PuglView* view, LPARAM lParam, WPARAM wParam)
-{
- POINT pt = { GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam) };
- ScreenToClient(view->impl->hwnd, &pt);
-
- event->scroll.time = GetMessageTime();
- event->scroll.type = PUGL_SCROLL;
- event->scroll.x = pt.x;
- event->scroll.y = pt.y;
- event->scroll.x_root = GET_X_LPARAM(lParam);
- event->scroll.y_root = GET_Y_LPARAM(lParam);
- event->scroll.state = getModifiers();
- event->scroll.dx = 0;
- event->scroll.dy = 0;
-}
-
-static unsigned int
-utf16_to_code_point(const wchar_t* input, size_t input_size)
-{
- unsigned int code_unit = *input;
- // Equiv. range check between 0xD800 to 0xDBFF inclusive
- if ((code_unit & 0xFC00) == 0xD800) {
- if (input_size < 2) {
- // "Error: is surrogate but input_size too small"
- return 0xFFFD; // replacement character
- }
-
- unsigned int code_unit_2 = *++input;
- // Equiv. range check between 0xDC00 to 0xDFFF inclusive
- if ((code_unit_2 & 0xFC00) == 0xDC00) {
- return (code_unit << 10) + code_unit_2 - 0x35FDC00;
- }
-
- // TODO: push_back(code_unit_2);
- // "Error: Unpaired surrogates."
- return 0xFFFD; // replacement character
- }
- return code_unit;
-}
-
-static void
-initKeyEvent(PuglEvent* event, PuglView* view, bool press, LPARAM lParam)
-{
- POINT rpos = { 0, 0 };
- GetCursorPos(&rpos);
-
- POINT cpos = { rpos.x, rpos.y };
- ScreenToClient(view->impl->hwnd, &rpos);
-
- event->key.type = press ? PUGL_KEY_PRESS : PUGL_KEY_RELEASE;
- event->key.time = GetMessageTime();
- event->key.state = getModifiers();
- event->key.x_root = rpos.x;
- event->key.y_root = rpos.y;
- event->key.x = cpos.x;
- event->key.y = cpos.y;
- event->key.keycode = (lParam & 0xFF0000) >> 16;
- event->key.character = 0;
- event->key.special = static_cast<PuglKey>(0);
- event->key.filter = 0;
-}
-
-static void
-wcharBufToEvent(wchar_t* buf, int n, PuglEvent* event)
-{
- if (n > 0) {
- char* charp = reinterpret_cast<char*>(event->key.utf8);
- if (!WideCharToMultiByte(CP_UTF8, 0, buf, n,
- charp, 8, NULL, NULL)) {
- /* error: could not convert to utf-8,
- GetLastError has details */
- memset(event->key.utf8, 0, 8);
- // replacement character
- event->key.utf8[0] = 0xEF;
- event->key.utf8[1] = 0xBF;
- event->key.utf8[2] = 0xBD;
- }
-
- event->key.character = utf16_to_code_point(buf, n);
- } else {
- // replacement character
- event->key.utf8[0] = 0xEF;
- event->key.utf8[1] = 0xBF;
- event->key.utf8[2] = 0xBD;
- event->key.character = 0xFFFD;
- }
-}
-
-static void
-translateMessageParamsToEvent(LPARAM lParam, WPARAM wParam, PuglEvent* event)
-{
- /* TODO: This is a kludge. Would be nice to use ToUnicode here, but this
- breaks composed keys because it messes with the keyboard state. Not
- sure how to correctly handle this on Windows. */
-
- // This is how I really want to do this, but it breaks composed keys (é,
- // è, ü, ö, and so on) because ToUnicode messes with the keyboard state.
-
- //wchar_t buf[5];
- //BYTE keyboard_state[256];
- //int wcharCount = 0;
- //GetKeyboardState(keyboard_state);
- //wcharCount = ToUnicode(wParam, MapVirtualKey(wParam, MAPVK_VK_TO_VSC),
- // keyboard_state, buf, 4, 0);
- //wcharBufToEvent(buf, wcharCount, event);
-
- // So, since Google refuses to give me a better solution, and if no one
- // else has a better solution, I will make a hack...
- wchar_t buf[5] = { 0, 0, 0, 0, 0 };
- UINT c = MapVirtualKey(wParam, MAPVK_VK_TO_CHAR);
- buf[0] = c & 0xffff;
- // TODO: This does not take caps lock into account
- // TODO: Dead keys should affect key releases as well
- if (!(event->key.state && PUGL_MOD_SHIFT))
- buf[0] = towlower(buf[0]);
- wcharBufToEvent(buf, 1, event);
- event->key.filter = ((c >> 31) & 0x1);
-}
-
-static void
-translateCharEventToEvent(WPARAM wParam, PuglEvent* event)
-{
- wchar_t buf[2];
- int wcharCount;
- if (wParam & 0xFFFF0000) {
- wcharCount = 2;
- buf[0] = (wParam & 0xFFFF);
- buf[1] = ((wParam >> 16) & 0xFFFF);
- } else {
- wcharCount = 1;
- buf[0] = (wParam & 0xFFFF);
- }
- wcharBufToEvent(buf, wcharCount, event);
-}
-
-static bool
-ignoreKeyEvent(PuglView* view, LPARAM lParam)
-{
- return view->ignoreKeyRepeat && (lParam & (1 << 30));
-}
-
-static LRESULT
-handleMessage(PuglView* view, UINT message, WPARAM wParam, LPARAM lParam)
-{
- PuglEvent event;
- void* dummy_ptr = NULL;
- RECT rect;
- MINMAXINFO* mmi;
- POINT pt;
- bool dispatchThisEvent = true;
-
- memset(&event, 0, sizeof(event));
-
- event.any.type = PUGL_NOTHING;
- event.any.view = view;
- if (InSendMessageEx(dummy_ptr)) {
- event.any.flags |= PUGL_IS_SEND_EVENT;
- }
-
- switch (message) {
- case WM_CREATE:
- case WM_SHOWWINDOW:
- case WM_SIZE:
- GetWindowRect(view->impl->hwnd, &rect);
- event.configure.type = PUGL_CONFIGURE;
- event.configure.x = rect.left;
- event.configure.y = rect.top;
- view->width = rect.right - rect.left;
- view->height = rect.bottom - rect.top;
- event.configure.width = view->width;
- event.configure.height = view->height;
- break;
- case WM_GETMINMAXINFO:
- mmi = (MINMAXINFO*)lParam;
- mmi->ptMinTrackSize.x = view->min_width;
- mmi->ptMinTrackSize.y = view->min_height;
- break;
- case WM_PAINT:
- GetUpdateRect(view->impl->hwnd, &rect, false);
- event.expose.type = PUGL_EXPOSE;
- event.expose.x = rect.left;
- event.expose.y = rect.top;
- event.expose.width = rect.right - rect.left;
- event.expose.height = rect.bottom - rect.top;
- event.expose.count = 0;
- break;
- case WM_MOUSEMOVE:
- pt.x = GET_X_LPARAM(lParam);
- pt.y = GET_Y_LPARAM(lParam);
- ClientToScreen(view->impl->hwnd, &pt);
-
- event.motion.type = PUGL_MOTION_NOTIFY;
- event.motion.time = GetMessageTime();
- event.motion.x = GET_X_LPARAM(lParam);
- event.motion.y = GET_Y_LPARAM(lParam);
- event.motion.x_root = pt.x;
- event.motion.y_root = pt.y;
- event.motion.state = getModifiers();
- event.motion.is_hint = false;
- break;
- case WM_LBUTTONDOWN:
- initMouseEvent(&event, view, 1, true, lParam);
- break;
- case WM_MBUTTONDOWN:
- initMouseEvent(&event, view, 2, true, lParam);
- break;
- case WM_RBUTTONDOWN:
- initMouseEvent(&event, view, 3, true, lParam);
- break;
- case WM_LBUTTONUP:
- initMouseEvent(&event, view, 1, false, lParam);
- break;
- case WM_MBUTTONUP:
- initMouseEvent(&event, view, 2, false, lParam);
- break;
- case WM_RBUTTONUP:
- initMouseEvent(&event, view, 3, false, lParam);
- break;
- case WM_MOUSEWHEEL:
- initScrollEvent(&event, view, lParam, wParam);
- event.scroll.dy = GET_WHEEL_DELTA_WPARAM(wParam) / (float)WHEEL_DELTA;
- break;
- case WM_MOUSEHWHEEL:
- initScrollEvent(&event, view, lParam, wParam);
- event.scroll.dx = GET_WHEEL_DELTA_WPARAM(wParam) / (float)WHEEL_DELTA;
- break;
- case WM_KEYDOWN:
- if (!ignoreKeyEvent(view, lParam)) {
- initKeyEvent(&event, view, true, lParam);
- if (!(event.key.special = keySymToSpecial(wParam))) {
- event.key.type = PUGL_NOTHING;
- }
- }
- break;
- case WM_CHAR:
- if (!ignoreKeyEvent(view, lParam)) {
- initKeyEvent(&event, view, true, lParam);
- translateCharEventToEvent(wParam, &event);
- }
- break;
- case WM_DEADCHAR:
- if (!ignoreKeyEvent(view, lParam)) {
- initKeyEvent(&event, view, true, lParam);
- translateCharEventToEvent(wParam, &event);
- event.key.filter = 1;
- }
- break;
- case WM_KEYUP:
- initKeyEvent(&event, view, false, lParam);
- if (!(event.key.special = keySymToSpecial(wParam))) {
- translateMessageParamsToEvent(lParam, wParam, &event);
- }
- break;
- case WM_QUIT:
- case PUGL_LOCAL_CLOSE_MSG:
- event.close.type = PUGL_CLOSE;
- break;
- default:
- return DefWindowProc(
- view->impl->hwnd, message, wParam, lParam);
- }
-
- puglDispatchEvent(view, &event);
-
- return 0;
-}
-
-void
-puglCopyToClipboard(PuglView* view, const char* selection, size_t len)
-{
- PuglInternals* const impl = view->impl;
-
- puglSetSelection(view, selection, len);
-
- if(OpenClipboard(impl->hwnd)) {
- const int wsize = MultiByteToWideChar(CP_UTF8, 0, selection, len, NULL, 0);
- if(wsize) {
- HGLOBAL mem = GlobalAlloc(GMEM_MOVEABLE, (wsize + 1) * sizeof(wchar_t));
- if(mem) {
- wchar_t* wstr = (wchar_t*)GlobalLock(mem);
- if(wstr) {
- MultiByteToWideChar(CP_UTF8, 0, selection, len, wstr, wsize);
- wstr[wsize] = 0;
- GlobalUnlock(mem);
- SetClipboardData(CF_UNICODETEXT, mem);
- }
- else {
- GlobalFree(mem);
- }
- }
- }
- CloseClipboard();
- }
-}
-
-const char*
-puglPasteFromClipboard(PuglView* view, size_t* len)
-{
- PuglInternals* const impl = view->impl;
-
- if(IsClipboardFormatAvailable(CF_UNICODETEXT) && OpenClipboard(impl->hwnd)) {
- HGLOBAL mem = GetClipboardData(CF_UNICODETEXT);
- if(mem) {
- const int wsize = GlobalSize(mem) / sizeof(wchar_t) - 1;
- if(wsize) {
- wchar_t* wstr = (wchar_t*)GlobalLock(mem);
- if(wstr) {
- const int utf8size = WideCharToMultiByte(CP_UTF8, 0, wstr, wsize, NULL, 0, NULL, NULL);
- if(utf8size) {
- char* utf8 = (char*)malloc(utf8size);
- if(utf8) {
- WideCharToMultiByte(CP_UTF8, 0, wstr, wsize, utf8, utf8size, NULL, NULL);
- puglSetSelection(view, utf8, utf8size);
- free(utf8);
- }
- }
- GlobalUnlock(mem);
- }
- }
- }
- CloseClipboard();
- }
-
- return puglGetSelection(view, len);
-}
-
-void
-puglGrabFocus(PuglView* view)
-{
- // TODO
-}
-
-PuglStatus
-puglWaitForEvent(PuglView* view)
-{
- WaitMessage();
- return PUGL_SUCCESS;
-}
-
-PuglStatus
-puglProcessEvents(PuglView* view)
-{
- MSG msg;
- while (PeekMessage(&msg, view->impl->hwnd, 0, 0, PM_REMOVE)) {
- TranslateMessage(&msg);
- handleMessage(view, msg.message, msg.wParam, msg.lParam);
- }
-
- if (view->redisplay) {
- InvalidateRect(view->impl->hwnd, NULL, FALSE);
- view->redisplay = false;
- }
-
- return PUGL_SUCCESS;
-}
-
-LRESULT CALLBACK
-wndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
-{
-#ifdef _WIN64
- PuglView* view = (PuglView*)GetWindowLongPtr(hwnd, GWLP_USERDATA);
-#else
- PuglView* view = (PuglView*)GetWindowLongPtr(hwnd, GWL_USERDATA);
-#endif
-
- switch (message) {
- case WM_CREATE:
- PostMessage(hwnd, WM_SHOWWINDOW, TRUE, 0);
- return 0;
- case WM_CLOSE:
- PostMessage(hwnd, PUGL_LOCAL_CLOSE_MSG, wParam, lParam);
- return 0;
- case WM_DESTROY:
- return 0;
- default:
- if (view && hwnd == view->impl->hwnd) {
- return handleMessage(view, message, wParam, lParam);
- } else {
- return DefWindowProc(hwnd, message, wParam, lParam);
- }
- }
-}
-
-void
-puglPostRedisplay(PuglView* view)
-{
- view->redisplay = true;
-}
-
-PuglNativeWindow
-puglGetNativeWindow(PuglView* view)
-{
- return (PuglNativeWindow)view->impl->hwnd;
-}
diff --git a/pugl/pugl_x11.c b/pugl/pugl_x11.c
deleted file mode 100644
index 5afc880..0000000
--- a/pugl/pugl_x11.c
+++ /dev/null
@@ -1,810 +0,0 @@
-/*
- Copyright 2012-2016 David Robillard <http://drobilla.net>
- Copyright 2013 Robin Gareus <robin@gareus.org>
- Copyright 2011-2012 Ben Loftis, Harrison Consoles
-
- Permission to use, copy, modify, and/or distribute this software for any
- purpose with or without fee is hereby granted, provided that the above
- copyright notice and this permission notice appear in all copies.
-
- THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
- WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
- MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
- ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
- WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
- ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
- OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
-*/
-
-/**
- @file pugl_x11.c X11 Pugl Implementation.
-*/
-
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <limits.h>
-
-#include <X11/Xatom.h>
-#include <X11/Xlib.h>
-#include <X11/Xutil.h>
-#include <X11/keysym.h>
-
-#ifdef PUGL_HAVE_GL
-#include <GL/gl.h>
-#include <GL/glx.h>
-#endif
-
-#ifdef PUGL_HAVE_CAIRO
-#include <cairo/cairo.h>
-#include <cairo/cairo-xlib.h>
-#endif
-
-#include "pugl/cairo_gl.h"
-#include "pugl/pugl_internal.h"
-
-#ifndef MIN
-# define MIN(a, b) (((a) < (b)) ? (a) : (b))
-#endif
-
-#ifndef MAX
-# define MAX(a, b) (((a) > (b)) ? (a) : (b))
-#endif
-
-#ifdef PUGL_HAVE_GL
-
-/** Attributes for double-buffered RGBA. */
-static int attrListDbl[] = {
- GLX_RGBA,
- GLX_DOUBLEBUFFER , True,
- GLX_RED_SIZE , 4,
- GLX_GREEN_SIZE , 4,
- GLX_BLUE_SIZE , 4,
- GLX_DEPTH_SIZE , 8,
- GLX_STENCIL_SIZE , 8,
- /* GLX_SAMPLE_BUFFERS , 1, */
- /* GLX_SAMPLES , 4, */
- None
-};
-
-/** Attributes for single-buffered RGBA. */
-static int attrListSgl[] = {
- GLX_RGBA,
- GLX_DOUBLEBUFFER , False,
- GLX_RED_SIZE , 4,
- GLX_GREEN_SIZE , 4,
- GLX_BLUE_SIZE , 4,
- GLX_DEPTH_SIZE , 8,
- GLX_STENCIL_SIZE , 8,
- /* GLX_SAMPLE_BUFFERS , 1, */
- /* GLX_SAMPLES , 4, */
- None
-};
-
-/** Null-terminated list of attributes in order of preference. */
-static int* attrLists[] = { attrListDbl, attrListSgl, NULL };
-
-#endif // PUGL_HAVE_GL
-
-struct PuglInternalsImpl {
- Display* display;
- int screen;
- Window win;
- XIM xim;
- XIC xic;
-#ifdef PUGL_HAVE_CAIRO
- cairo_surface_t* surface;
- cairo_t* cr;
-#endif
-#ifdef PUGL_HAVE_GL
- GLXContext ctx;
- int doubleBuffered;
-#endif
-#if defined(PUGL_HAVE_CAIRO) && defined(PUGL_HAVE_GL)
- PuglCairoGL cairo_gl;
-#endif
- Atom clipboard;
- Atom utf8_string;
-};
-
-PuglInternals*
-puglInitInternals(void)
-{
- return (PuglInternals*)calloc(1, sizeof(PuglInternals));
-}
-
-static XVisualInfo*
-getVisual(PuglView* view)
-{
- PuglInternals* const impl = view->impl;
- XVisualInfo* vi = NULL;
-
-#ifdef PUGL_HAVE_GL
- if (view->ctx_type & PUGL_GL) {
- for (int* attr = *attrLists; !vi && *attr; ++attr) {
- vi = glXChooseVisual(impl->display, impl->screen, attr);
- }
- }
-#endif
-#ifdef PUGL_HAVE_CAIRO
- if (view->ctx_type == PUGL_CAIRO) {
- XVisualInfo pat;
- int n;
- pat.screen = impl->screen;
- vi = XGetVisualInfo(impl->display, VisualScreenMask, &pat, &n);
- }
-#endif
-
- return vi;
-}
-
-#ifdef PUGL_HAVE_CAIRO
-static int
-createCairoContext(PuglView* view)
-{
- PuglInternals* const impl = view->impl;
-
- if (impl->cr) {
- cairo_destroy(impl->cr);
- }
-
- impl->cr = cairo_create(impl->surface);
- return cairo_status(impl->cr);
-}
-#endif
-
-static bool
-createContext(PuglView* view, XVisualInfo* vi)
-{
- PuglInternals* const impl = view->impl;
-
-#ifdef PUGL_HAVE_GL
- if (view->ctx_type & PUGL_GL) {
- impl->ctx = glXCreateContext(impl->display, vi, 0, GL_TRUE);
- glXGetConfig(impl->display, vi, GLX_DOUBLEBUFFER, &impl->doubleBuffered);
- }
-#endif
-#ifdef PUGL_HAVE_CAIRO
- if (view->ctx_type == PUGL_CAIRO) {
- impl->surface = cairo_xlib_surface_create(
- impl->display, impl->win, vi->visual, view->width, view->height);
- }
-#endif
-#if defined(PUGL_HAVE_GL) && defined(PUGL_HAVE_CAIRO)
- if (view->ctx_type == PUGL_CAIRO_GL) {
- impl->surface = pugl_cairo_gl_create(
- &impl->cairo_gl, view->width, view->height, 4);
- }
-#endif
-
-#ifdef PUGL_HAVE_CAIRO
- if (view->ctx_type & PUGL_CAIRO) {
- if (cairo_surface_status(impl->surface) != CAIRO_STATUS_SUCCESS) {
- fprintf(stderr, "error: failed to create cairo surface\n");
- return false;
- }
-
- if (createCairoContext(view) != CAIRO_STATUS_SUCCESS) {
- cairo_surface_destroy(impl->surface);
- fprintf(stderr, "error: failed to create cairo context\n");
- return false;
- }
- }
-#endif
-
- return true;
-}
-
-static void
-destroyContext(PuglView* view)
-{
-#if defined(PUGL_HAVE_CAIRO) && defined(PUGL_HAVE_GL)
- if (view->ctx_type == PUGL_CAIRO_GL) {
- pugl_cairo_gl_free(&view->impl->cairo_gl);
- }
-#endif
-#ifdef PUGL_HAVE_GL
- if (view->ctx_type & PUGL_GL) {
- glXDestroyContext(view->impl->display, view->impl->ctx);
- }
-#endif
-#ifdef PUGL_HAVE_CAIRO
- if (view->ctx_type & PUGL_CAIRO) {
- cairo_destroy(view->impl->cr);
- cairo_surface_destroy(view->impl->surface);
- }
-#endif
-}
-
-void
-puglEnterContext(PuglView* view)
-{
-#ifdef PUGL_HAVE_GL
- if (view->ctx_type & PUGL_GL) {
- glXMakeCurrent(view->impl->display, view->impl->win, view->impl->ctx);
- }
-#endif
-}
-
-void
-puglLeaveContext(PuglView* view, bool flush)
-{
-#ifdef PUGL_HAVE_GL
- if (flush && view->ctx_type & PUGL_GL) {
-#ifdef PUGL_HAVE_CAIRO
- if (view->ctx_type == PUGL_CAIRO_GL) {
- pugl_cairo_gl_draw(&view->impl->cairo_gl, view->width, view->height);
- }
-#endif
-
- glFlush();
- if (view->impl->doubleBuffered) {
- glXSwapBuffers(view->impl->display, view->impl->win);
- }
- }
-
- glXMakeCurrent(view->impl->display, None, NULL);
-#endif
-}
-
-int
-puglCreateWindow(PuglView* view, const char* title)
-{
- PuglInternals* const impl = view->impl;
-
- impl->display = XOpenDisplay(0);
- impl->screen = DefaultScreen(impl->display);
-
- XVisualInfo* const vi = getVisual(view);
- if (!vi) {
- return 1;
- }
-
- Window xParent = view->parent
- ? (Window)view->parent
- : RootWindow(impl->display, impl->screen);
-
- Colormap cmap = XCreateColormap(
- impl->display, xParent, vi->visual, AllocNone);
-
- impl->clipboard = XInternAtom(impl->display, "CLIPBOARD", 0);
- impl->utf8_string = XInternAtom(impl->display, "UTF8_STRING", 0);
-
- XSetWindowAttributes attr;
- memset(&attr, 0, sizeof(XSetWindowAttributes));
- attr.colormap = cmap;
- attr.event_mask = (ExposureMask | StructureNotifyMask |
- EnterWindowMask | LeaveWindowMask |
- KeyPressMask | KeyReleaseMask |
- ButtonPressMask | ButtonReleaseMask |
- PointerMotionMask | FocusChangeMask);
-
- impl->win = XCreateWindow(
- impl->display, xParent,
- 0, 0, view->width, view->height, 0, vi->depth, InputOutput, vi->visual,
- CWColormap | CWEventMask, &attr);
-
- if (!createContext(view, vi)) {
- XFree(vi);
- return 2;
- }
-
- XSizeHints sizeHints;
- memset(&sizeHints, 0, sizeof(sizeHints));
- if (!view->resizable) {
- sizeHints.flags = PMinSize|PMaxSize;
- sizeHints.min_width = view->width;
- sizeHints.min_height = view->height;
- sizeHints.max_width = view->width;
- sizeHints.max_height = view->height;
- XSetNormalHints(impl->display, impl->win, &sizeHints);
- } else {
- if (view->min_width || view->min_height) {
- sizeHints.flags = PMinSize;
- sizeHints.min_width = view->min_width;
- sizeHints.min_height = view->min_height;
- }
- if (view->min_aspect_x) {
- sizeHints.flags |= PAspect;
- sizeHints.min_aspect.x = view->min_aspect_x;
- sizeHints.min_aspect.y = view->min_aspect_y;
- sizeHints.max_aspect.x = view->max_aspect_x;
- sizeHints.max_aspect.y = view->max_aspect_y;
- }
-
- XSetNormalHints(impl->display, impl->win, &sizeHints);
- }
-
- if (title) {
- XStoreName(impl->display, impl->win, title);
- }
-
- if (!view->parent) {
- Atom wmDelete = XInternAtom(impl->display, "WM_DELETE_WINDOW", True);
- XSetWMProtocols(impl->display, impl->win, &wmDelete, 1);
- }
-
- if (view->transient_parent) {
- XSetTransientForHint(impl->display, impl->win,
- (Window)(view->transient_parent));
- }
-
- XSetLocaleModifiers("");
- if (!(impl->xim = XOpenIM(impl->display, NULL, NULL, NULL))) {
- XSetLocaleModifiers("@im=");
- if (!(impl->xim = XOpenIM(impl->display, NULL, NULL, NULL))) {
- fprintf(stderr, "warning: XOpenIM failed\n");
- }
- }
-
- const XIMStyle im_style = XIMPreeditNothing | XIMStatusNothing;
- if (!(impl->xic = XCreateIC(impl->xim,
- XNInputStyle, im_style,
- XNClientWindow, impl->win,
- XNFocusWindow, impl->win,
- NULL))) {
- fprintf(stderr, "warning: XCreateIC failed\n");
- }
-
- XFree(vi);
-
- return 0;
-}
-
-void
-puglShowWindow(PuglView* view)
-{
- XMapRaised(view->impl->display, view->impl->win);
- view->visible = true;
-}
-
-void
-puglHideWindow(PuglView* view)
-{
- XUnmapWindow(view->impl->display, view->impl->win);
- view->visible = false;
-}
-
-void
-puglDestroy(PuglView* view)
-{
- if (view) {
- destroyContext(view);
- XDestroyWindow(view->impl->display, view->impl->win);
- XCloseDisplay(view->impl->display);
- puglClearSelection(view);
- free(view->windowClass);
- free(view->impl);
- free(view);
- }
-}
-
-static PuglKey
-keySymToSpecial(KeySym sym)
-{
- switch (sym) {
- case XK_F1: return PUGL_KEY_F1;
- case XK_F2: return PUGL_KEY_F2;
- case XK_F3: return PUGL_KEY_F3;
- case XK_F4: return PUGL_KEY_F4;
- case XK_F5: return PUGL_KEY_F5;
- case XK_F6: return PUGL_KEY_F6;
- case XK_F7: return PUGL_KEY_F7;
- case XK_F8: return PUGL_KEY_F8;
- case XK_F9: return PUGL_KEY_F9;
- case XK_F10: return PUGL_KEY_F10;
- case XK_F11: return PUGL_KEY_F11;
- case XK_F12: return PUGL_KEY_F12;
- case XK_Left: return PUGL_KEY_LEFT;
- case XK_Up: return PUGL_KEY_UP;
- case XK_Right: return PUGL_KEY_RIGHT;
- case XK_Down: return PUGL_KEY_DOWN;
- case XK_Page_Up: return PUGL_KEY_PAGE_UP;
- case XK_Page_Down: return PUGL_KEY_PAGE_DOWN;
- case XK_Home: return PUGL_KEY_HOME;
- case XK_End: return PUGL_KEY_END;
- case XK_Insert: return PUGL_KEY_INSERT;
- case XK_Shift_L: return PUGL_KEY_SHIFT;
- case XK_Shift_R: return PUGL_KEY_SHIFT;
- case XK_Control_L: return PUGL_KEY_CTRL;
- case XK_Control_R: return PUGL_KEY_CTRL;
- case XK_Alt_L: return PUGL_KEY_ALT;
- case XK_Alt_R: return PUGL_KEY_ALT;
- case XK_Super_L: return PUGL_KEY_SUPER;
- case XK_Super_R: return PUGL_KEY_SUPER;
- }
- return (PuglKey)0;
-}
-
-static void
-translateKey(PuglView* view, XEvent* xevent, PuglEvent* event)
-{
- KeySym sym = 0;
- char* str = (char*)event->key.utf8;
- memset(str, 0, 8);
- event->key.filter = XFilterEvent(xevent, None);
- if (xevent->type == KeyRelease || event->key.filter || !view->impl->xic) {
- if (XLookupString(&xevent->xkey, str, 7, &sym, NULL) == 1) {
- event->key.character = str[0];
- }
- } else {
- /* TODO: Not sure about this. On my system, some characters work with
- Xutf8LookupString but not with XmbLookupString, and some are the
- opposite. */
- Status status = 0;
-#ifdef X_HAVE_UTF8_STRING
- const int n = Xutf8LookupString(
- view->impl->xic, &xevent->xkey, str, 7, &sym, &status);
-#else
- const int n = XmbLookupString(
- view->impl->xic, &xevent->xkey, str, 7, &sym, &status);
-#endif
- if (n > 0) {
- event->key.character = puglDecodeUTF8((const uint8_t*)str);
- }
- }
- event->key.special = keySymToSpecial(sym);
- event->key.keycode = xevent->xkey.keycode;
-}
-
-static unsigned
-translateModifiers(unsigned xstate)
-{
- unsigned state = 0;
- state |= (xstate & ShiftMask) ? PUGL_MOD_SHIFT : 0;
- state |= (xstate & ControlMask) ? PUGL_MOD_CTRL : 0;
- state |= (xstate & Mod1Mask) ? PUGL_MOD_ALT : 0;
- state |= (xstate & Mod4Mask) ? PUGL_MOD_SUPER : 0;
- return state;
-}
-
-static PuglEvent
-translateEvent(PuglView* view, XEvent xevent)
-{
- PuglEvent event;
- memset(&event, 0, sizeof(event));
-
- event.any.view = view;
- if (xevent.xany.send_event) {
- event.any.flags |= PUGL_IS_SEND_EVENT;
- }
-
- switch (xevent.type) {
- case ClientMessage: {
- char* type = XGetAtomName(view->impl->display,
- xevent.xclient.message_type);
- if (!strcmp(type, "WM_PROTOCOLS")) {
- event.type = PUGL_CLOSE;
- }
- XFree(type);
- break;
- }
- case ConfigureNotify:
- event.type = PUGL_CONFIGURE;
- event.configure.x = xevent.xconfigure.x;
- event.configure.y = xevent.xconfigure.y;
- event.configure.width = xevent.xconfigure.width;
- event.configure.height = xevent.xconfigure.height;
- break;
- case Expose:
- event.type = PUGL_EXPOSE;
- event.expose.x = xevent.xexpose.x;
- event.expose.y = xevent.xexpose.y;
- event.expose.width = xevent.xexpose.width;
- event.expose.height = xevent.xexpose.height;
- event.expose.count = xevent.xexpose.count;
- break;
- case MotionNotify:
- event.type = PUGL_MOTION_NOTIFY;
- event.motion.time = xevent.xmotion.time;
- event.motion.x = xevent.xmotion.x;
- event.motion.y = xevent.xmotion.y;
- event.motion.x_root = xevent.xmotion.x_root;
- event.motion.y_root = xevent.xmotion.y_root;
- event.motion.state = translateModifiers(xevent.xmotion.state);
- event.motion.is_hint = (xevent.xmotion.is_hint == NotifyHint);
- break;
- case ButtonPress:
- if (xevent.xbutton.button >= 4 && xevent.xbutton.button <= 7) {
- event.type = PUGL_SCROLL;
- event.scroll.time = xevent.xbutton.time;
- event.scroll.x = xevent.xbutton.x;
- event.scroll.y = xevent.xbutton.y;
- event.scroll.x_root = xevent.xbutton.x_root;
- event.scroll.y_root = xevent.xbutton.y_root;
- event.scroll.state = translateModifiers(xevent.xbutton.state);
- event.scroll.dx = 0.0;
- event.scroll.dy = 0.0;
- switch (xevent.xbutton.button) {
- case 4: event.scroll.dy = 1.0f; break;
- case 5: event.scroll.dy = -1.0f; break;
- case 6: event.scroll.dx = -1.0f; break;
- case 7: event.scroll.dx = 1.0f; break;
- }
- }
- /* fall through */
- case ButtonRelease:
- if (xevent.xbutton.button < 4 || xevent.xbutton.button > 7) {
- event.button.type = ((xevent.type == ButtonPress)
- ? PUGL_BUTTON_PRESS
- : PUGL_BUTTON_RELEASE);
- event.button.time = xevent.xbutton.time;
- event.button.x = xevent.xbutton.x;
- event.button.y = xevent.xbutton.y;
- event.button.x_root = xevent.xbutton.x_root;
- event.button.y_root = xevent.xbutton.y_root;
- event.button.state = translateModifiers(xevent.xbutton.state);
- event.button.button = xevent.xbutton.button;
- }
- break;
- case KeyPress:
- case KeyRelease:
- event.type = ((xevent.type == KeyPress)
- ? PUGL_KEY_PRESS
- : PUGL_KEY_RELEASE);
- event.key.time = xevent.xkey.time;
- event.key.x = xevent.xkey.x;
- event.key.y = xevent.xkey.y;
- event.key.x_root = xevent.xkey.x_root;
- event.key.y_root = xevent.xkey.y_root;
- event.key.state = translateModifiers(xevent.xkey.state);
- translateKey(view, &xevent, &event);
- break;
- case EnterNotify:
- case LeaveNotify:
- event.type = ((xevent.type == EnterNotify)
- ? PUGL_ENTER_NOTIFY
- : PUGL_LEAVE_NOTIFY);
- event.crossing.time = xevent.xcrossing.time;
- event.crossing.x = xevent.xcrossing.x;
- event.crossing.y = xevent.xcrossing.y;
- event.crossing.x_root = xevent.xcrossing.x_root;
- event.crossing.y_root = xevent.xcrossing.y_root;
- event.crossing.state = translateModifiers(xevent.xcrossing.state);
- event.crossing.mode = PUGL_CROSSING_NORMAL;
- if (xevent.xcrossing.mode == NotifyGrab) {
- event.crossing.mode = PUGL_CROSSING_GRAB;
- } else if (xevent.xcrossing.mode == NotifyUngrab) {
- event.crossing.mode = PUGL_CROSSING_UNGRAB;
- }
- break;
-
- case FocusIn:
- case FocusOut:
- event.type = ((xevent.type == FocusIn)
- ? PUGL_FOCUS_IN
- : PUGL_FOCUS_OUT);
- event.focus.grab = (xevent.xfocus.mode != NotifyNormal);
- break;
-
- case SelectionClear:
- puglClearSelection(view);
- break;
- case SelectionRequest:
- {
- XSelectionEvent xev = {
- .type = SelectionNotify,
- .requestor = xevent.xselectionrequest.requestor,
- .selection = xevent.xselectionrequest.selection,
- .target = xevent.xselectionrequest.target,
- .time = xevent.xselectionrequest.time,
- };
-
- size_t len;
- const char *selection = puglGetSelection(view, &len);
- if(selection
- && (xevent.xselectionrequest.selection == view->impl->clipboard)
- && (xevent.xselectionrequest.target == view->impl->utf8_string) )
- {
- xev.property = xevent.xselectionrequest.property;
- XChangeProperty(view->impl->display, xev.requestor,
- xev.property, xev.target, 8, PropModeReplace,
- (const uint8_t*)selection, len);
- }
- else // conversion failed
- {
- xev.property = None;
- }
- XSendEvent(view->impl->display, xev.requestor, True, 0, (XEvent*)&xev);
-
- break;
- }
-
- default:
- break;
- }
-
- return event;
-}
-
-void
-puglCopyToClipboard(PuglView* view, const char* selection, size_t len)
-{
- PuglInternals* const impl = view->impl;
-
- puglSetSelection(view, selection, len);
-
- XSetSelectionOwner(impl->display, impl->clipboard, impl->win, CurrentTime);
-}
-
-const char*
-puglPasteFromClipboard(PuglView* view, size_t* len)
-{
- PuglInternals* const impl = view->impl;
-
- if(XGetSelectionOwner(impl->display, impl->clipboard) != impl->win) {
- XConvertSelection(impl->display, impl->clipboard, impl->utf8_string,
- XA_PRIMARY, impl->win, CurrentTime);
-
- XEvent xevent;
- while(!XCheckTypedWindowEvent(impl->display, impl->win, SelectionNotify,
- &xevent)) {
- // wait for answer
- }
-
- if( (xevent.xselection.selection == impl->clipboard)
- && (xevent.xselection.target == impl->utf8_string)
- && (xevent.xselection.property == XA_PRIMARY) ) {
- unsigned long nitems, rem;
- int format;
- uint8_t* data;
- Atom type;
-
- XGetWindowProperty(impl->display, impl->win, XA_PRIMARY,
- 0, LONG_MAX/4, False, AnyPropertyType, // request 32*32=1024 bytes
- &type, &format, &nitems, &rem, &data);
- if(data) {
- if( (format == 8) && (type == impl->utf8_string) && (rem == 0) ) {
- puglSetSelection(view, (const char*)data, nitems);
- }
-
- XFree(data);
- }
- }
- }
-
- return puglGetSelection(view, len);
-}
-
-void
-puglGrabFocus(PuglView* view)
-{
- XSetInputFocus(
- view->impl->display, view->impl->win, RevertToPointerRoot, CurrentTime);
-}
-
-PuglStatus
-puglWaitForEvent(PuglView* view)
-{
- XEvent xevent;
- XPeekEvent(view->impl->display, &xevent);
- return PUGL_SUCCESS;
-}
-
-static void
-merge_draw_events(PuglEvent* dst, const PuglEvent* src)
-{
- if (!dst->type) {
- *dst = *src;
- } else {
- dst->expose.x = MIN(dst->expose.x, src->expose.x);
- dst->expose.y = MIN(dst->expose.y, src->expose.y);
- dst->expose.width = MAX(dst->expose.width, src->expose.width);
- dst->expose.height = MAX(dst->expose.height, src->expose.height);
- }
-}
-
-PuglStatus
-puglProcessEvents(PuglView* view)
-{
- /* Maintain a single expose/configure event to execute after all pending
- events. This avoids redundant drawing/configuration which prevents a
- series of window resizes in the same loop from being laggy. */
- PuglEvent expose_event = { 0 };
- PuglEvent config_event = { 0 };
- XEvent xevent;
- while (XPending(view->impl->display) > 0) {
- XNextEvent(view->impl->display, &xevent);
- if (xevent.type == KeyRelease) {
- // Ignore key repeat if necessary
- if (view->ignoreKeyRepeat &&
- XEventsQueued(view->impl->display, QueuedAfterReading)) {
- XEvent next;
- XPeekEvent(view->impl->display, &next);
- if (next.type == KeyPress &&
- next.xkey.time == xevent.xkey.time &&
- next.xkey.keycode == xevent.xkey.keycode) {
- XNextEvent(view->impl->display, &xevent);
- continue;
- }
- }
- } else if (xevent.type == FocusIn) {
- XSetICFocus(view->impl->xic);
- } else if (xevent.type == FocusOut) {
- XUnsetICFocus(view->impl->xic);
- }
-
- // Translate X11 event to Pugl event
- const PuglEvent event = translateEvent(view, xevent);
-
- if (event.type == PUGL_EXPOSE) {
- // Expand expose event to be dispatched after loop
- merge_draw_events(&expose_event, &event);
- } else if (event.type == PUGL_CONFIGURE) {
- // Expand configure event to be dispatched after loop
- merge_draw_events(&config_event, &event);
- } else {
- // Dispatch event to application immediately
- puglDispatchEvent(view, &event);
- }
- }
-
- if (config_event.type
- && ( (view->width != config_event.configure.width)
- || (view->height != config_event.configure.height) )) {
-#ifdef PUGL_HAVE_CAIRO
- if (view->ctx_type == PUGL_CAIRO) {
- // Resize surfaces/contexts before dispatching
- view->redisplay = true;
- cairo_xlib_surface_set_size(view->impl->surface,
- config_event.configure.width,
- config_event.configure.height);
- }
-#ifdef PUGL_HAVE_GL
- if (view->ctx_type == PUGL_CAIRO_GL) {
- view->redisplay = true;
- cairo_surface_destroy(view->impl->surface);
- view->impl->surface = pugl_cairo_gl_create(
- &view->impl->cairo_gl,
- config_event.configure.width,
- config_event.configure.height,
- 4);
- pugl_cairo_gl_configure(&view->impl->cairo_gl,
- config_event.configure.width,
- config_event.configure.height);
- createCairoContext(view);
- }
-#endif
-#endif
- puglDispatchEvent(view, (const PuglEvent*)&config_event);
- }
-
- if (view->redisplay) {
- expose_event.expose.type = PUGL_EXPOSE;
- expose_event.expose.view = view;
- expose_event.expose.x = 0;
- expose_event.expose.y = 0;
- expose_event.expose.width = view->width;
- expose_event.expose.height = view->height;
- view->redisplay = false;
- }
-
- if (expose_event.type) {
- puglDispatchEvent(view, (const PuglEvent*)&expose_event);
- }
-
- return PUGL_SUCCESS;
-}
-
-void
-puglPostRedisplay(PuglView* view)
-{
- view->redisplay = true;
-}
-
-PuglNativeWindow
-puglGetNativeWindow(PuglView* view)
-{
- return view->impl->win;
-}
-
-void*
-puglGetContext(PuglView* view __attribute__((unused)))
-{
-#ifdef PUGL_HAVE_CAIRO
- if (view->ctx_type & PUGL_CAIRO) {
- return view->impl->cr;
- }
-#endif
- return NULL;
-}
diff --git a/pugl_test.c b/pugl_test.c
deleted file mode 100644
index 367e7a4..0000000
--- a/pugl_test.c
+++ /dev/null
@@ -1,261 +0,0 @@
-/*
- Copyright 2012-2016 David Robillard <http://drobilla.net>
-
- Permission to use, copy, modify, and/or distribute this software for any
- purpose with or without fee is hereby granted, provided that the above
- copyright notice and this permission notice appear in all copies.
-
- THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
- WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
- MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
- ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
- WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
- ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
- OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
-*/
-
-/**
- @file pugl_test.c A simple Pugl test that creates a top-level window.
-*/
-
-#include <locale.h>
-#include <math.h>
-#include <stdio.h>
-#include <string.h>
-
-#include "pugl/gl.h"
-#include "pugl/pugl.h"
-
-static int quit = 0;
-static float xAngle = 0.0f;
-static float yAngle = 0.0f;
-static float dist = 10.0f;
-
-static const float cubeVertices[] = {
- -1.0f, -1.0f, -1.0f,
- -1.0f, -1.0f, 1.0f,
- -1.0f, 1.0f, 1.0f,
-
- 1.0f, 1.0f, -1.0f,
- -1.0f, -1.0f, -1.0f,
- -1.0f, 1.0f, -1.0f,
-
- 1.0f, -1.0f, 1.0f,
- -1.0f, -1.0f, -1.0f,
- 1.0f, -1.0f, -1.0f,
-
- 1.0f, 1.0f, -1.0f,
- 1.0f, -1.0f, -1.0f,
- -1.0f, -1.0f, -1.0f,
-
- -1.0f, -1.0f, -1.0f,
- -1.0f, 1.0f, 1.0f,
- -1.0f, 1.0f, -1.0f,
-
- 1.0f, -1.0f, 1.0f,
- -1.0f, -1.0f, 1.0f,
- -1.0f, -1.0f, -1.0f,
-
- -1.0f, 1.0f, 1.0f,
- -1.0f, -1.0f, 1.0f,
- 1.0f, -1.0f, 1.0f,
-
- 1.0f, 1.0f, 1.0f,
- 1.0f, -1.0f, -1.0f,
- 1.0f, 1.0f, -1.0f,
-
- 1.0f, -1.0f, -1.0f,
- 1.0f, 1.0f, 1.0f,
- 1.0f, -1.0f, 1.0f,
-
- 1.0f, 1.0f, 1.0f,
- 1.0f, 1.0f, -1.0f,
- -1.0f, 1.0f, -1.0f,
-
- 1.0f, 1.0f, 1.0f,
- -1.0f, 1.0f, -1.0f,
- -1.0f, 1.0f, 1.0f,
-
- 1.0f, 1.0f, 1.0f,
- -1.0f, 1.0f, 1.0f,
- 1.0f, -1.0f, 1.0f
-};
-
-/** Calculate a projection matrix for a given perspective. */
-static void
-perspective(float* m, float fov, float aspect, float zNear, float zFar)
-{
- const float h = tan(fov);
- const float w = h / aspect;
- const float depth = zNear - zFar;
- const float q = (zFar + zNear) / depth;
- const float qn = 2 * zFar * zNear / depth;
-
- m[0] = w; m[1] = 0; m[2] = 0; m[3] = 0;
- m[4] = 0; m[5] = h; m[6] = 0; m[7] = 0;
- m[8] = 0; m[9] = 0; m[10] = q; m[11] = -1;
- m[12] = 0; m[13] = 0; m[14] = qn; m[15] = 0;
-}
-
-static void
-onReshape(PuglView* view, int width, int height)
-{
- glMatrixMode(GL_PROJECTION);
- glLoadIdentity();
- glViewport(0, 0, width, height);
-
- float projection[16];
- perspective(projection, 1.8f, width / (float)height, 1.0, 100.0f);
- glLoadMatrixf(projection);
-}
-
-static void
-onDisplay(PuglView* view)
-{
- glMatrixMode(GL_MODELVIEW);
- glLoadIdentity();
- glTranslatef(0.0f, 0.0f, dist * -1);
- glRotatef(xAngle, 0.0f, 1.0f, 0.0f);
- glRotatef(yAngle, 1.0f, 0.0f, 0.0f);
-
- glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
- glEnableClientState(GL_VERTEX_ARRAY);
- glEnableClientState(GL_COLOR_ARRAY);
-
- glVertexPointer(3, GL_FLOAT, 0, cubeVertices);
- glColorPointer(3, GL_FLOAT, 0, cubeVertices);
- glDrawArrays(GL_TRIANGLES, 0, 12 * 3);
-
- glDisableClientState(GL_VERTEX_ARRAY);
- glDisableClientState(GL_COLOR_ARRAY);
-}
-
-static void
-printModifiers(PuglView* view, unsigned mods)
-{
- fprintf(stderr, "Modifiers:%s%s%s%s\n",
- (mods & PUGL_MOD_SHIFT) ? " Shift" : "",
- (mods & PUGL_MOD_CTRL) ? " Ctrl" : "",
- (mods & PUGL_MOD_ALT) ? " Alt" : "",
- (mods & PUGL_MOD_SUPER) ? " Super" : "");
-}
-
-static void
-onEvent(PuglView* view, const PuglEvent* event)
-{
- switch (event->type) {
- case PUGL_NOTHING:
- break;
- case PUGL_CONFIGURE:
- onReshape(view, event->configure.width, event->configure.height);
- break;
- case PUGL_EXPOSE:
- onDisplay(view);
- break;
- case PUGL_CLOSE:
- quit = 1;
- break;
- case PUGL_KEY_PRESS:
- fprintf(stderr, "Key %u (char %u) press (%s)%s\n",
- event->key.keycode, event->key.character, event->key.utf8,
- event->key.filter ? " (filtered)" : "");
- if (event->key.character == 'q' ||
- event->key.character == 'Q' ||
- event->key.character == PUGL_CHAR_ESCAPE) {
- quit = 1;
- }
- break;
- case PUGL_KEY_RELEASE:
- fprintf(stderr, "Key %u (char %u) release (%s)%s\n",
- event->key.keycode, event->key.character, event->key.utf8,
- event->key.filter ? " (filtered)" : "");
- break;
- case PUGL_MOTION_NOTIFY:
- xAngle = -(int)event->motion.x % 360;
- yAngle = (int)event->motion.y % 360;
- puglPostRedisplay(view);
- break;
- case PUGL_BUTTON_PRESS:
- case PUGL_BUTTON_RELEASE:
- fprintf(stderr, "Mouse %d %s at %f,%f ",
- event->button.button,
- (event->type == PUGL_BUTTON_PRESS) ? "down" : "up",
- event->button.x,
- event->button.y);
- printModifiers(view, event->scroll.state);
- break;
- case PUGL_SCROLL:
- fprintf(stderr, "Scroll %f %f %f %f ",
- event->scroll.x, event->scroll.y, event->scroll.dx, event->scroll.dy);
- printModifiers(view, event->scroll.state);
- dist += event->scroll.dy;
- if (dist < 10.0f) {
- dist = 10.0f;
- }
- puglPostRedisplay(view);
- break;
- case PUGL_ENTER_NOTIFY:
- fprintf(stderr, "Entered\n");
- break;
- case PUGL_LEAVE_NOTIFY:
- fprintf(stderr, "Exited\n");
- break;
- case PUGL_FOCUS_IN:
- fprintf(stderr, "Focus in\n");
- break;
- case PUGL_FOCUS_OUT:
- fprintf(stderr, "Focus out\n");
- break;
- }
-}
-
-int
-main(int argc, char** argv)
-{
- bool ignoreKeyRepeat = false;
- bool resizable = false;
- for (int i = 1; i < argc; ++i) {
- if (!strcmp(argv[i], "-h")) {
- printf("USAGE: %s [OPTIONS]...\n\n"
- " -h Display this help\n"
- " -i Ignore key repeat\n"
- " -r Resizable window\n", argv[0]);
- return 0;
- } else if (!strcmp(argv[i], "-i")) {
- ignoreKeyRepeat = true;
- } else if (!strcmp(argv[i], "-r")) {
- resizable = true;
- } else {
- fprintf(stderr, "Unknown option: %s\n", argv[i]);
- }
- }
-
- setlocale(LC_CTYPE, "");
-
- PuglView* view = puglInit(NULL, NULL);
- puglInitWindowClass(view, "PuglTest");
- puglInitWindowSize(view, 512, 512);
- puglInitWindowMinSize(view, 256, 256);
- puglInitResizable(view, resizable);
-
- puglIgnoreKeyRepeat(view, ignoreKeyRepeat);
- puglSetEventFunc(view, onEvent);
-
- puglCreateWindow(view, "Pugl Test");
-
- puglEnterContext(view);
- glEnable(GL_DEPTH_TEST);
- glDepthFunc(GL_LESS);
- glClearColor(0.2f, 0.2f, 0.2f, 1.0f);
- puglLeaveContext(view, false);
-
- puglShowWindow(view);
-
- while (!quit) {
- puglWaitForEvent(view);
- puglProcessEvents(view);
- }
-
- puglDestroy(view);
- return 0;
-}
diff --git a/resources/Info.plist.in b/resources/Info.plist.in
new file mode 100644
index 0000000..a08dbd0
--- /dev/null
+++ b/resources/Info.plist.in
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+ <dict>
+ <key>CFBundleIdentifier</key>
+ <string>net.drobilla.pugl.@NAME@</string>
+ <key>CFBundleShortVersionString</key>
+ <string>1.0</string>
+ <key>CFBundleVersion</key>
+ <string>1.0</string>
+ <key>MinimumOSVersion</key>
+ <string>10.6</string>
+ <key>CFBundleDisplayName</key>
+ <string>@NAME@</string>
+ <key>CFBundleName</key>
+ <string>@NAME@</string>
+ <key>NSHighResolutionCapable</key>
+ <true/>
+ </dict>
+</plist>
diff --git a/resources/pugl.ipe b/resources/pugl.ipe
new file mode 100644
index 0000000..238c09c
--- /dev/null
+++ b/resources/pugl.ipe
@@ -0,0 +1,293 @@
+<?xml version="1.0"?>
+<!DOCTYPE ipe SYSTEM "ipe.dtd">
+<ipe version="70206" creator="Ipe 7.2.7">
+<info created="D:20190111123224" modified="D:20190725105605"/>
+<ipestyle name="basic">
+<symbol name="arrow/arc(spx)">
+<path stroke="sym-stroke" fill="sym-stroke" pen="sym-pen">
+0 0 m
+-1 0.333 l
+-1 -0.333 l
+h
+</path>
+</symbol>
+<symbol name="arrow/farc(spx)">
+<path stroke="sym-stroke" fill="white" pen="sym-pen">
+0 0 m
+-1 0.333 l
+-1 -0.333 l
+h
+</path>
+</symbol>
+<symbol name="arrow/ptarc(spx)">
+<path stroke="sym-stroke" fill="sym-stroke" pen="sym-pen">
+0 0 m
+-1 0.333 l
+-0.8 0 l
+-1 -0.333 l
+h
+</path>
+</symbol>
+<symbol name="arrow/fptarc(spx)">
+<path stroke="sym-stroke" fill="white" pen="sym-pen">
+0 0 m
+-1 0.333 l
+-0.8 0 l
+-1 -0.333 l
+h
+</path>
+</symbol>
+<symbol name="mark/circle(sx)" transformations="translations">
+<path fill="sym-stroke">
+0.6 0 0 0.6 0 0 e
+0.4 0 0 0.4 0 0 e
+</path>
+</symbol>
+<symbol name="mark/disk(sx)" transformations="translations">
+<path fill="sym-stroke">
+0.6 0 0 0.6 0 0 e
+</path>
+</symbol>
+<symbol name="mark/fdisk(sfx)" transformations="translations">
+<group>
+<path fill="sym-fill">
+0.5 0 0 0.5 0 0 e
+</path>
+<path fill="sym-stroke" fillrule="eofill">
+0.6 0 0 0.6 0 0 e
+0.4 0 0 0.4 0 0 e
+</path>
+</group>
+</symbol>
+<symbol name="mark/box(sx)" transformations="translations">
+<path fill="sym-stroke" fillrule="eofill">
+-0.6 -0.6 m
+0.6 -0.6 l
+0.6 0.6 l
+-0.6 0.6 l
+h
+-0.4 -0.4 m
+0.4 -0.4 l
+0.4 0.4 l
+-0.4 0.4 l
+h
+</path>
+</symbol>
+<symbol name="mark/square(sx)" transformations="translations">
+<path fill="sym-stroke">
+-0.6 -0.6 m
+0.6 -0.6 l
+0.6 0.6 l
+-0.6 0.6 l
+h
+</path>
+</symbol>
+<symbol name="mark/fsquare(sfx)" transformations="translations">
+<group>
+<path fill="sym-fill">
+-0.5 -0.5 m
+0.5 -0.5 l
+0.5 0.5 l
+-0.5 0.5 l
+h
+</path>
+<path fill="sym-stroke" fillrule="eofill">
+-0.6 -0.6 m
+0.6 -0.6 l
+0.6 0.6 l
+-0.6 0.6 l
+h
+-0.4 -0.4 m
+0.4 -0.4 l
+0.4 0.4 l
+-0.4 0.4 l
+h
+</path>
+</group>
+</symbol>
+<symbol name="mark/cross(sx)" transformations="translations">
+<group>
+<path fill="sym-stroke">
+-0.43 -0.57 m
+0.57 0.43 l
+0.43 0.57 l
+-0.57 -0.43 l
+h
+</path>
+<path fill="sym-stroke">
+-0.43 0.57 m
+0.57 -0.43 l
+0.43 -0.57 l
+-0.57 0.43 l
+h
+</path>
+</group>
+</symbol>
+<symbol name="arrow/fnormal(spx)">
+<path stroke="sym-stroke" fill="white" pen="sym-pen">
+0 0 m
+-1 0.333 l
+-1 -0.333 l
+h
+</path>
+</symbol>
+<symbol name="arrow/pointed(spx)">
+<path stroke="sym-stroke" fill="sym-stroke" pen="sym-pen">
+0 0 m
+-1 0.333 l
+-0.8 0 l
+-1 -0.333 l
+h
+</path>
+</symbol>
+<symbol name="arrow/fpointed(spx)">
+<path stroke="sym-stroke" fill="white" pen="sym-pen">
+0 0 m
+-1 0.333 l
+-0.8 0 l
+-1 -0.333 l
+h
+</path>
+</symbol>
+<symbol name="arrow/linear(spx)">
+<path stroke="sym-stroke" pen="sym-pen">
+-1 0.333 m
+0 0 l
+-1 -0.333 l
+</path>
+</symbol>
+<symbol name="arrow/fdouble(spx)">
+<path stroke="sym-stroke" fill="white" pen="sym-pen">
+0 0 m
+-1 0.333 l
+-1 -0.333 l
+h
+-1 0 m
+-2 0.333 l
+-2 -0.333 l
+h
+</path>
+</symbol>
+<symbol name="arrow/double(spx)">
+<path stroke="sym-stroke" fill="sym-stroke" pen="sym-pen">
+0 0 m
+-1 0.333 l
+-1 -0.333 l
+h
+-1 0 m
+-2 0.333 l
+-2 -0.333 l
+h
+</path>
+</symbol>
+<pen name="heavier" value="0.8"/>
+<pen name="fat" value="1.2"/>
+<pen name="ultrafat" value="2"/>
+<symbolsize name="large" value="5"/>
+<symbolsize name="small" value="2"/>
+<symbolsize name="tiny" value="1.1"/>
+<arrowsize name="large" value="10"/>
+<arrowsize name="small" value="5"/>
+<arrowsize name="tiny" value="3"/>
+<color name="red" value="1 0 0"/>
+<color name="green" value="0 1 0"/>
+<color name="blue" value="0 0 1"/>
+<color name="yellow" value="1 1 0"/>
+<color name="orange" value="1 0.647 0"/>
+<color name="gold" value="1 0.843 0"/>
+<color name="purple" value="0.627 0.125 0.941"/>
+<color name="gray" value="0.745"/>
+<color name="brown" value="0.647 0.165 0.165"/>
+<color name="navy" value="0 0 0.502"/>
+<color name="pink" value="1 0.753 0.796"/>
+<color name="seagreen" value="0.18 0.545 0.341"/>
+<color name="turquoise" value="0.251 0.878 0.816"/>
+<color name="violet" value="0.933 0.51 0.933"/>
+<color name="darkblue" value="0 0 0.545"/>
+<color name="darkcyan" value="0 0.545 0.545"/>
+<color name="darkgray" value="0.663"/>
+<color name="darkgreen" value="0 0.392 0"/>
+<color name="darkmagenta" value="0.545 0 0.545"/>
+<color name="darkorange" value="1 0.549 0"/>
+<color name="darkred" value="0.545 0 0"/>
+<color name="lightblue" value="0.678 0.847 0.902"/>
+<color name="lightcyan" value="0.878 1 1"/>
+<color name="lightgray" value="0.827"/>
+<color name="lightgreen" value="0.565 0.933 0.565"/>
+<color name="lightyellow" value="1 1 0.878"/>
+<dashstyle name="dashed" value="[4] 0"/>
+<dashstyle name="dotted" value="[1 3] 0"/>
+<dashstyle name="dash dotted" value="[4 2 1 2] 0"/>
+<dashstyle name="dash dot dotted" value="[4 2 1 2 1 2] 0"/>
+<textsize name="large" value="\large"/>
+<textsize name="small" value="\small"/>
+<textsize name="tiny" value="\tiny"/>
+<textsize name="Large" value="\Large"/>
+<textsize name="LARGE" value="\LARGE"/>
+<textsize name="huge" value="\huge"/>
+<textsize name="Huge" value="\Huge"/>
+<textsize name="footnote" value="\footnotesize"/>
+<textstyle name="center" begin="\begin{center}" end="\end{center}"/>
+<textstyle name="itemize" begin="\begin{itemize}" end="\end{itemize}"/>
+<textstyle name="item" begin="\begin{itemize}\item{}" end="\end{itemize}"/>
+<gridsize name="4 pts" value="4"/>
+<gridsize name="8 pts (~3 mm)" value="8"/>
+<gridsize name="16 pts (~6 mm)" value="16"/>
+<gridsize name="32 pts (~12 mm)" value="32"/>
+<gridsize name="10 pts (~3.5 mm)" value="10"/>
+<gridsize name="20 pts (~7 mm)" value="20"/>
+<gridsize name="14 pts (~5 mm)" value="14"/>
+<gridsize name="28 pts (~10 mm)" value="28"/>
+<gridsize name="56 pts (~20 mm)" value="56"/>
+<anglesize name="90 deg" value="90"/>
+<anglesize name="60 deg" value="60"/>
+<anglesize name="45 deg" value="45"/>
+<anglesize name="30 deg" value="30"/>
+<anglesize name="22.5 deg" value="22.5"/>
+<opacity name="10%" value="0.1"/>
+<opacity name="30%" value="0.3"/>
+<opacity name="50%" value="0.5"/>
+<opacity name="75%" value="0.75"/>
+<tiling name="falling" angle="-60" step="4" width="1"/>
+<tiling name="rising" angle="30" step="4" width="1"/>
+</ipestyle>
+<page>
+<layer name="alpha"/>
+<view layers="alpha" active="alpha"/>
+<path layer="alpha" stroke="black" fill="gray" pen="ultrafat">
+128 704 m
+128 640 l
+448 640 l
+448 704 l
+h
+</path>
+<path stroke="black" pen="ultrafat">
+128 704 m
+128 384 l
+448 384 l
+448 704 l
+h
+</path>
+<path stroke="black" pen="ultrafat">
+176 592 m
+176 432 l
+400 432 l
+400 592 l
+h
+</path>
+<path stroke="black" fill="gray" pen="ultrafat">
+176 592 m
+176 560 l
+400 560 l
+400 592 l
+h
+</path>
+<path stroke="black" pen="ultrafat">
+416 544 m
+416 544 l
+416 544 l
+416 544 l
+h
+</path>
+</page>
+</ipe>
diff --git a/resources/pugl.png b/resources/pugl.png
new file mode 100644
index 0000000..4641660
--- /dev/null
+++ b/resources/pugl.png
Binary files differ
diff --git a/resources/pugl.svg b/resources/pugl.svg
new file mode 100644
index 0000000..5bb5335
--- /dev/null
+++ b/resources/pugl.svg
@@ -0,0 +1,83 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ version="1.1"
+ id="svg2"
+ xml:space="preserve"
+ width="544"
+ height="544"
+ viewBox="0 0 544.00035 544.00035"
+ sodipodi:docname="pugl.svg"
+ inkscape:version="0.92.4 5da689c313, 2019-01-14"
+ inkscape:export-filename="/home/drobilla/src/pugl/resources/pugl.png"
+ inkscape:export-xdpi="90.352943"
+ inkscape:export-ydpi="90.352943">
+ <metadata
+ id="metadata8"><rdf:RDF><cc:Work
+ rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title></dc:title></cc:Work></rdf:RDF></metadata>
+ <defs
+ id="defs6" />
+ <sodipodi:namedview
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1"
+ objecttolerance="10"
+ gridtolerance="10"
+ guidetolerance="10"
+ inkscape:pageopacity="0"
+ inkscape:pageshadow="2"
+ inkscape:window-width="3816"
+ inkscape:window-height="2100"
+ id="namedview4"
+ showgrid="false"
+ inkscape:zoom="2"
+ inkscape:cx="399.81254"
+ inkscape:cy="304.68124"
+ inkscape:window-x="12"
+ inkscape:window-y="48"
+ inkscape:window-maximized="0"
+ inkscape:current-layer="g10"
+ fit-margin-top="16"
+ fit-margin-left="16"
+ fit-margin-right="16"
+ fit-margin-bottom="16"
+ inkscape:document-units="mm"
+ scale-x="1" /><g
+ id="g10"
+ inkscape:groupmode="layer"
+ inkscape:label="ink_ext_XXXXXX"
+ transform="matrix(1.3333333,0,0,-1.3333333,57.333339,486.66667)"
+ style="filter:url(#filter5238)"><path
+ inkscape:connector-curvature="0"
+ id="path16"
+ style="opacity:1;vector-effect:none;fill:#4b4ba3;fill-opacity:1;stroke:#000000;stroke-width:13.87951851;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;"
+ d="M -24.060246,272.03615 H 346.06025 v 74.0241 H -24.060246 Z" /><path
+ inkscape:connector-curvature="0"
+ id="path18"
+ style="opacity:1;vector-effect:none;fill:none;fill-opacity:1;stroke:#000000;stroke-width:13.87951851;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;"
+ d="M -24.060246,-24.060246 H 346.06025 V 346.06025 H -24.060246 Z" /><path
+ inkscape:connector-curvature="0"
+ id="path20"
+ style="opacity:1;vector-effect:none;fill:none;fill-opacity:1;stroke:#000000;stroke-width:13.87951851;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;"
+ d="M 31.457828,31.457828 H 290.54217 V 216.51807 H 31.457828 Z" /><path
+ inkscape:connector-curvature="0"
+ id="path24"
+ style="opacity:1;vector-effect:none;fill:#4b4ba3;fill-opacity:1;stroke:#000000;stroke-width:13.87951851;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;"
+ d="M 31.457828,179.50602 H 290.54217 v 37.01205 H 31.457828 Z" /><path
+ inkscape:connector-curvature="0"
+ id="path14"
+ style="opacity:1;vector-effect:none;fill:#546e00;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:13.87951851;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;"
+ d="M -24.060246,272.03615 H 346.06025 v 74.0241 H -24.060246 Z" /><path
+ inkscape:connector-curvature="0"
+ id="path22"
+ style="opacity:1;vector-effect:none;fill:#546e00;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:13.87951851;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;"
+ d="M 31.457828,179.50602 H 290.54217 v 37.01205 H 31.457828 Z" /></g></svg>
diff --git a/pugl_cairo_test.c b/test/pugl_cairo_test.c
index c04c785..a16c821 100644
--- a/pugl_cairo_test.c
+++ b/test/pugl_cairo_test.c
@@ -1,5 +1,5 @@
/*
- Copyright 2012-2014 David Robillard <http://drobilla.net>
+ Copyright 2012-2019 David Robillard <http://drobilla.net>
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
@@ -18,27 +18,37 @@
@file pugl_cairo_test.c A simple Pugl test that creates a top-level window.
*/
-#include <stdint.h>
-#include <stdio.h>
-#include <string.h>
-
-#include <cairo/cairo.h>
+#include "test_utils.h"
#include "pugl/pugl.h"
+#include "pugl/pugl_cairo_backend.h"
+
+#include <cairo.h>
+
+#include <math.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <string.h>
-static int quit = 0;
-static bool entered = false;
+static bool continuous = false;
+static int quit = 0;
+static bool entered = false;
+static bool mouseDown = false;
+static unsigned framesDrawn = 0;
typedef struct {
int x;
int y;
int w;
int h;
- bool pressed;
const char* label;
} Button;
-static Button toggle_button = { 16, 16, 128, 64, false, "Test" };
+static Button buttons[] = { { 128, 128, 64, 64, "1" },
+ { 384, 128, 64, 64, "2" },
+ { 128, 384, 64, 64, "3" },
+ { 384, 384, 64, 64, "4" },
+ { 0, 0, 0, 0, NULL } };
static void
roundedBox(cairo_t* cr, double x, double y, double w, double h)
@@ -64,15 +74,19 @@ roundedBox(cairo_t* cr, double x, double y, double w, double h)
}
static void
-buttonDraw(cairo_t* cr, const Button* but)
+buttonDraw(cairo_t* cr, const Button* but, const double time)
{
+ cairo_save(cr);
+ cairo_translate(cr, but->x, but->y);
+ cairo_rotate(cr, sin(time) * 3.141592);
+
// Draw base
- if (but->pressed) {
+ if (mouseDown) {
cairo_set_source_rgba(cr, 0.4, 0.9, 0.1, 1);
} else {
cairo_set_source_rgba(cr, 0.3, 0.5, 0.1, 1);
}
- roundedBox(cr, but->x, but->y, but->w, but->h);
+ roundedBox(cr, 0, 0, but->w, but->h);
cairo_fill_preserve(cr);
// Draw border
@@ -85,23 +99,18 @@ buttonDraw(cairo_t* cr, const Button* but)
cairo_set_font_size(cr, 32.0);
cairo_text_extents(cr, but->label, &extents);
cairo_move_to(cr,
- (but->x + but->w / 2) - extents.width / 2,
- (but->y + but->h / 2) + extents.height / 2);
+ (but->w / 2.0) - extents.width / 2,
+ (but->h / 2.0) + extents.height / 2);
cairo_set_source_rgba(cr, 0, 0, 0, 1);
cairo_show_text(cr, but->label);
-}
-static bool
-buttonTouches(const Button* but, double x, double y)
-{
- return (x >= toggle_button.x && x <= toggle_button.x + toggle_button.w &&
- y >= toggle_button.y && y <= toggle_button.y + toggle_button.h);
+ cairo_restore(cr);
}
static void
onDisplay(PuglView* view)
{
- cairo_t* cr = puglGetContext(view);
+ cairo_t* cr = (cairo_t*)puglGetContext(view);
// Draw background
int width, height;
@@ -114,13 +123,23 @@ onDisplay(PuglView* view)
cairo_rectangle(cr, 0, 0, width, height);
cairo_fill(cr);
+ // Scale to view size
+ const double scaleX = (width - (512 / (double)width)) / 512.0;
+ const double scaleY = (height - (512 / (double)height)) / 512.0;
+ cairo_scale(cr, scaleX, scaleY);
+
// Draw button
- buttonDraw(cr, &toggle_button);
+ for (Button* b = buttons; b->label; ++b) {
+ buttonDraw(cr, b, continuous ? puglGetTime(view) : 0.0);
+ }
+
+ ++framesDrawn;
}
static void
onClose(PuglView* view)
{
+ (void)view;
quit = 1;
}
@@ -129,17 +148,17 @@ onEvent(PuglView* view, const PuglEvent* event)
{
switch (event->type) {
case PUGL_KEY_PRESS:
- if (event->key.character == 'q' ||
- event->key.character == 'Q' ||
- event->key.character == PUGL_CHAR_ESCAPE) {
+ if (event->key.key == 'q' || event->key.key == PUGL_KEY_ESCAPE) {
quit = 1;
}
break;
case PUGL_BUTTON_PRESS:
- if (buttonTouches(&toggle_button, event->button.x, event->button.y)) {
- toggle_button.pressed = !toggle_button.pressed;
- puglPostRedisplay(view);
- }
+ mouseDown = true;
+ puglPostRedisplay(view);
+ break;
+ case PUGL_BUTTON_RELEASE:
+ mouseDown = false;
+ puglPostRedisplay(view);
break;
case PUGL_ENTER_NOTIFY:
entered = true;
@@ -162,19 +181,18 @@ onEvent(PuglView* view, const PuglEvent* event)
int
main(int argc, char** argv)
{
- bool useGL = false;
bool ignoreKeyRepeat = false;
bool resizable = false;
for (int i = 1; i < argc; ++i) {
- if (!strcmp(argv[i], "-h")) {
+ if (!strcmp(argv[i], "-c")) {
+ continuous = true;
+ } else if (!strcmp(argv[i], "-h")) {
printf("USAGE: %s [OPTIONS]...\n\n"
- " -g Use OpenGL\n"
+ " -c Continuously animate and draw\n"
" -h Display this help\n"
" -i Ignore key repeat\n"
" -r Resizable window\n", argv[0]);
return 0;
- } else if (!strcmp(argv[i], "-g")) {
- useGL = true;
} else if (!strcmp(argv[i], "-i")) {
ignoreKeyRepeat = true;
} else if (!strcmp(argv[i], "-r")) {
@@ -185,19 +203,34 @@ main(int argc, char** argv)
}
PuglView* view = puglInit(NULL, NULL);
+ puglInitWindowClass(view, "PuglCairoTest");
puglInitWindowSize(view, 512, 512);
- puglInitResizable(view, resizable);
- puglInitContextType(view, useGL ? PUGL_CAIRO_GL : PUGL_CAIRO);
+ puglInitWindowMinSize(view, 256, 256);
+ puglInitWindowHint(view, PUGL_RESIZABLE, resizable);
+ puglInitBackend(view, puglCairoBackend());
- puglIgnoreKeyRepeat(view, ignoreKeyRepeat);
+ puglInitWindowHint(view, PUGL_IGNORE_KEY_REPEAT, ignoreKeyRepeat);
puglSetEventFunc(view, onEvent);
- puglCreateWindow(view, "Pugl Test");
+ if (puglCreateWindow(view, "Pugl Test")) {
+ return 1;
+ }
+
puglShowWindow(view);
+ PuglFpsPrinter fpsPrinter = { puglGetTime(view) };
while (!quit) {
- puglWaitForEvent(view);
+ if (continuous) {
+ puglPostRedisplay(view);
+ } else {
+ puglWaitForEvent(view);
+ }
+
puglProcessEvents(view);
+
+ if (continuous) {
+ puglPrintFps(view, &fpsPrinter, &framesDrawn);
+ }
}
puglDestroy(view);
diff --git a/test/pugl_test.c b/test/pugl_test.c
new file mode 100644
index 0000000..b8a0a42
--- /dev/null
+++ b/test/pugl_test.c
@@ -0,0 +1,229 @@
+/*
+ Copyright 2012-2019 David Robillard <http://drobilla.net>
+
+ Permission to use, copy, modify, and/or distribute this software for any
+ purpose with or without fee is hereby granted, provided that the above
+ copyright notice and this permission notice appear in all copies.
+
+ THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+*/
+
+/**
+ @file pugl_test.c A simple Pugl test that creates a top-level window.
+*/
+
+#define GL_SILENCE_DEPRECATION 1
+
+#include "test_utils.h"
+
+#include "pugl/gl.h"
+#include "pugl/pugl.h"
+#include "pugl/pugl_gl_backend.h"
+
+#include <math.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <string.h>
+
+typedef struct
+{
+ bool continuous;
+ int quit;
+ float xAngle;
+ float yAngle;
+ float dist;
+ double lastMouseX;
+ double lastMouseY;
+ double lastDrawTime;
+ unsigned framesDrawn;
+ bool mouseEntered;
+} PuglTestApp;
+
+static void
+onReshape(PuglView* view, int width, int height)
+{
+ (void)view;
+
+ glEnable(GL_DEPTH_TEST);
+ glDepthFunc(GL_LESS);
+ glClearColor(0.2f, 0.2f, 0.2f, 1.0f);
+
+ glMatrixMode(GL_PROJECTION);
+ glLoadIdentity();
+ glViewport(0, 0, width, height);
+
+ float projection[16];
+ perspective(projection, 1.8f, width / (float)height, 1.0, 100.0f);
+ glLoadMatrixf(projection);
+}
+
+static void
+onDisplay(PuglView* view)
+{
+ PuglTestApp* app = (PuglTestApp*)puglGetHandle(view);
+
+ const double thisTime = puglGetTime(view);
+ if (app->continuous) {
+ const double dTime = thisTime - app->lastDrawTime;
+ app->xAngle = fmodf((float)(app->xAngle + dTime * 100.0f), 360.0f);
+ app->yAngle = fmodf((float)(app->yAngle + dTime * 100.0f), 360.0f);
+ }
+
+ glMatrixMode(GL_MODELVIEW);
+ glLoadIdentity();
+ glTranslatef(0.0f, 0.0f, app->dist * -1);
+ glRotatef(app->xAngle, 0.0f, 1.0f, 0.0f);
+ glRotatef(app->yAngle, 1.0f, 0.0f, 0.0f);
+
+ const float bg = app->mouseEntered ? 0.2f : 0.0f;
+ glClearColor(bg, bg, bg, 1.0f);
+ glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
+ glEnableClientState(GL_VERTEX_ARRAY);
+ glEnableClientState(GL_COLOR_ARRAY);
+
+ glVertexPointer(3, GL_FLOAT, 0, cubeVertices);
+ glColorPointer(3, GL_FLOAT, 0, cubeVertices);
+ glDrawArrays(GL_TRIANGLES, 0, 12 * 3);
+
+ glDisableClientState(GL_VERTEX_ARRAY);
+ glDisableClientState(GL_COLOR_ARRAY);
+
+ app->lastDrawTime = thisTime;
+ ++app->framesDrawn;
+}
+
+static void
+onEvent(PuglView* view, const PuglEvent* event)
+{
+ PuglTestApp* app = (PuglTestApp*)puglGetHandle(view);
+
+ printEvent(event, "Event: ");
+
+ switch (event->type) {
+ case PUGL_CONFIGURE:
+ onReshape(view, (int)event->configure.width, (int)event->configure.height);
+ break;
+ case PUGL_EXPOSE:
+ onDisplay(view);
+ break;
+ case PUGL_CLOSE:
+ app->quit = 1;
+ break;
+ case PUGL_KEY_PRESS:
+ if (event->key.key == 'q' || event->key.key == PUGL_KEY_ESCAPE) {
+ app->quit = 1;
+ }
+ break;
+ case PUGL_MOTION_NOTIFY:
+ app->xAngle = fmodf(app->xAngle - (float)(event->motion.x - app->lastMouseX), 360.0f);
+ app->yAngle = fmodf(app->yAngle + (float)(event->motion.y - app->lastMouseY), 360.0f);
+ app->lastMouseX = event->motion.x;
+ app->lastMouseY = event->motion.y;
+ puglPostRedisplay(view);
+ break;
+ case PUGL_SCROLL:
+ app->dist = fmaxf(10.0f, app->dist + (float)event->scroll.dy);
+ puglPostRedisplay(view);
+ break;
+ case PUGL_ENTER_NOTIFY:
+ app->mouseEntered = true;
+ break;
+ case PUGL_LEAVE_NOTIFY:
+ app->mouseEntered = false;
+ break;
+ default:
+ break;
+ }
+}
+
+int
+main(int argc, char** argv)
+{
+ PuglTestApp app = {0};
+ app.dist = 10;
+
+ int samples = 0;
+ int doubleBuffer = PUGL_FALSE;
+ bool ignoreKeyRepeat = false;
+ bool resizable = false;
+ for (int i = 1; i < argc; ++i) {
+ if (!strcmp(argv[i], "-a")) {
+ samples = 4;
+ } else if (!strcmp(argv[i], "-c")) {
+ app.continuous = true;
+ } else if (!strcmp(argv[i], "-d")) {
+ doubleBuffer = PUGL_TRUE;
+ } else if (!strcmp(argv[i], "-h")) {
+ printf("USAGE: %s [OPTIONS]...\n\n"
+ " -a Enable anti-aliasing\n"
+ " -c Continuously animate and draw\n"
+ " -d Enable double-buffering\n"
+ " -h Display this help\n"
+ " -i Ignore key repeat\n"
+ " -r Resizable window\n", argv[0]);
+ return 0;
+ } else if (!strcmp(argv[i], "-i")) {
+ ignoreKeyRepeat = true;
+ } else if (!strcmp(argv[i], "-r")) {
+ resizable = true;
+ } else {
+ fprintf(stderr, "Unknown option: %s\n", argv[i]);
+ }
+ }
+
+ PuglView* view = puglInit(NULL, NULL);
+ puglInitWindowClass(view, "PuglTest");
+ puglInitWindowSize(view, 512, 512);
+ puglInitWindowMinSize(view, 256, 256);
+ puglInitWindowAspectRatio(view, 1, 1, 16, 9);
+ puglInitBackend(view, puglGlBackend());
+
+ puglInitWindowHint(view, PUGL_RESIZABLE, resizable);
+ puglInitWindowHint(view, PUGL_SAMPLES, samples);
+ puglInitWindowHint(view, PUGL_DOUBLE_BUFFER, doubleBuffer);
+
+ puglInitWindowHint(view, PUGL_IGNORE_KEY_REPEAT, ignoreKeyRepeat);
+ puglSetEventFunc(view, onEvent);
+ puglSetHandle(view, &app);
+
+ const uint8_t title[] = { 'P', 'u', 'g', 'l', ' ',
+ 'P', 'r', 0xC3, 0xBC, 'f', 'u', 'n', 'g', 0 };
+ if (puglCreateWindow(view, (const char*)title)) {
+ return 1;
+ }
+
+ puglShowWindow(view);
+
+ PuglFpsPrinter fpsPrinter = { puglGetTime(view) };
+ bool requestedAttention = false;
+ while (!app.quit) {
+ const double thisTime = puglGetTime(view);
+
+ if (app.continuous) {
+ puglPostRedisplay(view);
+ } else {
+ puglWaitForEvent(view);
+ }
+
+ puglProcessEvents(view);
+
+ if (!requestedAttention && thisTime > 5.0) {
+ puglRequestAttention(view);
+ requestedAttention = true;
+ }
+
+ if (app.continuous) {
+ puglPrintFps(view, &fpsPrinter, &app.framesDrawn);
+ }
+ }
+
+ puglDestroy(view);
+ return 0;
+}
diff --git a/test/test_utils.h b/test/test_utils.h
new file mode 100644
index 0000000..9738d96
--- /dev/null
+++ b/test/test_utils.h
@@ -0,0 +1,167 @@
+/*
+ Copyright 2012-2019 David Robillard <http://drobilla.net>
+
+ Permission to use, copy, modify, and/or distribute this software for any
+ purpose with or without fee is hereby granted, provided that the above
+ copyright notice and this permission notice appear in all copies.
+
+ THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+*/
+
+#include "pugl/pugl.h"
+
+#include <math.h>
+#include <stdint.h>
+#include <stdio.h>
+
+typedef struct {
+ double lastReportTime;
+} PuglFpsPrinter;
+
+static const float cubeVertices[] = {
+ -1.0f, -1.0f, -1.0f,
+ -1.0f, -1.0f, 1.0f,
+ -1.0f, 1.0f, 1.0f,
+
+ 1.0f, 1.0f, -1.0f,
+ -1.0f, -1.0f, -1.0f,
+ -1.0f, 1.0f, -1.0f,
+
+ 1.0f, -1.0f, 1.0f,
+ -1.0f, -1.0f, -1.0f,
+ 1.0f, -1.0f, -1.0f,
+
+ 1.0f, 1.0f, -1.0f,
+ 1.0f, -1.0f, -1.0f,
+ -1.0f, -1.0f, -1.0f,
+
+ -1.0f, -1.0f, -1.0f,
+ -1.0f, 1.0f, 1.0f,
+ -1.0f, 1.0f, -1.0f,
+
+ 1.0f, -1.0f, 1.0f,
+ -1.0f, -1.0f, 1.0f,
+ -1.0f, -1.0f, -1.0f,
+
+ -1.0f, 1.0f, 1.0f,
+ -1.0f, -1.0f, 1.0f,
+ 1.0f, -1.0f, 1.0f,
+
+ 1.0f, 1.0f, 1.0f,
+ 1.0f, -1.0f, -1.0f,
+ 1.0f, 1.0f, -1.0f,
+
+ 1.0f, -1.0f, -1.0f,
+ 1.0f, 1.0f, 1.0f,
+ 1.0f, -1.0f, 1.0f,
+
+ 1.0f, 1.0f, 1.0f,
+ 1.0f, 1.0f, -1.0f,
+ -1.0f, 1.0f, -1.0f,
+
+ 1.0f, 1.0f, 1.0f,
+ -1.0f, 1.0f, -1.0f,
+ -1.0f, 1.0f, 1.0f,
+
+ 1.0f, 1.0f, 1.0f,
+ -1.0f, 1.0f, 1.0f,
+ 1.0f, -1.0f, 1.0f
+};
+
+/** Calculate a projection matrix for a given perspective. */
+static inline void
+perspective(float* m, float fov, float aspect, float zNear, float zFar)
+{
+ const float h = tanf(fov);
+ const float w = h / aspect;
+ const float depth = zNear - zFar;
+ const float q = (zFar + zNear) / depth;
+ const float qn = 2 * zFar * zNear / depth;
+
+ m[0] = w; m[1] = 0; m[2] = 0; m[3] = 0;
+ m[4] = 0; m[5] = h; m[6] = 0; m[7] = 0;
+ m[8] = 0; m[9] = 0; m[10] = q; m[11] = -1;
+ m[12] = 0; m[13] = 0; m[14] = qn; m[15] = 0;
+}
+
+static inline int
+printModifiers(const uint32_t mods)
+{
+ return fprintf(stderr, "Modifiers:%s%s%s%s\n",
+ (mods & PUGL_MOD_SHIFT) ? " Shift" : "",
+ (mods & PUGL_MOD_CTRL) ? " Ctrl" : "",
+ (mods & PUGL_MOD_ALT) ? " Alt" : "",
+ (mods & PUGL_MOD_SUPER) ? " Super" : "");
+}
+
+static inline int
+printEvent(const PuglEvent* event, const char* prefix)
+{
+ switch (event->type) {
+ case PUGL_KEY_PRESS:
+ return fprintf(stderr, "%sKey press code %3u key U+%04X\n",
+ prefix, event->key.keycode, event->key.key);
+ case PUGL_KEY_RELEASE:
+ return fprintf(stderr, "%sKey release code %3u key U+%04X\n",
+ prefix, event->key.keycode, event->key.key);
+ case PUGL_TEXT:
+ return fprintf(stderr, "%sText entry code %3u char U+%04X (%s)\n",
+ prefix, event->text.keycode,
+ event->text.character, event->text.string);
+ case PUGL_BUTTON_PRESS:
+ case PUGL_BUTTON_RELEASE:
+ return (fprintf(stderr, "%sMouse %d %s at %f,%f ",
+ prefix,
+ event->button.button,
+ (event->type == PUGL_BUTTON_PRESS) ? "down" : "up",
+ event->button.x,
+ event->button.y) +
+ printModifiers(event->scroll.state));
+ case PUGL_SCROLL:
+ return (fprintf(stderr, "%sScroll %f %f %f %f ",
+ prefix,
+ event->scroll.x, event->scroll.y,
+ event->scroll.dx, event->scroll.dy) +
+ printModifiers(event->scroll.state));
+ case PUGL_ENTER_NOTIFY:
+ return fprintf(stderr, "%sMouse enter at %f,%f\n",
+ prefix, event->crossing.x, event->crossing.y);
+ case PUGL_LEAVE_NOTIFY:
+ return fprintf(stderr, "%sMouse leave at %f,%f\n",
+ prefix, event->crossing.x, event->crossing.y);
+ case PUGL_FOCUS_IN:
+ return fprintf(stderr, "%sFocus in%s\n",
+ prefix, event->focus.grab ? " (grab)" : "");
+ case PUGL_FOCUS_OUT:
+ return fprintf(stderr, "%sFocus out%s\n",
+ prefix, event->focus.grab ? " (ungrab)" : "");
+ default: break;
+ }
+
+ return 0;
+}
+
+static inline void
+puglPrintFps(PuglView* view,
+ PuglFpsPrinter* printer,
+ unsigned* const framesDrawn)
+{
+ const double thisTime = puglGetTime(view);
+ if (thisTime > printer->lastReportTime + 5) {
+ const double fps = *framesDrawn / (thisTime - printer->lastReportTime);
+ fprintf(stderr,
+ "%u frames in %.0f seconds = %.3f FPS\n",
+ *framesDrawn,
+ thisTime - printer->lastReportTime,
+ fps);
+
+ printer->lastReportTime = thisTime;
+ *framesDrawn = 0;
+ }
+}
diff --git a/waf b/waf
index 2dbb06b..e22930a 100755
--- a/waf
+++ b/waf
Binary files differ
diff --git a/waflib/.gitignore b/waflib/.gitignore
new file mode 100644
index 0000000..8d35cb3
--- /dev/null
+++ b/waflib/.gitignore
@@ -0,0 +1,2 @@
+__pycache__
+*.pyc
diff --git a/waflib/Build.py b/waflib/Build.py
new file mode 100644
index 0000000..8143dbc
--- /dev/null
+++ b/waflib/Build.py
@@ -0,0 +1,1496 @@
+#!/usr/bin/env python
+# encoding: utf-8
+# Thomas Nagy, 2005-2018 (ita)
+
+"""
+Classes related to the build phase (build, clean, install, step, etc)
+
+The inheritance tree is the following:
+
+"""
+
+import os, sys, errno, re, shutil, stat
+try:
+ import cPickle
+except ImportError:
+ import pickle as cPickle
+from waflib import Node, Runner, TaskGen, Utils, ConfigSet, Task, Logs, Options, Context, Errors
+
+CACHE_DIR = 'c4che'
+"""Name of the cache directory"""
+
+CACHE_SUFFIX = '_cache.py'
+"""ConfigSet cache files for variants are written under :py:attr:´waflib.Build.CACHE_DIR´ in the form ´variant_name´_cache.py"""
+
+INSTALL = 1337
+"""Positive value '->' install, see :py:attr:`waflib.Build.BuildContext.is_install`"""
+
+UNINSTALL = -1337
+"""Negative value '<-' uninstall, see :py:attr:`waflib.Build.BuildContext.is_install`"""
+
+SAVED_ATTRS = 'root node_sigs task_sigs imp_sigs raw_deps node_deps'.split()
+"""Build class members to save between the runs; these should be all dicts
+except for `root` which represents a :py:class:`waflib.Node.Node` instance
+"""
+
+CFG_FILES = 'cfg_files'
+"""Files from the build directory to hash before starting the build (``config.h`` written during the configuration)"""
+
+POST_AT_ONCE = 0
+"""Post mode: all task generators are posted before any task executed"""
+
+POST_LAZY = 1
+"""Post mode: post the task generators group after group, the tasks in the next group are created when the tasks in the previous groups are done"""
+
+PROTOCOL = -1
+if sys.platform == 'cli':
+ PROTOCOL = 0
+
+class BuildContext(Context.Context):
+ '''executes the build'''
+
+ cmd = 'build'
+ variant = ''
+
+ def __init__(self, **kw):
+ super(BuildContext, self).__init__(**kw)
+
+ self.is_install = 0
+ """Non-zero value when installing or uninstalling file"""
+
+ self.top_dir = kw.get('top_dir', Context.top_dir)
+ """See :py:attr:`waflib.Context.top_dir`; prefer :py:attr:`waflib.Build.BuildContext.srcnode`"""
+
+ self.out_dir = kw.get('out_dir', Context.out_dir)
+ """See :py:attr:`waflib.Context.out_dir`; prefer :py:attr:`waflib.Build.BuildContext.bldnode`"""
+
+ self.run_dir = kw.get('run_dir', Context.run_dir)
+ """See :py:attr:`waflib.Context.run_dir`"""
+
+ self.launch_dir = Context.launch_dir
+ """See :py:attr:`waflib.Context.out_dir`; prefer :py:meth:`waflib.Build.BuildContext.launch_node`"""
+
+ self.post_mode = POST_LAZY
+ """Whether to post the task generators at once or group-by-group (default is group-by-group)"""
+
+ self.cache_dir = kw.get('cache_dir')
+ if not self.cache_dir:
+ self.cache_dir = os.path.join(self.out_dir, CACHE_DIR)
+
+ self.all_envs = {}
+ """Map names to :py:class:`waflib.ConfigSet.ConfigSet`, the empty string must map to the default environment"""
+
+ # ======================================= #
+ # cache variables
+
+ self.node_sigs = {}
+ """Dict mapping build nodes to task identifier (uid), it indicates whether a task created a particular file (persists across builds)"""
+
+ self.task_sigs = {}
+ """Dict mapping task identifiers (uid) to task signatures (persists across builds)"""
+
+ self.imp_sigs = {}
+ """Dict mapping task identifiers (uid) to implicit task dependencies used for scanning targets (persists across builds)"""
+
+ self.node_deps = {}
+ """Dict mapping task identifiers (uid) to node dependencies found by :py:meth:`waflib.Task.Task.scan` (persists across builds)"""
+
+ self.raw_deps = {}
+ """Dict mapping task identifiers (uid) to custom data returned by :py:meth:`waflib.Task.Task.scan` (persists across builds)"""
+
+ self.task_gen_cache_names = {}
+
+ self.jobs = Options.options.jobs
+ """Amount of jobs to run in parallel"""
+
+ self.targets = Options.options.targets
+ """List of targets to build (default: \\*)"""
+
+ self.keep = Options.options.keep
+ """Whether the build should continue past errors"""
+
+ self.progress_bar = Options.options.progress_bar
+ """
+ Level of progress status:
+
+ 0. normal output
+ 1. progress bar
+ 2. IDE output
+ 3. No output at all
+ """
+
+ # Manual dependencies.
+ self.deps_man = Utils.defaultdict(list)
+ """Manual dependencies set by :py:meth:`waflib.Build.BuildContext.add_manual_dependency`"""
+
+ # just the structure here
+ self.current_group = 0
+ """
+ Current build group
+ """
+
+ self.groups = []
+ """
+ List containing lists of task generators
+ """
+
+ self.group_names = {}
+ """
+ Map group names to the group lists. See :py:meth:`waflib.Build.BuildContext.add_group`
+ """
+
+ for v in SAVED_ATTRS:
+ if not hasattr(self, v):
+ setattr(self, v, {})
+
+ def get_variant_dir(self):
+ """Getter for the variant_dir attribute"""
+ if not self.variant:
+ return self.out_dir
+ return os.path.join(self.out_dir, os.path.normpath(self.variant))
+ variant_dir = property(get_variant_dir, None)
+
+ def __call__(self, *k, **kw):
+ """
+ Create a task generator and add it to the current build group. The following forms are equivalent::
+
+ def build(bld):
+ tg = bld(a=1, b=2)
+
+ def build(bld):
+ tg = bld()
+ tg.a = 1
+ tg.b = 2
+
+ def build(bld):
+ tg = TaskGen.task_gen(a=1, b=2)
+ bld.add_to_group(tg, None)
+
+ :param group: group name to add the task generator to
+ :type group: string
+ """
+ kw['bld'] = self
+ ret = TaskGen.task_gen(*k, **kw)
+ self.task_gen_cache_names = {} # reset the cache, each time
+ self.add_to_group(ret, group=kw.get('group'))
+ return ret
+
+ def __copy__(self):
+ """
+ Build contexts cannot be copied
+
+ :raises: :py:class:`waflib.Errors.WafError`
+ """
+ raise Errors.WafError('build contexts cannot be copied')
+
+ def load_envs(self):
+ """
+ The configuration command creates files of the form ``build/c4che/NAMEcache.py``. This method
+ creates a :py:class:`waflib.ConfigSet.ConfigSet` instance for each ``NAME`` by reading those
+ files and stores them in :py:attr:`waflib.Build.BuildContext.allenvs`.
+ """
+ node = self.root.find_node(self.cache_dir)
+ if not node:
+ raise Errors.WafError('The project was not configured: run "waf configure" first!')
+ lst = node.ant_glob('**/*%s' % CACHE_SUFFIX, quiet=True)
+
+ if not lst:
+ raise Errors.WafError('The cache directory is empty: reconfigure the project')
+
+ for x in lst:
+ name = x.path_from(node).replace(CACHE_SUFFIX, '').replace('\\', '/')
+ env = ConfigSet.ConfigSet(x.abspath())
+ self.all_envs[name] = env
+ for f in env[CFG_FILES]:
+ newnode = self.root.find_resource(f)
+ if not newnode or not newnode.exists():
+ raise Errors.WafError('Missing configuration file %r, reconfigure the project!' % f)
+
+ def init_dirs(self):
+ """
+ Initialize the project directory and the build directory by creating the nodes
+ :py:attr:`waflib.Build.BuildContext.srcnode` and :py:attr:`waflib.Build.BuildContext.bldnode`
+ corresponding to ``top_dir`` and ``variant_dir`` respectively. The ``bldnode`` directory is
+ created if necessary.
+ """
+ if not (os.path.isabs(self.top_dir) and os.path.isabs(self.out_dir)):
+ raise Errors.WafError('The project was not configured: run "waf configure" first!')
+
+ self.path = self.srcnode = self.root.find_dir(self.top_dir)
+ self.bldnode = self.root.make_node(self.variant_dir)
+ self.bldnode.mkdir()
+
+ def execute(self):
+ """
+ Restore data from previous builds and call :py:meth:`waflib.Build.BuildContext.execute_build`.
+ Overrides from :py:func:`waflib.Context.Context.execute`
+ """
+ self.restore()
+ if not self.all_envs:
+ self.load_envs()
+ self.execute_build()
+
+ def execute_build(self):
+ """
+ Execute the build by:
+
+ * reading the scripts (see :py:meth:`waflib.Context.Context.recurse`)
+ * calling :py:meth:`waflib.Build.BuildContext.pre_build` to call user build functions
+ * calling :py:meth:`waflib.Build.BuildContext.compile` to process the tasks
+ * calling :py:meth:`waflib.Build.BuildContext.post_build` to call user build functions
+ """
+
+ Logs.info("Waf: Entering directory `%s'", self.variant_dir)
+ self.recurse([self.run_dir])
+ self.pre_build()
+
+ # display the time elapsed in the progress bar
+ self.timer = Utils.Timer()
+
+ try:
+ self.compile()
+ finally:
+ if self.progress_bar == 1 and sys.stderr.isatty():
+ c = self.producer.processed or 1
+ m = self.progress_line(c, c, Logs.colors.BLUE, Logs.colors.NORMAL)
+ Logs.info(m, extra={'stream': sys.stderr, 'c1': Logs.colors.cursor_off, 'c2' : Logs.colors.cursor_on})
+ Logs.info("Waf: Leaving directory `%s'", self.variant_dir)
+ try:
+ self.producer.bld = None
+ del self.producer
+ except AttributeError:
+ pass
+ self.post_build()
+
+ def restore(self):
+ """
+ Load data from a previous run, sets the attributes listed in :py:const:`waflib.Build.SAVED_ATTRS`
+ """
+ try:
+ env = ConfigSet.ConfigSet(os.path.join(self.cache_dir, 'build.config.py'))
+ except EnvironmentError:
+ pass
+ else:
+ if env.version < Context.HEXVERSION:
+ raise Errors.WafError('Project was configured with a different version of Waf, please reconfigure it')
+
+ for t in env.tools:
+ self.setup(**t)
+
+ dbfn = os.path.join(self.variant_dir, Context.DBFILE)
+ try:
+ data = Utils.readf(dbfn, 'rb')
+ except (EnvironmentError, EOFError):
+ # handle missing file/empty file
+ Logs.debug('build: Could not load the build cache %s (missing)', dbfn)
+ else:
+ try:
+ Node.pickle_lock.acquire()
+ Node.Nod3 = self.node_class
+ try:
+ data = cPickle.loads(data)
+ except Exception as e:
+ Logs.debug('build: Could not pickle the build cache %s: %r', dbfn, e)
+ else:
+ for x in SAVED_ATTRS:
+ setattr(self, x, data.get(x, {}))
+ finally:
+ Node.pickle_lock.release()
+
+ self.init_dirs()
+
+ def store(self):
+ """
+ Store data for next runs, set the attributes listed in :py:const:`waflib.Build.SAVED_ATTRS`. Uses a temporary
+ file to avoid problems on ctrl+c.
+ """
+ data = {}
+ for x in SAVED_ATTRS:
+ data[x] = getattr(self, x)
+ db = os.path.join(self.variant_dir, Context.DBFILE)
+
+ try:
+ Node.pickle_lock.acquire()
+ Node.Nod3 = self.node_class
+ x = cPickle.dumps(data, PROTOCOL)
+ finally:
+ Node.pickle_lock.release()
+
+ Utils.writef(db + '.tmp', x, m='wb')
+
+ try:
+ st = os.stat(db)
+ os.remove(db)
+ if not Utils.is_win32: # win32 has no chown but we're paranoid
+ os.chown(db + '.tmp', st.st_uid, st.st_gid)
+ except (AttributeError, OSError):
+ pass
+
+ # do not use shutil.move (copy is not thread-safe)
+ os.rename(db + '.tmp', db)
+
+ def compile(self):
+ """
+ Run the build by creating an instance of :py:class:`waflib.Runner.Parallel`
+ The cache file is written when at least a task was executed.
+
+ :raises: :py:class:`waflib.Errors.BuildError` in case the build fails
+ """
+ Logs.debug('build: compile()')
+
+ # delegate the producer-consumer logic to another object to reduce the complexity
+ self.producer = Runner.Parallel(self, self.jobs)
+ self.producer.biter = self.get_build_iterator()
+ try:
+ self.producer.start()
+ except KeyboardInterrupt:
+ if self.is_dirty():
+ self.store()
+ raise
+ else:
+ if self.is_dirty():
+ self.store()
+
+ if self.producer.error:
+ raise Errors.BuildError(self.producer.error)
+
+ def is_dirty(self):
+ return self.producer.dirty
+
+ def setup(self, tool, tooldir=None, funs=None):
+ """
+ Import waf tools defined during the configuration::
+
+ def configure(conf):
+ conf.load('glib2')
+
+ def build(bld):
+ pass # glib2 is imported implicitly
+
+ :param tool: tool list
+ :type tool: list
+ :param tooldir: optional tool directory (sys.path)
+ :type tooldir: list of string
+ :param funs: unused variable
+ """
+ if isinstance(tool, list):
+ for i in tool:
+ self.setup(i, tooldir)
+ return
+
+ module = Context.load_tool(tool, tooldir)
+ if hasattr(module, "setup"):
+ module.setup(self)
+
+ def get_env(self):
+ """Getter for the env property"""
+ try:
+ return self.all_envs[self.variant]
+ except KeyError:
+ return self.all_envs['']
+ def set_env(self, val):
+ """Setter for the env property"""
+ self.all_envs[self.variant] = val
+
+ env = property(get_env, set_env)
+
+ def add_manual_dependency(self, path, value):
+ """
+ Adds a dependency from a node object to a value::
+
+ def build(bld):
+ bld.add_manual_dependency(
+ bld.path.find_resource('wscript'),
+ bld.root.find_resource('/etc/fstab'))
+
+ :param path: file path
+ :type path: string or :py:class:`waflib.Node.Node`
+ :param value: value to depend
+ :type value: :py:class:`waflib.Node.Node`, byte object, or function returning a byte object
+ """
+ if not path:
+ raise ValueError('Invalid input path %r' % path)
+
+ if isinstance(path, Node.Node):
+ node = path
+ elif os.path.isabs(path):
+ node = self.root.find_resource(path)
+ else:
+ node = self.path.find_resource(path)
+ if not node:
+ raise ValueError('Could not find the path %r' % path)
+
+ if isinstance(value, list):
+ self.deps_man[node].extend(value)
+ else:
+ self.deps_man[node].append(value)
+
+ def launch_node(self):
+ """Returns the launch directory as a :py:class:`waflib.Node.Node` object (cached)"""
+ try:
+ # private cache
+ return self.p_ln
+ except AttributeError:
+ self.p_ln = self.root.find_dir(self.launch_dir)
+ return self.p_ln
+
+ def hash_env_vars(self, env, vars_lst):
+ """
+ Hashes configuration set variables::
+
+ def build(bld):
+ bld.hash_env_vars(bld.env, ['CXX', 'CC'])
+
+ This method uses an internal cache.
+
+ :param env: Configuration Set
+ :type env: :py:class:`waflib.ConfigSet.ConfigSet`
+ :param vars_lst: list of variables
+ :type vars_list: list of string
+ """
+
+ if not env.table:
+ env = env.parent
+ if not env:
+ return Utils.SIG_NIL
+
+ idx = str(id(env)) + str(vars_lst)
+ try:
+ cache = self.cache_env
+ except AttributeError:
+ cache = self.cache_env = {}
+ else:
+ try:
+ return self.cache_env[idx]
+ except KeyError:
+ pass
+
+ lst = [env[a] for a in vars_lst]
+ cache[idx] = ret = Utils.h_list(lst)
+ Logs.debug('envhash: %s %r', Utils.to_hex(ret), lst)
+ return ret
+
+ def get_tgen_by_name(self, name):
+ """
+ Fetches a task generator by its name or its target attribute;
+ the name must be unique in a build::
+
+ def build(bld):
+ tg = bld(name='foo')
+ tg == bld.get_tgen_by_name('foo')
+
+ This method use a private internal cache.
+
+ :param name: Task generator name
+ :raises: :py:class:`waflib.Errors.WafError` in case there is no task genenerator by that name
+ """
+ cache = self.task_gen_cache_names
+ if not cache:
+ # create the index lazily
+ for g in self.groups:
+ for tg in g:
+ try:
+ cache[tg.name] = tg
+ except AttributeError:
+ # raised if not a task generator, which should be uncommon
+ pass
+ try:
+ return cache[name]
+ except KeyError:
+ raise Errors.WafError('Could not find a task generator for the name %r' % name)
+
+ def progress_line(self, idx, total, col1, col2):
+ """
+ Computes a progress bar line displayed when running ``waf -p``
+
+ :returns: progress bar line
+ :rtype: string
+ """
+ if not sys.stderr.isatty():
+ return ''
+
+ n = len(str(total))
+
+ Utils.rot_idx += 1
+ ind = Utils.rot_chr[Utils.rot_idx % 4]
+
+ pc = (100. * idx)/total
+ fs = "[%%%dd/%%d][%%s%%2d%%%%%%s][%s][" % (n, ind)
+ left = fs % (idx, total, col1, pc, col2)
+ right = '][%s%s%s]' % (col1, self.timer, col2)
+
+ cols = Logs.get_term_cols() - len(left) - len(right) + 2*len(col1) + 2*len(col2)
+ if cols < 7:
+ cols = 7
+
+ ratio = ((cols * idx)//total) - 1
+
+ bar = ('='*ratio+'>').ljust(cols)
+ msg = Logs.indicator % (left, bar, right)
+
+ return msg
+
+ def declare_chain(self, *k, **kw):
+ """
+ Wraps :py:func:`waflib.TaskGen.declare_chain` for convenience
+ """
+ return TaskGen.declare_chain(*k, **kw)
+
+ def pre_build(self):
+ """Executes user-defined methods before the build starts, see :py:meth:`waflib.Build.BuildContext.add_pre_fun`"""
+ for m in getattr(self, 'pre_funs', []):
+ m(self)
+
+ def post_build(self):
+ """Executes user-defined methods after the build is successful, see :py:meth:`waflib.Build.BuildContext.add_post_fun`"""
+ for m in getattr(self, 'post_funs', []):
+ m(self)
+
+ def add_pre_fun(self, meth):
+ """
+ Binds a callback method to execute after the scripts are read and before the build starts::
+
+ def mycallback(bld):
+ print("Hello, world!")
+
+ def build(bld):
+ bld.add_pre_fun(mycallback)
+ """
+ try:
+ self.pre_funs.append(meth)
+ except AttributeError:
+ self.pre_funs = [meth]
+
+ def add_post_fun(self, meth):
+ """
+ Binds a callback method to execute immediately after the build is successful::
+
+ def call_ldconfig(bld):
+ bld.exec_command('/sbin/ldconfig')
+
+ def build(bld):
+ if bld.cmd == 'install':
+ bld.add_pre_fun(call_ldconfig)
+ """
+ try:
+ self.post_funs.append(meth)
+ except AttributeError:
+ self.post_funs = [meth]
+
+ def get_group(self, x):
+ """
+ Returns the build group named `x`, or the current group if `x` is None
+
+ :param x: name or number or None
+ :type x: string, int or None
+ """
+ if not self.groups:
+ self.add_group()
+ if x is None:
+ return self.groups[self.current_group]
+ if x in self.group_names:
+ return self.group_names[x]
+ return self.groups[x]
+
+ def add_to_group(self, tgen, group=None):
+ """Adds a task or a task generator to the build; there is no attempt to remove it if it was already added."""
+ assert(isinstance(tgen, TaskGen.task_gen) or isinstance(tgen, Task.Task))
+ tgen.bld = self
+ self.get_group(group).append(tgen)
+
+ def get_group_name(self, g):
+ """
+ Returns the name of the input build group
+
+ :param g: build group object or build group index
+ :type g: integer or list
+ :return: name
+ :rtype: string
+ """
+ if not isinstance(g, list):
+ g = self.groups[g]
+ for x in self.group_names:
+ if id(self.group_names[x]) == id(g):
+ return x
+ return ''
+
+ def get_group_idx(self, tg):
+ """
+ Returns the index of the group containing the task generator given as argument::
+
+ def build(bld):
+ tg = bld(name='nada')
+ 0 == bld.get_group_idx(tg)
+
+ :param tg: Task generator object
+ :type tg: :py:class:`waflib.TaskGen.task_gen`
+ :rtype: int
+ """
+ se = id(tg)
+ for i, tmp in enumerate(self.groups):
+ for t in tmp:
+ if id(t) == se:
+ return i
+ return None
+
+ def add_group(self, name=None, move=True):
+ """
+ Adds a new group of tasks/task generators. By default the new group becomes
+ the default group for new task generators (make sure to create build groups in order).
+
+ :param name: name for this group
+ :type name: string
+ :param move: set this new group as default group (True by default)
+ :type move: bool
+ :raises: :py:class:`waflib.Errors.WafError` if a group by the name given already exists
+ """
+ if name and name in self.group_names:
+ raise Errors.WafError('add_group: name %s already present', name)
+ g = []
+ self.group_names[name] = g
+ self.groups.append(g)
+ if move:
+ self.current_group = len(self.groups) - 1
+
+ def set_group(self, idx):
+ """
+ Sets the build group at position idx as current so that newly added
+ task generators are added to this one by default::
+
+ def build(bld):
+ bld(rule='touch ${TGT}', target='foo.txt')
+ bld.add_group() # now the current group is 1
+ bld(rule='touch ${TGT}', target='bar.txt')
+ bld.set_group(0) # now the current group is 0
+ bld(rule='touch ${TGT}', target='truc.txt') # build truc.txt before bar.txt
+
+ :param idx: group name or group index
+ :type idx: string or int
+ """
+ if isinstance(idx, str):
+ g = self.group_names[idx]
+ for i, tmp in enumerate(self.groups):
+ if id(g) == id(tmp):
+ self.current_group = i
+ break
+ else:
+ self.current_group = idx
+
+ def total(self):
+ """
+ Approximate task count: this value may be inaccurate if task generators
+ are posted lazily (see :py:attr:`waflib.Build.BuildContext.post_mode`).
+ The value :py:attr:`waflib.Runner.Parallel.total` is updated during the task execution.
+
+ :rtype: int
+ """
+ total = 0
+ for group in self.groups:
+ for tg in group:
+ try:
+ total += len(tg.tasks)
+ except AttributeError:
+ total += 1
+ return total
+
+ def get_targets(self):
+ """
+ This method returns a pair containing the index of the last build group to post,
+ and the list of task generator objects corresponding to the target names.
+
+ This is used internally by :py:meth:`waflib.Build.BuildContext.get_build_iterator`
+ to perform partial builds::
+
+ $ waf --targets=myprogram,myshlib
+
+ :return: the minimum build group index, and list of task generators
+ :rtype: tuple
+ """
+ to_post = []
+ min_grp = 0
+ for name in self.targets.split(','):
+ tg = self.get_tgen_by_name(name)
+ m = self.get_group_idx(tg)
+ if m > min_grp:
+ min_grp = m
+ to_post = [tg]
+ elif m == min_grp:
+ to_post.append(tg)
+ return (min_grp, to_post)
+
+ def get_all_task_gen(self):
+ """
+ Returns a list of all task generators for troubleshooting purposes.
+ """
+ lst = []
+ for g in self.groups:
+ lst.extend(g)
+ return lst
+
+ def post_group(self):
+ """
+ Post task generators from the group indexed by self.current_group; used internally
+ by :py:meth:`waflib.Build.BuildContext.get_build_iterator`
+ """
+ def tgpost(tg):
+ try:
+ f = tg.post
+ except AttributeError:
+ pass
+ else:
+ f()
+
+ if self.targets == '*':
+ for tg in self.groups[self.current_group]:
+ tgpost(tg)
+ elif self.targets:
+ if self.current_group < self._min_grp:
+ for tg in self.groups[self.current_group]:
+ tgpost(tg)
+ else:
+ for tg in self._exact_tg:
+ tg.post()
+ else:
+ ln = self.launch_node()
+ if ln.is_child_of(self.bldnode):
+ Logs.warn('Building from the build directory, forcing --targets=*')
+ ln = self.srcnode
+ elif not ln.is_child_of(self.srcnode):
+ Logs.warn('CWD %s is not under %s, forcing --targets=* (run distclean?)', ln.abspath(), self.srcnode.abspath())
+ ln = self.srcnode
+
+ def is_post(tg, ln):
+ try:
+ p = tg.path
+ except AttributeError:
+ pass
+ else:
+ if p.is_child_of(ln):
+ return True
+
+ def is_post_group():
+ for i, g in enumerate(self.groups):
+ if i > self.current_group:
+ for tg in g:
+ if is_post(tg, ln):
+ return True
+
+ if self.post_mode == POST_LAZY and ln != self.srcnode:
+ # partial folder builds require all targets from a previous build group
+ if is_post_group():
+ ln = self.srcnode
+
+ for tg in self.groups[self.current_group]:
+ if is_post(tg, ln):
+ tgpost(tg)
+
+ def get_tasks_group(self, idx):
+ """
+ Returns all task instances for the build group at position idx,
+ used internally by :py:meth:`waflib.Build.BuildContext.get_build_iterator`
+
+ :rtype: list of :py:class:`waflib.Task.Task`
+ """
+ tasks = []
+ for tg in self.groups[idx]:
+ try:
+ tasks.extend(tg.tasks)
+ except AttributeError: # not a task generator
+ tasks.append(tg)
+ return tasks
+
+ def get_build_iterator(self):
+ """
+ Creates a Python generator object that returns lists of tasks that may be processed in parallel.
+
+ :return: tasks which can be executed immediately
+ :rtype: generator returning lists of :py:class:`waflib.Task.Task`
+ """
+ if self.targets and self.targets != '*':
+ (self._min_grp, self._exact_tg) = self.get_targets()
+
+ if self.post_mode != POST_LAZY:
+ for self.current_group, _ in enumerate(self.groups):
+ self.post_group()
+
+ for self.current_group, _ in enumerate(self.groups):
+ # first post the task generators for the group
+ if self.post_mode != POST_AT_ONCE:
+ self.post_group()
+
+ # then extract the tasks
+ tasks = self.get_tasks_group(self.current_group)
+
+ # if the constraints are set properly (ext_in/ext_out, before/after)
+ # the call to set_file_constraints may be removed (can be a 15% penalty on no-op rebuilds)
+ # (but leave set_file_constraints for the installation step)
+ #
+ # if the tasks have only files, set_file_constraints is required but set_precedence_constraints is not necessary
+ #
+ Task.set_file_constraints(tasks)
+ Task.set_precedence_constraints(tasks)
+
+ self.cur_tasks = tasks
+ if tasks:
+ yield tasks
+
+ while 1:
+ # the build stops once there are no tasks to process
+ yield []
+
+ def install_files(self, dest, files, **kw):
+ """
+ Creates a task generator to install files on the system::
+
+ def build(bld):
+ bld.install_files('${DATADIR}', self.path.find_resource('wscript'))
+
+ :param dest: path representing the destination directory
+ :type dest: :py:class:`waflib.Node.Node` or string (absolute path)
+ :param files: input files
+ :type files: list of strings or list of :py:class:`waflib.Node.Node`
+ :param env: configuration set to expand *dest*
+ :type env: :py:class:`waflib.ConfigSet.ConfigSet`
+ :param relative_trick: preserve the folder hierarchy when installing whole folders
+ :type relative_trick: bool
+ :param cwd: parent node for searching srcfile, when srcfile is not an instance of :py:class:`waflib.Node.Node`
+ :type cwd: :py:class:`waflib.Node.Node`
+ :param postpone: execute the task immediately to perform the installation (False by default)
+ :type postpone: bool
+ """
+ assert(dest)
+ tg = self(features='install_task', install_to=dest, install_from=files, **kw)
+ tg.dest = tg.install_to
+ tg.type = 'install_files'
+ if not kw.get('postpone', True):
+ tg.post()
+ return tg
+
+ def install_as(self, dest, srcfile, **kw):
+ """
+ Creates a task generator to install a file on the system with a different name::
+
+ def build(bld):
+ bld.install_as('${PREFIX}/bin', 'myapp', chmod=Utils.O755)
+
+ :param dest: destination file
+ :type dest: :py:class:`waflib.Node.Node` or string (absolute path)
+ :param srcfile: input file
+ :type srcfile: string or :py:class:`waflib.Node.Node`
+ :param cwd: parent node for searching srcfile, when srcfile is not an instance of :py:class:`waflib.Node.Node`
+ :type cwd: :py:class:`waflib.Node.Node`
+ :param env: configuration set for performing substitutions in dest
+ :type env: :py:class:`waflib.ConfigSet.ConfigSet`
+ :param postpone: execute the task immediately to perform the installation (False by default)
+ :type postpone: bool
+ """
+ assert(dest)
+ tg = self(features='install_task', install_to=dest, install_from=srcfile, **kw)
+ tg.dest = tg.install_to
+ tg.type = 'install_as'
+ if not kw.get('postpone', True):
+ tg.post()
+ return tg
+
+ def symlink_as(self, dest, src, **kw):
+ """
+ Creates a task generator to install a symlink::
+
+ def build(bld):
+ bld.symlink_as('${PREFIX}/lib/libfoo.so', 'libfoo.so.1.2.3')
+
+ :param dest: absolute path of the symlink
+ :type dest: :py:class:`waflib.Node.Node` or string (absolute path)
+ :param src: link contents, which is a relative or absolute path which may exist or not
+ :type src: string
+ :param env: configuration set for performing substitutions in dest
+ :type env: :py:class:`waflib.ConfigSet.ConfigSet`
+ :param add: add the task created to a build group - set ``False`` only if the installation task is created after the build has started
+ :type add: bool
+ :param postpone: execute the task immediately to perform the installation
+ :type postpone: bool
+ :param relative_trick: make the symlink relative (default: ``False``)
+ :type relative_trick: bool
+ """
+ assert(dest)
+ tg = self(features='install_task', install_to=dest, install_from=src, **kw)
+ tg.dest = tg.install_to
+ tg.type = 'symlink_as'
+ tg.link = src
+ # TODO if add: self.add_to_group(tsk)
+ if not kw.get('postpone', True):
+ tg.post()
+ return tg
+
+@TaskGen.feature('install_task')
+@TaskGen.before_method('process_rule', 'process_source')
+def process_install_task(self):
+ """Creates the installation task for the current task generator; uses :py:func:`waflib.Build.add_install_task` internally."""
+ self.add_install_task(**self.__dict__)
+
+@TaskGen.taskgen_method
+def add_install_task(self, **kw):
+ """
+ Creates the installation task for the current task generator, and executes it immediately if necessary
+
+ :returns: An installation task
+ :rtype: :py:class:`waflib.Build.inst`
+ """
+ if not self.bld.is_install:
+ return
+ if not kw['install_to']:
+ return
+
+ if kw['type'] == 'symlink_as' and Utils.is_win32:
+ if kw.get('win32_install'):
+ kw['type'] = 'install_as'
+ else:
+ # just exit
+ return
+
+ tsk = self.install_task = self.create_task('inst')
+ tsk.chmod = kw.get('chmod', Utils.O644)
+ tsk.link = kw.get('link', '') or kw.get('install_from', '')
+ tsk.relative_trick = kw.get('relative_trick', False)
+ tsk.type = kw['type']
+ tsk.install_to = tsk.dest = kw['install_to']
+ tsk.install_from = kw['install_from']
+ tsk.relative_base = kw.get('cwd') or kw.get('relative_base', self.path)
+ tsk.install_user = kw.get('install_user')
+ tsk.install_group = kw.get('install_group')
+ tsk.init_files()
+ if not kw.get('postpone', True):
+ tsk.run_now()
+ return tsk
+
+@TaskGen.taskgen_method
+def add_install_files(self, **kw):
+ """
+ Creates an installation task for files
+
+ :returns: An installation task
+ :rtype: :py:class:`waflib.Build.inst`
+ """
+ kw['type'] = 'install_files'
+ return self.add_install_task(**kw)
+
+@TaskGen.taskgen_method
+def add_install_as(self, **kw):
+ """
+ Creates an installation task for a single file
+
+ :returns: An installation task
+ :rtype: :py:class:`waflib.Build.inst`
+ """
+ kw['type'] = 'install_as'
+ return self.add_install_task(**kw)
+
+@TaskGen.taskgen_method
+def add_symlink_as(self, **kw):
+ """
+ Creates an installation task for a symbolic link
+
+ :returns: An installation task
+ :rtype: :py:class:`waflib.Build.inst`
+ """
+ kw['type'] = 'symlink_as'
+ return self.add_install_task(**kw)
+
+class inst(Task.Task):
+ """Task that installs files or symlinks; it is typically executed by :py:class:`waflib.Build.InstallContext` and :py:class:`waflib.Build.UnInstallContext`"""
+ def __str__(self):
+ """Returns an empty string to disable the standard task display"""
+ return ''
+
+ def uid(self):
+ """Returns a unique identifier for the task"""
+ lst = self.inputs + self.outputs + [self.link, self.generator.path.abspath()]
+ return Utils.h_list(lst)
+
+ def init_files(self):
+ """
+ Initializes the task input and output nodes
+ """
+ if self.type == 'symlink_as':
+ inputs = []
+ else:
+ inputs = self.generator.to_nodes(self.install_from)
+ if self.type == 'install_as':
+ assert len(inputs) == 1
+ self.set_inputs(inputs)
+
+ dest = self.get_install_path()
+ outputs = []
+ if self.type == 'symlink_as':
+ if self.relative_trick:
+ self.link = os.path.relpath(self.link, os.path.dirname(dest))
+ outputs.append(self.generator.bld.root.make_node(dest))
+ elif self.type == 'install_as':
+ outputs.append(self.generator.bld.root.make_node(dest))
+ else:
+ for y in inputs:
+ if self.relative_trick:
+ destfile = os.path.join(dest, y.path_from(self.relative_base))
+ else:
+ destfile = os.path.join(dest, y.name)
+ outputs.append(self.generator.bld.root.make_node(destfile))
+ self.set_outputs(outputs)
+
+ def runnable_status(self):
+ """
+ Installation tasks are always executed, so this method returns either :py:const:`waflib.Task.ASK_LATER` or :py:const:`waflib.Task.RUN_ME`.
+ """
+ ret = super(inst, self).runnable_status()
+ if ret == Task.SKIP_ME and self.generator.bld.is_install:
+ return Task.RUN_ME
+ return ret
+
+ def post_run(self):
+ """
+ Disables any post-run operations
+ """
+ pass
+
+ def get_install_path(self, destdir=True):
+ """
+ Returns the destination path where files will be installed, pre-pending `destdir`.
+
+ Relative paths will be interpreted relative to `PREFIX` if no `destdir` is given.
+
+ :rtype: string
+ """
+ if isinstance(self.install_to, Node.Node):
+ dest = self.install_to.abspath()
+ else:
+ dest = os.path.normpath(Utils.subst_vars(self.install_to, self.env))
+ if not os.path.isabs(dest):
+ dest = os.path.join(self.env.PREFIX, dest)
+ if destdir and Options.options.destdir:
+ dest = os.path.join(Options.options.destdir, os.path.splitdrive(dest)[1].lstrip(os.sep))
+ return dest
+
+ def copy_fun(self, src, tgt):
+ """
+ Copies a file from src to tgt, preserving permissions and trying to work
+ around path limitations on Windows platforms. On Unix-like platforms,
+ the owner/group of the target file may be set through install_user/install_group
+
+ :param src: absolute path
+ :type src: string
+ :param tgt: absolute path
+ :type tgt: string
+ """
+ # override this if you want to strip executables
+ # kw['tsk'].source is the task that created the files in the build
+ if Utils.is_win32 and len(tgt) > 259 and not tgt.startswith('\\\\?\\'):
+ tgt = '\\\\?\\' + tgt
+ shutil.copy2(src, tgt)
+ self.fix_perms(tgt)
+
+ def rm_empty_dirs(self, tgt):
+ """
+ Removes empty folders recursively when uninstalling.
+
+ :param tgt: absolute path
+ :type tgt: string
+ """
+ while tgt:
+ tgt = os.path.dirname(tgt)
+ try:
+ os.rmdir(tgt)
+ except OSError:
+ break
+
+ def run(self):
+ """
+ Performs file or symlink installation
+ """
+ is_install = self.generator.bld.is_install
+ if not is_install: # unnecessary?
+ return
+
+ for x in self.outputs:
+ if is_install == INSTALL:
+ x.parent.mkdir()
+ if self.type == 'symlink_as':
+ fun = is_install == INSTALL and self.do_link or self.do_unlink
+ fun(self.link, self.outputs[0].abspath())
+ else:
+ fun = is_install == INSTALL and self.do_install or self.do_uninstall
+ launch_node = self.generator.bld.launch_node()
+ for x, y in zip(self.inputs, self.outputs):
+ fun(x.abspath(), y.abspath(), x.path_from(launch_node))
+
+ def run_now(self):
+ """
+ Try executing the installation task right now
+
+ :raises: :py:class:`waflib.Errors.TaskNotReady`
+ """
+ status = self.runnable_status()
+ if status not in (Task.RUN_ME, Task.SKIP_ME):
+ raise Errors.TaskNotReady('Could not process %r: status %r' % (self, status))
+ self.run()
+ self.hasrun = Task.SUCCESS
+
+ def do_install(self, src, tgt, lbl, **kw):
+ """
+ Copies a file from src to tgt with given file permissions. The actual copy is only performed
+ if the source and target file sizes or timestamps differ. When the copy occurs,
+ the file is always first removed and then copied so as to prevent stale inodes.
+
+ :param src: file name as absolute path
+ :type src: string
+ :param tgt: file destination, as absolute path
+ :type tgt: string
+ :param lbl: file source description
+ :type lbl: string
+ :param chmod: installation mode
+ :type chmod: int
+ :raises: :py:class:`waflib.Errors.WafError` if the file cannot be written
+ """
+ if not Options.options.force:
+ # check if the file is already there to avoid a copy
+ try:
+ st1 = os.stat(tgt)
+ st2 = os.stat(src)
+ except OSError:
+ pass
+ else:
+ # same size and identical timestamps -> make no copy
+ if st1.st_mtime + 2 >= st2.st_mtime and st1.st_size == st2.st_size:
+ if not self.generator.bld.progress_bar:
+ Logs.info('- install %s (from %s)', tgt, lbl)
+ return False
+
+ if not self.generator.bld.progress_bar:
+ Logs.info('+ install %s (from %s)', tgt, lbl)
+
+ # Give best attempt at making destination overwritable,
+ # like the 'install' utility used by 'make install' does.
+ try:
+ os.chmod(tgt, Utils.O644 | stat.S_IMODE(os.stat(tgt).st_mode))
+ except EnvironmentError:
+ pass
+
+ # following is for shared libs and stale inodes (-_-)
+ try:
+ os.remove(tgt)
+ except OSError:
+ pass
+
+ try:
+ self.copy_fun(src, tgt)
+ except EnvironmentError as e:
+ if not os.path.exists(src):
+ Logs.error('File %r does not exist', src)
+ elif not os.path.isfile(src):
+ Logs.error('Input %r is not a file', src)
+ raise Errors.WafError('Could not install the file %r' % tgt, e)
+
+ def fix_perms(self, tgt):
+ """
+ Change the ownership of the file/folder/link pointed by the given path
+ This looks up for `install_user` or `install_group` attributes
+ on the task or on the task generator::
+
+ def build(bld):
+ bld.install_as('${PREFIX}/wscript',
+ 'wscript',
+ install_user='nobody', install_group='nogroup')
+ bld.symlink_as('${PREFIX}/wscript_link',
+ Utils.subst_vars('${PREFIX}/wscript', bld.env),
+ install_user='nobody', install_group='nogroup')
+ """
+ if not Utils.is_win32:
+ user = getattr(self, 'install_user', None) or getattr(self.generator, 'install_user', None)
+ group = getattr(self, 'install_group', None) or getattr(self.generator, 'install_group', None)
+ if user or group:
+ Utils.lchown(tgt, user or -1, group or -1)
+ if not os.path.islink(tgt):
+ os.chmod(tgt, self.chmod)
+
+ def do_link(self, src, tgt, **kw):
+ """
+ Creates a symlink from tgt to src.
+
+ :param src: file name as absolute path
+ :type src: string
+ :param tgt: file destination, as absolute path
+ :type tgt: string
+ """
+ if os.path.islink(tgt) and os.readlink(tgt) == src:
+ if not self.generator.bld.progress_bar:
+ Logs.info('- symlink %s (to %s)', tgt, src)
+ else:
+ try:
+ os.remove(tgt)
+ except OSError:
+ pass
+ if not self.generator.bld.progress_bar:
+ Logs.info('+ symlink %s (to %s)', tgt, src)
+ os.symlink(src, tgt)
+ self.fix_perms(tgt)
+
+ def do_uninstall(self, src, tgt, lbl, **kw):
+ """
+ See :py:meth:`waflib.Build.inst.do_install`
+ """
+ if not self.generator.bld.progress_bar:
+ Logs.info('- remove %s', tgt)
+
+ #self.uninstall.append(tgt)
+ try:
+ os.remove(tgt)
+ except OSError as e:
+ if e.errno != errno.ENOENT:
+ if not getattr(self, 'uninstall_error', None):
+ self.uninstall_error = True
+ Logs.warn('build: some files could not be uninstalled (retry with -vv to list them)')
+ if Logs.verbose > 1:
+ Logs.warn('Could not remove %s (error code %r)', e.filename, e.errno)
+ self.rm_empty_dirs(tgt)
+
+ def do_unlink(self, src, tgt, **kw):
+ """
+ See :py:meth:`waflib.Build.inst.do_link`
+ """
+ try:
+ if not self.generator.bld.progress_bar:
+ Logs.info('- remove %s', tgt)
+ os.remove(tgt)
+ except OSError:
+ pass
+ self.rm_empty_dirs(tgt)
+
+class InstallContext(BuildContext):
+ '''installs the targets on the system'''
+ cmd = 'install'
+
+ def __init__(self, **kw):
+ super(InstallContext, self).__init__(**kw)
+ self.is_install = INSTALL
+
+class UninstallContext(InstallContext):
+ '''removes the targets installed'''
+ cmd = 'uninstall'
+
+ def __init__(self, **kw):
+ super(UninstallContext, self).__init__(**kw)
+ self.is_install = UNINSTALL
+
+class CleanContext(BuildContext):
+ '''cleans the project'''
+ cmd = 'clean'
+ def execute(self):
+ """
+ See :py:func:`waflib.Build.BuildContext.execute`.
+ """
+ self.restore()
+ if not self.all_envs:
+ self.load_envs()
+
+ self.recurse([self.run_dir])
+ try:
+ self.clean()
+ finally:
+ self.store()
+
+ def clean(self):
+ """
+ Remove most files from the build directory, and reset all caches.
+
+ Custom lists of files to clean can be declared as `bld.clean_files`.
+ For example, exclude `build/program/myprogram` from getting removed::
+
+ def build(bld):
+ bld.clean_files = bld.bldnode.ant_glob('**',
+ excl='.lock* config.log c4che/* config.h program/myprogram',
+ quiet=True, generator=True)
+ """
+ Logs.debug('build: clean called')
+
+ if hasattr(self, 'clean_files'):
+ for n in self.clean_files:
+ n.delete()
+ elif self.bldnode != self.srcnode:
+ # would lead to a disaster if top == out
+ lst = []
+ for env in self.all_envs.values():
+ lst.extend(self.root.find_or_declare(f) for f in env[CFG_FILES])
+ excluded_dirs = '.lock* *conf_check_*/** config.log %s/*' % CACHE_DIR
+ for n in self.bldnode.ant_glob('**/*', excl=excluded_dirs, quiet=True):
+ if n in lst:
+ continue
+ n.delete()
+ self.root.children = {}
+
+ for v in SAVED_ATTRS:
+ if v == 'root':
+ continue
+ setattr(self, v, {})
+
+class ListContext(BuildContext):
+ '''lists the targets to execute'''
+ cmd = 'list'
+
+ def execute(self):
+ """
+ In addition to printing the name of each build target,
+ a description column will include text for each task
+ generator which has a "description" field set.
+
+ See :py:func:`waflib.Build.BuildContext.execute`.
+ """
+ self.restore()
+ if not self.all_envs:
+ self.load_envs()
+
+ self.recurse([self.run_dir])
+ self.pre_build()
+
+ # display the time elapsed in the progress bar
+ self.timer = Utils.Timer()
+
+ for g in self.groups:
+ for tg in g:
+ try:
+ f = tg.post
+ except AttributeError:
+ pass
+ else:
+ f()
+
+ try:
+ # force the cache initialization
+ self.get_tgen_by_name('')
+ except Errors.WafError:
+ pass
+
+ targets = sorted(self.task_gen_cache_names)
+
+ # figure out how much to left-justify, for largest target name
+ line_just = max(len(t) for t in targets) if targets else 0
+
+ for target in targets:
+ tgen = self.task_gen_cache_names[target]
+
+ # Support displaying the description for the target
+ # if it was set on the tgen
+ descript = getattr(tgen, 'description', '')
+ if descript:
+ target = target.ljust(line_just)
+ descript = ': %s' % descript
+
+ Logs.pprint('GREEN', target, label=descript)
+
+class StepContext(BuildContext):
+ '''executes tasks in a step-by-step fashion, for debugging'''
+ cmd = 'step'
+
+ def __init__(self, **kw):
+ super(StepContext, self).__init__(**kw)
+ self.files = Options.options.files
+
+ def compile(self):
+ """
+ Overrides :py:meth:`waflib.Build.BuildContext.compile` to perform a partial build
+ on tasks matching the input/output pattern given (regular expression matching)::
+
+ $ waf step --files=foo.c,bar.c,in:truc.c,out:bar.o
+ $ waf step --files=in:foo.cpp.1.o # link task only
+
+ """
+ if not self.files:
+ Logs.warn('Add a pattern for the debug build, for example "waf step --files=main.c,app"')
+ BuildContext.compile(self)
+ return
+
+ targets = []
+ if self.targets and self.targets != '*':
+ targets = self.targets.split(',')
+
+ for g in self.groups:
+ for tg in g:
+ if targets and tg.name not in targets:
+ continue
+
+ try:
+ f = tg.post
+ except AttributeError:
+ pass
+ else:
+ f()
+
+ for pat in self.files.split(','):
+ matcher = self.get_matcher(pat)
+ for tg in g:
+ if isinstance(tg, Task.Task):
+ lst = [tg]
+ else:
+ lst = tg.tasks
+ for tsk in lst:
+ do_exec = False
+ for node in tsk.inputs:
+ if matcher(node, output=False):
+ do_exec = True
+ break
+ for node in tsk.outputs:
+ if matcher(node, output=True):
+ do_exec = True
+ break
+ if do_exec:
+ ret = tsk.run()
+ Logs.info('%s -> exit %r', tsk, ret)
+
+ def get_matcher(self, pat):
+ """
+ Converts a step pattern into a function
+
+ :param: pat: pattern of the form in:truc.c,out:bar.o
+ :returns: Python function that uses Node objects as inputs and returns matches
+ :rtype: function
+ """
+ # this returns a function
+ inn = True
+ out = True
+ if pat.startswith('in:'):
+ out = False
+ pat = pat.replace('in:', '')
+ elif pat.startswith('out:'):
+ inn = False
+ pat = pat.replace('out:', '')
+
+ anode = self.root.find_node(pat)
+ pattern = None
+ if not anode:
+ if not pat.startswith('^'):
+ pat = '^.+?%s' % pat
+ if not pat.endswith('$'):
+ pat = '%s$' % pat
+ pattern = re.compile(pat)
+
+ def match(node, output):
+ if output and not out:
+ return False
+ if not output and not inn:
+ return False
+
+ if anode:
+ return anode == node
+ else:
+ return pattern.match(node.abspath())
+ return match
+
+class EnvContext(BuildContext):
+ """Subclass EnvContext to create commands that require configuration data in 'env'"""
+ fun = cmd = None
+ def execute(self):
+ """
+ See :py:func:`waflib.Build.BuildContext.execute`.
+ """
+ self.restore()
+ if not self.all_envs:
+ self.load_envs()
+ self.recurse([self.run_dir])
+
diff --git a/waflib/COPYING b/waflib/COPYING
new file mode 100644
index 0000000..a4147d2
--- /dev/null
+++ b/waflib/COPYING
@@ -0,0 +1,25 @@
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions
+are met:
+
+1. Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+
+2. Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+
+3. The name of the author may not be used to endorse or promote products
+ derived from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE AUTHOR "AS IS" AND ANY EXPRESS OR
+IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT,
+INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
+IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGE.
diff --git a/waflib/ConfigSet.py b/waflib/ConfigSet.py
new file mode 100644
index 0000000..901fba6
--- /dev/null
+++ b/waflib/ConfigSet.py
@@ -0,0 +1,361 @@
+#!/usr/bin/env python
+# encoding: utf-8
+# Thomas Nagy, 2005-2018 (ita)
+
+"""
+
+ConfigSet: a special dict
+
+The values put in :py:class:`ConfigSet` must be serializable (dicts, lists, strings)
+"""
+
+import copy, re, os
+from waflib import Logs, Utils
+re_imp = re.compile(r'^(#)*?([^#=]*?)\ =\ (.*?)$', re.M)
+
+class ConfigSet(object):
+ """
+ A copy-on-write dict with human-readable serialized format. The serialization format
+ is human-readable (python-like) and performed by using eval() and repr().
+ For high performance prefer pickle. Do not store functions as they are not serializable.
+
+ The values can be accessed by attributes or by keys::
+
+ from waflib.ConfigSet import ConfigSet
+ env = ConfigSet()
+ env.FOO = 'test'
+ env['FOO'] = 'test'
+ """
+ __slots__ = ('table', 'parent')
+ def __init__(self, filename=None):
+ self.table = {}
+ """
+ Internal dict holding the object values
+ """
+ #self.parent = None
+
+ if filename:
+ self.load(filename)
+
+ def __contains__(self, key):
+ """
+ Enables the *in* syntax::
+
+ if 'foo' in env:
+ print(env['foo'])
+ """
+ if key in self.table:
+ return True
+ try:
+ return self.parent.__contains__(key)
+ except AttributeError:
+ return False # parent may not exist
+
+ def keys(self):
+ """Dict interface"""
+ keys = set()
+ cur = self
+ while cur:
+ keys.update(cur.table.keys())
+ cur = getattr(cur, 'parent', None)
+ keys = list(keys)
+ keys.sort()
+ return keys
+
+ def __iter__(self):
+ return iter(self.keys())
+
+ def __str__(self):
+ """Text representation of the ConfigSet (for debugging purposes)"""
+ return "\n".join(["%r %r" % (x, self.__getitem__(x)) for x in self.keys()])
+
+ def __getitem__(self, key):
+ """
+ Dictionary interface: get value from key::
+
+ def configure(conf):
+ conf.env['foo'] = {}
+ print(env['foo'])
+ """
+ try:
+ while 1:
+ x = self.table.get(key)
+ if not x is None:
+ return x
+ self = self.parent
+ except AttributeError:
+ return []
+
+ def __setitem__(self, key, value):
+ """
+ Dictionary interface: set value from key
+ """
+ self.table[key] = value
+
+ def __delitem__(self, key):
+ """
+ Dictionary interface: mark the value as missing
+ """
+ self[key] = []
+
+ def __getattr__(self, name):
+ """
+ Attribute access provided for convenience. The following forms are equivalent::
+
+ def configure(conf):
+ conf.env.value
+ conf.env['value']
+ """
+ if name in self.__slots__:
+ return object.__getattribute__(self, name)
+ else:
+ return self[name]
+
+ def __setattr__(self, name, value):
+ """
+ Attribute access provided for convenience. The following forms are equivalent::
+
+ def configure(conf):
+ conf.env.value = x
+ env['value'] = x
+ """
+ if name in self.__slots__:
+ object.__setattr__(self, name, value)
+ else:
+ self[name] = value
+
+ def __delattr__(self, name):
+ """
+ Attribute access provided for convenience. The following forms are equivalent::
+
+ def configure(conf):
+ del env.value
+ del env['value']
+ """
+ if name in self.__slots__:
+ object.__delattr__(self, name)
+ else:
+ del self[name]
+
+ def derive(self):
+ """
+ Returns a new ConfigSet deriving from self. The copy returned
+ will be a shallow copy::
+
+ from waflib.ConfigSet import ConfigSet
+ env = ConfigSet()
+ env.append_value('CFLAGS', ['-O2'])
+ child = env.derive()
+ child.CFLAGS.append('test') # warning! this will modify 'env'
+ child.CFLAGS = ['-O3'] # new list, ok
+ child.append_value('CFLAGS', ['-O3']) # ok
+
+ Use :py:func:`ConfigSet.detach` to detach the child from the parent.
+ """
+ newenv = ConfigSet()
+ newenv.parent = self
+ return newenv
+
+ def detach(self):
+ """
+ Detaches this instance from its parent (if present)
+
+ Modifying the parent :py:class:`ConfigSet` will not change the current object
+ Modifying this :py:class:`ConfigSet` will not modify the parent one.
+ """
+ tbl = self.get_merged_dict()
+ try:
+ delattr(self, 'parent')
+ except AttributeError:
+ pass
+ else:
+ keys = tbl.keys()
+ for x in keys:
+ tbl[x] = copy.deepcopy(tbl[x])
+ self.table = tbl
+ return self
+
+ def get_flat(self, key):
+ """
+ Returns a value as a string. If the input is a list, the value returned is space-separated.
+
+ :param key: key to use
+ :type key: string
+ """
+ s = self[key]
+ if isinstance(s, str):
+ return s
+ return ' '.join(s)
+
+ def _get_list_value_for_modification(self, key):
+ """
+ Returns a list value for further modification.
+
+ The list may be modified inplace and there is no need to do this afterwards::
+
+ self.table[var] = value
+ """
+ try:
+ value = self.table[key]
+ except KeyError:
+ try:
+ value = self.parent[key]
+ except AttributeError:
+ value = []
+ else:
+ if isinstance(value, list):
+ # force a copy
+ value = value[:]
+ else:
+ value = [value]
+ self.table[key] = value
+ else:
+ if not isinstance(value, list):
+ self.table[key] = value = [value]
+ return value
+
+ def append_value(self, var, val):
+ """
+ Appends a value to the specified config key::
+
+ def build(bld):
+ bld.env.append_value('CFLAGS', ['-O2'])
+
+ The value must be a list or a tuple
+ """
+ if isinstance(val, str): # if there were string everywhere we could optimize this
+ val = [val]
+ current_value = self._get_list_value_for_modification(var)
+ current_value.extend(val)
+
+ def prepend_value(self, var, val):
+ """
+ Prepends a value to the specified item::
+
+ def configure(conf):
+ conf.env.prepend_value('CFLAGS', ['-O2'])
+
+ The value must be a list or a tuple
+ """
+ if isinstance(val, str):
+ val = [val]
+ self.table[var] = val + self._get_list_value_for_modification(var)
+
+ def append_unique(self, var, val):
+ """
+ Appends a value to the specified item only if it's not already present::
+
+ def build(bld):
+ bld.env.append_unique('CFLAGS', ['-O2', '-g'])
+
+ The value must be a list or a tuple
+ """
+ if isinstance(val, str):
+ val = [val]
+ current_value = self._get_list_value_for_modification(var)
+
+ for x in val:
+ if x not in current_value:
+ current_value.append(x)
+
+ def get_merged_dict(self):
+ """
+ Computes the merged dictionary from the fusion of self and all its parent
+
+ :rtype: a ConfigSet object
+ """
+ table_list = []
+ env = self
+ while 1:
+ table_list.insert(0, env.table)
+ try:
+ env = env.parent
+ except AttributeError:
+ break
+ merged_table = {}
+ for table in table_list:
+ merged_table.update(table)
+ return merged_table
+
+ def store(self, filename):
+ """
+ Serializes the :py:class:`ConfigSet` data to a file. See :py:meth:`ConfigSet.load` for reading such files.
+
+ :param filename: file to use
+ :type filename: string
+ """
+ try:
+ os.makedirs(os.path.split(filename)[0])
+ except OSError:
+ pass
+
+ buf = []
+ merged_table = self.get_merged_dict()
+ keys = list(merged_table.keys())
+ keys.sort()
+
+ try:
+ fun = ascii
+ except NameError:
+ fun = repr
+
+ for k in keys:
+ if k != 'undo_stack':
+ buf.append('%s = %s\n' % (k, fun(merged_table[k])))
+ Utils.writef(filename, ''.join(buf))
+
+ def load(self, filename):
+ """
+ Restores contents from a file (current values are not cleared). Files are written using :py:meth:`ConfigSet.store`.
+
+ :param filename: file to use
+ :type filename: string
+ """
+ tbl = self.table
+ code = Utils.readf(filename, m='r')
+ for m in re_imp.finditer(code):
+ g = m.group
+ tbl[g(2)] = eval(g(3))
+ Logs.debug('env: %s', self.table)
+
+ def update(self, d):
+ """
+ Dictionary interface: replace values with the ones from another dict
+
+ :param d: object to use the value from
+ :type d: dict-like object
+ """
+ self.table.update(d)
+
+ def stash(self):
+ """
+ Stores the object state to provide transactionality semantics::
+
+ env = ConfigSet()
+ env.stash()
+ try:
+ env.append_value('CFLAGS', '-O3')
+ call_some_method(env)
+ finally:
+ env.revert()
+
+ The history is kept in a stack, and is lost during the serialization by :py:meth:`ConfigSet.store`
+ """
+ orig = self.table
+ tbl = self.table = self.table.copy()
+ for x in tbl.keys():
+ tbl[x] = copy.deepcopy(tbl[x])
+ self.undo_stack = self.undo_stack + [orig]
+
+ def commit(self):
+ """
+ Commits transactional changes. See :py:meth:`ConfigSet.stash`
+ """
+ self.undo_stack.pop(-1)
+
+ def revert(self):
+ """
+ Reverts the object to a previous state. See :py:meth:`ConfigSet.stash`
+ """
+ self.table = self.undo_stack.pop(-1)
+
diff --git a/waflib/Configure.py b/waflib/Configure.py
new file mode 100644
index 0000000..db09c0e
--- /dev/null
+++ b/waflib/Configure.py
@@ -0,0 +1,639 @@
+#!/usr/bin/env python
+# encoding: utf-8
+# Thomas Nagy, 2005-2018 (ita)
+
+"""
+Configuration system
+
+A :py:class:`waflib.Configure.ConfigurationContext` instance is created when ``waf configure`` is called, it is used to:
+
+* create data dictionaries (ConfigSet instances)
+* store the list of modules to import
+* hold configuration routines such as ``find_program``, etc
+"""
+
+import os, re, shlex, shutil, sys, time, traceback
+from waflib import ConfigSet, Utils, Options, Logs, Context, Build, Errors
+
+WAF_CONFIG_LOG = 'config.log'
+"""Name of the configuration log file"""
+
+autoconfig = False
+"""Execute the configuration automatically"""
+
+conf_template = '''# project %(app)s configured on %(now)s by
+# waf %(wafver)s (abi %(abi)s, python %(pyver)x on %(systype)s)
+# using %(args)s
+#'''
+
+class ConfigurationContext(Context.Context):
+ '''configures the project'''
+
+ cmd = 'configure'
+
+ error_handlers = []
+ """
+ Additional functions to handle configuration errors
+ """
+
+ def __init__(self, **kw):
+ super(ConfigurationContext, self).__init__(**kw)
+ self.environ = dict(os.environ)
+ self.all_envs = {}
+
+ self.top_dir = None
+ self.out_dir = None
+
+ self.tools = [] # tools loaded in the configuration, and that will be loaded when building
+
+ self.hash = 0
+ self.files = []
+
+ self.tool_cache = []
+
+ self.setenv('')
+
+ def setenv(self, name, env=None):
+ """
+ Set a new config set for conf.env. If a config set of that name already exists,
+ recall it without modification.
+
+ The name is the filename prefix to save to ``c4che/NAME_cache.py``, and it
+ is also used as *variants* by the build commands.
+ Though related to variants, whatever kind of data may be stored in the config set::
+
+ def configure(cfg):
+ cfg.env.ONE = 1
+ cfg.setenv('foo')
+ cfg.env.ONE = 2
+
+ def build(bld):
+ 2 == bld.env_of_name('foo').ONE
+
+ :param name: name of the configuration set
+ :type name: string
+ :param env: ConfigSet to copy, or an empty ConfigSet is created
+ :type env: :py:class:`waflib.ConfigSet.ConfigSet`
+ """
+ if name not in self.all_envs or env:
+ if not env:
+ env = ConfigSet.ConfigSet()
+ self.prepare_env(env)
+ else:
+ env = env.derive()
+ self.all_envs[name] = env
+ self.variant = name
+
+ def get_env(self):
+ """Getter for the env property"""
+ return self.all_envs[self.variant]
+ def set_env(self, val):
+ """Setter for the env property"""
+ self.all_envs[self.variant] = val
+
+ env = property(get_env, set_env)
+
+ def init_dirs(self):
+ """
+ Initialize the project directory and the build directory
+ """
+
+ top = self.top_dir
+ if not top:
+ top = Options.options.top
+ if not top:
+ top = getattr(Context.g_module, Context.TOP, None)
+ if not top:
+ top = self.path.abspath()
+ top = os.path.abspath(top)
+
+ self.srcnode = (os.path.isabs(top) and self.root or self.path).find_dir(top)
+ assert(self.srcnode)
+
+ out = self.out_dir
+ if not out:
+ out = Options.options.out
+ if not out:
+ out = getattr(Context.g_module, Context.OUT, None)
+ if not out:
+ out = Options.lockfile.replace('.lock-waf_%s_' % sys.platform, '').replace('.lock-waf', '')
+
+ # someone can be messing with symlinks
+ out = os.path.realpath(out)
+
+ self.bldnode = (os.path.isabs(out) and self.root or self.path).make_node(out)
+ self.bldnode.mkdir()
+
+ if not os.path.isdir(self.bldnode.abspath()):
+ self.fatal('Could not create the build directory %s' % self.bldnode.abspath())
+
+ def execute(self):
+ """
+ See :py:func:`waflib.Context.Context.execute`
+ """
+ self.init_dirs()
+
+ self.cachedir = self.bldnode.make_node(Build.CACHE_DIR)
+ self.cachedir.mkdir()
+
+ path = os.path.join(self.bldnode.abspath(), WAF_CONFIG_LOG)
+ self.logger = Logs.make_logger(path, 'cfg')
+
+ app = getattr(Context.g_module, 'APPNAME', '')
+ if app:
+ ver = getattr(Context.g_module, 'VERSION', '')
+ if ver:
+ app = "%s (%s)" % (app, ver)
+
+ params = {'now': time.ctime(), 'pyver': sys.hexversion, 'systype': sys.platform, 'args': " ".join(sys.argv), 'wafver': Context.WAFVERSION, 'abi': Context.ABI, 'app': app}
+ self.to_log(conf_template % params)
+ self.msg('Setting top to', self.srcnode.abspath())
+ self.msg('Setting out to', self.bldnode.abspath())
+
+ if id(self.srcnode) == id(self.bldnode):
+ Logs.warn('Setting top == out')
+ elif id(self.path) != id(self.srcnode):
+ if self.srcnode.is_child_of(self.path):
+ Logs.warn('Are you certain that you do not want to set top="." ?')
+
+ super(ConfigurationContext, self).execute()
+
+ self.store()
+
+ Context.top_dir = self.srcnode.abspath()
+ Context.out_dir = self.bldnode.abspath()
+
+ # this will write a configure lock so that subsequent builds will
+ # consider the current path as the root directory (see prepare_impl).
+ # to remove: use 'waf distclean'
+ env = ConfigSet.ConfigSet()
+ env.argv = sys.argv
+ env.options = Options.options.__dict__
+ env.config_cmd = self.cmd
+
+ env.run_dir = Context.run_dir
+ env.top_dir = Context.top_dir
+ env.out_dir = Context.out_dir
+
+ # conf.hash & conf.files hold wscript files paths and hash
+ # (used only by Configure.autoconfig)
+ env.hash = self.hash
+ env.files = self.files
+ env.environ = dict(self.environ)
+ env.launch_dir = Context.launch_dir
+
+ if not (self.env.NO_LOCK_IN_RUN or env.environ.get('NO_LOCK_IN_RUN') or getattr(Options.options, 'no_lock_in_run')):
+ env.store(os.path.join(Context.run_dir, Options.lockfile))
+ if not (self.env.NO_LOCK_IN_TOP or env.environ.get('NO_LOCK_IN_TOP') or getattr(Options.options, 'no_lock_in_top')):
+ env.store(os.path.join(Context.top_dir, Options.lockfile))
+ if not (self.env.NO_LOCK_IN_OUT or env.environ.get('NO_LOCK_IN_OUT') or getattr(Options.options, 'no_lock_in_out')):
+ env.store(os.path.join(Context.out_dir, Options.lockfile))
+
+ def prepare_env(self, env):
+ """
+ Insert *PREFIX*, *BINDIR* and *LIBDIR* values into ``env``
+
+ :type env: :py:class:`waflib.ConfigSet.ConfigSet`
+ :param env: a ConfigSet, usually ``conf.env``
+ """
+ if not env.PREFIX:
+ if Options.options.prefix or Utils.is_win32:
+ env.PREFIX = Options.options.prefix
+ else:
+ env.PREFIX = '/'
+ if not env.BINDIR:
+ if Options.options.bindir:
+ env.BINDIR = Options.options.bindir
+ else:
+ env.BINDIR = Utils.subst_vars('${PREFIX}/bin', env)
+ if not env.LIBDIR:
+ if Options.options.libdir:
+ env.LIBDIR = Options.options.libdir
+ else:
+ env.LIBDIR = Utils.subst_vars('${PREFIX}/lib%s' % Utils.lib64(), env)
+
+ def store(self):
+ """Save the config results into the cache file"""
+ n = self.cachedir.make_node('build.config.py')
+ n.write('version = 0x%x\ntools = %r\n' % (Context.HEXVERSION, self.tools))
+
+ if not self.all_envs:
+ self.fatal('nothing to store in the configuration context!')
+
+ for key in self.all_envs:
+ tmpenv = self.all_envs[key]
+ tmpenv.store(os.path.join(self.cachedir.abspath(), key + Build.CACHE_SUFFIX))
+
+ def load(self, tool_list, tooldir=None, funs=None, with_sys_path=True, cache=False):
+ """
+ Load Waf tools, which will be imported whenever a build is started.
+
+ :param tool_list: waf tools to import
+ :type tool_list: list of string
+ :param tooldir: paths for the imports
+ :type tooldir: list of string
+ :param funs: functions to execute from the waf tools
+ :type funs: list of string
+ :param cache: whether to prevent the tool from running twice
+ :type cache: bool
+ """
+
+ tools = Utils.to_list(tool_list)
+ if tooldir:
+ tooldir = Utils.to_list(tooldir)
+ for tool in tools:
+ # avoid loading the same tool more than once with the same functions
+ # used by composite projects
+
+ if cache:
+ mag = (tool, id(self.env), tooldir, funs)
+ if mag in self.tool_cache:
+ self.to_log('(tool %s is already loaded, skipping)' % tool)
+ continue
+ self.tool_cache.append(mag)
+
+ module = None
+ try:
+ module = Context.load_tool(tool, tooldir, ctx=self, with_sys_path=with_sys_path)
+ except ImportError as e:
+ self.fatal('Could not load the Waf tool %r from %r\n%s' % (tool, getattr(e, 'waf_sys_path', sys.path), e))
+ except Exception as e:
+ self.to_log('imp %r (%r & %r)' % (tool, tooldir, funs))
+ self.to_log(traceback.format_exc())
+ raise
+
+ if funs is not None:
+ self.eval_rules(funs)
+ else:
+ func = getattr(module, 'configure', None)
+ if func:
+ if type(func) is type(Utils.readf):
+ func(self)
+ else:
+ self.eval_rules(func)
+
+ self.tools.append({'tool':tool, 'tooldir':tooldir, 'funs':funs})
+
+ def post_recurse(self, node):
+ """
+ Records the path and a hash of the scripts visited, see :py:meth:`waflib.Context.Context.post_recurse`
+
+ :param node: script
+ :type node: :py:class:`waflib.Node.Node`
+ """
+ super(ConfigurationContext, self).post_recurse(node)
+ self.hash = Utils.h_list((self.hash, node.read('rb')))
+ self.files.append(node.abspath())
+
+ def eval_rules(self, rules):
+ """
+ Execute configuration tests provided as list of functions to run
+
+ :param rules: list of configuration method names
+ :type rules: list of string
+ """
+ self.rules = Utils.to_list(rules)
+ for x in self.rules:
+ f = getattr(self, x)
+ if not f:
+ self.fatal('No such configuration function %r' % x)
+ f()
+
+def conf(f):
+ """
+ Decorator: attach new configuration functions to :py:class:`waflib.Build.BuildContext` and
+ :py:class:`waflib.Configure.ConfigurationContext`. The methods bound will accept a parameter
+ named 'mandatory' to disable the configuration errors::
+
+ def configure(conf):
+ conf.find_program('abc', mandatory=False)
+
+ :param f: method to bind
+ :type f: function
+ """
+ def fun(*k, **kw):
+ mandatory = kw.pop('mandatory', True)
+ try:
+ return f(*k, **kw)
+ except Errors.ConfigurationError:
+ if mandatory:
+ raise
+
+ fun.__name__ = f.__name__
+ setattr(ConfigurationContext, f.__name__, fun)
+ setattr(Build.BuildContext, f.__name__, fun)
+ return f
+
+@conf
+def add_os_flags(self, var, dest=None, dup=False):
+ """
+ Import operating system environment values into ``conf.env`` dict::
+
+ def configure(conf):
+ conf.add_os_flags('CFLAGS')
+
+ :param var: variable to use
+ :type var: string
+ :param dest: destination variable, by default the same as var
+ :type dest: string
+ :param dup: add the same set of flags again
+ :type dup: bool
+ """
+ try:
+ flags = shlex.split(self.environ[var])
+ except KeyError:
+ return
+ if dup or ''.join(flags) not in ''.join(Utils.to_list(self.env[dest or var])):
+ self.env.append_value(dest or var, flags)
+
+@conf
+def cmd_to_list(self, cmd):
+ """
+ Detect if a command is written in pseudo shell like ``ccache g++`` and return a list.
+
+ :param cmd: command
+ :type cmd: a string or a list of string
+ """
+ if isinstance(cmd, str):
+ if os.path.isfile(cmd):
+ # do not take any risk
+ return [cmd]
+ if os.sep == '/':
+ return shlex.split(cmd)
+ else:
+ try:
+ return shlex.split(cmd, posix=False)
+ except TypeError:
+ # Python 2.5 on windows?
+ return shlex.split(cmd)
+ return cmd
+
+@conf
+def check_waf_version(self, mini='1.9.99', maxi='2.1.0', **kw):
+ """
+ Raise a Configuration error if the Waf version does not strictly match the given bounds::
+
+ conf.check_waf_version(mini='1.9.99', maxi='2.1.0')
+
+ :type mini: number, tuple or string
+ :param mini: Minimum required version
+ :type maxi: number, tuple or string
+ :param maxi: Maximum allowed version
+ """
+ self.start_msg('Checking for waf version in %s-%s' % (str(mini), str(maxi)), **kw)
+ ver = Context.HEXVERSION
+ if Utils.num2ver(mini) > ver:
+ self.fatal('waf version should be at least %r (%r found)' % (Utils.num2ver(mini), ver))
+ if Utils.num2ver(maxi) < ver:
+ self.fatal('waf version should be at most %r (%r found)' % (Utils.num2ver(maxi), ver))
+ self.end_msg('ok', **kw)
+
+@conf
+def find_file(self, filename, path_list=[]):
+ """
+ Find a file in a list of paths
+
+ :param filename: name of the file to search for
+ :param path_list: list of directories to search
+ :return: the first matching filename; else a configuration exception is raised
+ """
+ for n in Utils.to_list(filename):
+ for d in Utils.to_list(path_list):
+ p = os.path.expanduser(os.path.join(d, n))
+ if os.path.exists(p):
+ return p
+ self.fatal('Could not find %r' % filename)
+
+@conf
+def find_program(self, filename, **kw):
+ """
+ Search for a program on the operating system
+
+ When var is used, you may set os.environ[var] to help find a specific program version, for example::
+
+ $ CC='ccache gcc' waf configure
+
+ :param path_list: paths to use for searching
+ :type param_list: list of string
+ :param var: store the result to conf.env[var] where var defaults to filename.upper() if not provided; the result is stored as a list of strings
+ :type var: string
+ :param value: obtain the program from the value passed exclusively
+ :type value: list or string (list is preferred)
+ :param exts: list of extensions for the binary (do not add an extension for portability)
+ :type exts: list of string
+ :param msg: name to display in the log, by default filename is used
+ :type msg: string
+ :param interpreter: interpreter for the program
+ :type interpreter: ConfigSet variable key
+ :raises: :py:class:`waflib.Errors.ConfigurationError`
+ """
+
+ exts = kw.get('exts', Utils.is_win32 and '.exe,.com,.bat,.cmd' or ',.sh,.pl,.py')
+
+ environ = kw.get('environ', getattr(self, 'environ', os.environ))
+
+ ret = ''
+
+ filename = Utils.to_list(filename)
+ msg = kw.get('msg', ', '.join(filename))
+
+ var = kw.get('var', '')
+ if not var:
+ var = re.sub(r'[-.]', '_', filename[0].upper())
+
+ path_list = kw.get('path_list', '')
+ if path_list:
+ path_list = Utils.to_list(path_list)
+ else:
+ path_list = environ.get('PATH', '').split(os.pathsep)
+
+ if kw.get('value'):
+ # user-provided in command-line options and passed to find_program
+ ret = self.cmd_to_list(kw['value'])
+ elif environ.get(var):
+ # user-provided in the os environment
+ ret = self.cmd_to_list(environ[var])
+ elif self.env[var]:
+ # a default option in the wscript file
+ ret = self.cmd_to_list(self.env[var])
+ else:
+ if not ret:
+ ret = self.find_binary(filename, exts.split(','), path_list)
+ if not ret and Utils.winreg:
+ ret = Utils.get_registry_app_path(Utils.winreg.HKEY_CURRENT_USER, filename)
+ if not ret and Utils.winreg:
+ ret = Utils.get_registry_app_path(Utils.winreg.HKEY_LOCAL_MACHINE, filename)
+ ret = self.cmd_to_list(ret)
+
+ if ret:
+ if len(ret) == 1:
+ retmsg = ret[0]
+ else:
+ retmsg = ret
+ else:
+ retmsg = False
+
+ self.msg('Checking for program %r' % msg, retmsg, **kw)
+ if not kw.get('quiet'):
+ self.to_log('find program=%r paths=%r var=%r -> %r' % (filename, path_list, var, ret))
+
+ if not ret:
+ self.fatal(kw.get('errmsg', '') or 'Could not find the program %r' % filename)
+
+ interpreter = kw.get('interpreter')
+ if interpreter is None:
+ if not Utils.check_exe(ret[0], env=environ):
+ self.fatal('Program %r is not executable' % ret)
+ self.env[var] = ret
+ else:
+ self.env[var] = self.env[interpreter] + ret
+
+ return ret
+
+@conf
+def find_binary(self, filenames, exts, paths):
+ for f in filenames:
+ for ext in exts:
+ exe_name = f + ext
+ if os.path.isabs(exe_name):
+ if os.path.isfile(exe_name):
+ return exe_name
+ else:
+ for path in paths:
+ x = os.path.expanduser(os.path.join(path, exe_name))
+ if os.path.isfile(x):
+ return x
+ return None
+
+@conf
+def run_build(self, *k, **kw):
+ """
+ Create a temporary build context to execute a build. A reference to that build
+ context is kept on self.test_bld for debugging purposes, and you should not rely
+ on it too much (read the note on the cache below).
+ The parameters given in the arguments to this function are passed as arguments for
+ a single task generator created in the build. Only three parameters are obligatory:
+
+ :param features: features to pass to a task generator created in the build
+ :type features: list of string
+ :param compile_filename: file to create for the compilation (default: *test.c*)
+ :type compile_filename: string
+ :param code: code to write in the filename to compile
+ :type code: string
+
+ Though this function returns *0* by default, the build may set an attribute named *retval* on the
+ build context object to return a particular value. See :py:func:`waflib.Tools.c_config.test_exec_fun` for example.
+
+ This function also provides a limited cache. To use it, provide the following option::
+
+ def options(opt):
+ opt.add_option('--confcache', dest='confcache', default=0,
+ action='count', help='Use a configuration cache')
+
+ And execute the configuration with the following command-line::
+
+ $ waf configure --confcache
+
+ """
+ lst = [str(v) for (p, v) in kw.items() if p != 'env']
+ h = Utils.h_list(lst)
+ dir = self.bldnode.abspath() + os.sep + (not Utils.is_win32 and '.' or '') + 'conf_check_' + Utils.to_hex(h)
+
+ try:
+ os.makedirs(dir)
+ except OSError:
+ pass
+
+ try:
+ os.stat(dir)
+ except OSError:
+ self.fatal('cannot use the configuration test folder %r' % dir)
+
+ cachemode = getattr(Options.options, 'confcache', None)
+ if cachemode == 1:
+ try:
+ proj = ConfigSet.ConfigSet(os.path.join(dir, 'cache_run_build'))
+ except EnvironmentError:
+ pass
+ else:
+ ret = proj['cache_run_build']
+ if isinstance(ret, str) and ret.startswith('Test does not build'):
+ self.fatal(ret)
+ return ret
+
+ bdir = os.path.join(dir, 'testbuild')
+
+ if not os.path.exists(bdir):
+ os.makedirs(bdir)
+
+ cls_name = kw.get('run_build_cls') or getattr(self, 'run_build_cls', 'build')
+ self.test_bld = bld = Context.create_context(cls_name, top_dir=dir, out_dir=bdir)
+ bld.init_dirs()
+ bld.progress_bar = 0
+ bld.targets = '*'
+
+ bld.logger = self.logger
+ bld.all_envs.update(self.all_envs) # not really necessary
+ bld.env = kw['env']
+
+ bld.kw = kw
+ bld.conf = self
+ kw['build_fun'](bld)
+ ret = -1
+ try:
+ try:
+ bld.compile()
+ except Errors.WafError:
+ ret = 'Test does not build: %s' % traceback.format_exc()
+ self.fatal(ret)
+ else:
+ ret = getattr(bld, 'retval', 0)
+ finally:
+ if cachemode == 1:
+ # cache the results each time
+ proj = ConfigSet.ConfigSet()
+ proj['cache_run_build'] = ret
+ proj.store(os.path.join(dir, 'cache_run_build'))
+ else:
+ shutil.rmtree(dir)
+ return ret
+
+@conf
+def ret_msg(self, msg, args):
+ if isinstance(msg, str):
+ return msg
+ return msg(args)
+
+@conf
+def test(self, *k, **kw):
+
+ if not 'env' in kw:
+ kw['env'] = self.env.derive()
+
+ # validate_c for example
+ if kw.get('validate'):
+ kw['validate'](kw)
+
+ self.start_msg(kw['msg'], **kw)
+ ret = None
+ try:
+ ret = self.run_build(*k, **kw)
+ except self.errors.ConfigurationError:
+ self.end_msg(kw['errmsg'], 'YELLOW', **kw)
+ if Logs.verbose > 1:
+ raise
+ else:
+ self.fatal('The configuration failed')
+ else:
+ kw['success'] = ret
+
+ if kw.get('post_check'):
+ ret = kw['post_check'](kw)
+
+ if ret:
+ self.end_msg(kw['errmsg'], 'YELLOW', **kw)
+ self.fatal('The configuration failed %r' % ret)
+ else:
+ self.end_msg(self.ret_msg(kw['okmsg'], kw), **kw)
+ return ret
+
diff --git a/waflib/Context.py b/waflib/Context.py
new file mode 100644
index 0000000..876ea46
--- /dev/null
+++ b/waflib/Context.py
@@ -0,0 +1,737 @@
+#!/usr/bin/env python
+# encoding: utf-8
+# Thomas Nagy, 2010-2018 (ita)
+
+"""
+Classes and functions enabling the command system
+"""
+
+import os, re, imp, sys
+from waflib import Utils, Errors, Logs
+import waflib.Node
+
+# the following 3 constants are updated on each new release (do not touch)
+HEXVERSION=0x2000f00
+"""Constant updated on new releases"""
+
+WAFVERSION="2.0.15"
+"""Constant updated on new releases"""
+
+WAFREVISION="503db290b73ef738a495e0d116d6f8ee0b98dcc2"
+"""Git revision when the waf version is updated"""
+
+ABI = 20
+"""Version of the build data cache file format (used in :py:const:`waflib.Context.DBFILE`)"""
+
+DBFILE = '.wafpickle-%s-%d-%d' % (sys.platform, sys.hexversion, ABI)
+"""Name of the pickle file for storing the build data"""
+
+APPNAME = 'APPNAME'
+"""Default application name (used by ``waf dist``)"""
+
+VERSION = 'VERSION'
+"""Default application version (used by ``waf dist``)"""
+
+TOP = 'top'
+"""The variable name for the top-level directory in wscript files"""
+
+OUT = 'out'
+"""The variable name for the output directory in wscript files"""
+
+WSCRIPT_FILE = 'wscript'
+"""Name of the waf script files"""
+
+launch_dir = ''
+"""Directory from which waf has been called"""
+run_dir = ''
+"""Location of the wscript file to use as the entry point"""
+top_dir = ''
+"""Location of the project directory (top), if the project was configured"""
+out_dir = ''
+"""Location of the build directory (out), if the project was configured"""
+waf_dir = ''
+"""Directory containing the waf modules"""
+
+default_encoding = Utils.console_encoding()
+"""Encoding to use when reading outputs from other processes"""
+
+g_module = None
+"""
+Module representing the top-level wscript file (see :py:const:`waflib.Context.run_dir`)
+"""
+
+STDOUT = 1
+STDERR = -1
+BOTH = 0
+
+classes = []
+"""
+List of :py:class:`waflib.Context.Context` subclasses that can be used as waf commands. The classes
+are added automatically by a metaclass.
+"""
+
+def create_context(cmd_name, *k, **kw):
+ """
+ Returns a new :py:class:`waflib.Context.Context` instance corresponding to the given command.
+ Used in particular by :py:func:`waflib.Scripting.run_command`
+
+ :param cmd_name: command name
+ :type cmd_name: string
+ :param k: arguments to give to the context class initializer
+ :type k: list
+ :param k: keyword arguments to give to the context class initializer
+ :type k: dict
+ :return: Context object
+ :rtype: :py:class:`waflib.Context.Context`
+ """
+ for x in classes:
+ if x.cmd == cmd_name:
+ return x(*k, **kw)
+ ctx = Context(*k, **kw)
+ ctx.fun = cmd_name
+ return ctx
+
+class store_context(type):
+ """
+ Metaclass that registers command classes into the list :py:const:`waflib.Context.classes`
+ Context classes must provide an attribute 'cmd' representing the command name, and a function
+ attribute 'fun' representing the function name that the command uses.
+ """
+ def __init__(cls, name, bases, dct):
+ super(store_context, cls).__init__(name, bases, dct)
+ name = cls.__name__
+
+ if name in ('ctx', 'Context'):
+ return
+
+ try:
+ cls.cmd
+ except AttributeError:
+ raise Errors.WafError('Missing command for the context class %r (cmd)' % name)
+
+ if not getattr(cls, 'fun', None):
+ cls.fun = cls.cmd
+
+ classes.insert(0, cls)
+
+ctx = store_context('ctx', (object,), {})
+"""Base class for all :py:class:`waflib.Context.Context` classes"""
+
+class Context(ctx):
+ """
+ Default context for waf commands, and base class for new command contexts.
+
+ Context objects are passed to top-level functions::
+
+ def foo(ctx):
+ print(ctx.__class__.__name__) # waflib.Context.Context
+
+ Subclasses must define the class attributes 'cmd' and 'fun':
+
+ :param cmd: command to execute as in ``waf cmd``
+ :type cmd: string
+ :param fun: function name to execute when the command is called
+ :type fun: string
+
+ .. inheritance-diagram:: waflib.Context.Context waflib.Build.BuildContext waflib.Build.InstallContext waflib.Build.UninstallContext waflib.Build.StepContext waflib.Build.ListContext waflib.Configure.ConfigurationContext waflib.Scripting.Dist waflib.Scripting.DistCheck waflib.Build.CleanContext
+
+ """
+
+ errors = Errors
+ """
+ Shortcut to :py:mod:`waflib.Errors` provided for convenience
+ """
+
+ tools = {}
+ """
+ A module cache for wscript files; see :py:meth:`Context.Context.load`
+ """
+
+ def __init__(self, **kw):
+ try:
+ rd = kw['run_dir']
+ except KeyError:
+ rd = run_dir
+
+ # binds the context to the nodes in use to avoid a context singleton
+ self.node_class = type('Nod3', (waflib.Node.Node,), {})
+ self.node_class.__module__ = 'waflib.Node'
+ self.node_class.ctx = self
+
+ self.root = self.node_class('', None)
+ self.cur_script = None
+ self.path = self.root.find_dir(rd)
+
+ self.stack_path = []
+ self.exec_dict = {'ctx':self, 'conf':self, 'bld':self, 'opt':self}
+ self.logger = None
+
+ def finalize(self):
+ """
+ Called to free resources such as logger files
+ """
+ try:
+ logger = self.logger
+ except AttributeError:
+ pass
+ else:
+ Logs.free_logger(logger)
+ delattr(self, 'logger')
+
+ def load(self, tool_list, *k, **kw):
+ """
+ Loads a Waf tool as a module, and try calling the function named :py:const:`waflib.Context.Context.fun`
+ from it. A ``tooldir`` argument may be provided as a list of module paths.
+
+ :param tool_list: list of Waf tool names to load
+ :type tool_list: list of string or space-separated string
+ """
+ tools = Utils.to_list(tool_list)
+ path = Utils.to_list(kw.get('tooldir', ''))
+ with_sys_path = kw.get('with_sys_path', True)
+
+ for t in tools:
+ module = load_tool(t, path, with_sys_path=with_sys_path)
+ fun = getattr(module, kw.get('name', self.fun), None)
+ if fun:
+ fun(self)
+
+ def execute(self):
+ """
+ Here, it calls the function name in the top-level wscript file. Most subclasses
+ redefine this method to provide additional functionality.
+ """
+ self.recurse([os.path.dirname(g_module.root_path)])
+
+ def pre_recurse(self, node):
+ """
+ Method executed immediately before a folder is read by :py:meth:`waflib.Context.Context.recurse`.
+ The current script is bound as a Node object on ``self.cur_script``, and the current path
+ is bound to ``self.path``
+
+ :param node: script
+ :type node: :py:class:`waflib.Node.Node`
+ """
+ self.stack_path.append(self.cur_script)
+
+ self.cur_script = node
+ self.path = node.parent
+
+ def post_recurse(self, node):
+ """
+ Restores ``self.cur_script`` and ``self.path`` right after :py:meth:`waflib.Context.Context.recurse` terminates.
+
+ :param node: script
+ :type node: :py:class:`waflib.Node.Node`
+ """
+ self.cur_script = self.stack_path.pop()
+ if self.cur_script:
+ self.path = self.cur_script.parent
+
+ def recurse(self, dirs, name=None, mandatory=True, once=True, encoding=None):
+ """
+ Runs user-provided functions from the supplied list of directories.
+ The directories can be either absolute, or relative to the directory
+ of the wscript file
+
+ The methods :py:meth:`waflib.Context.Context.pre_recurse` and
+ :py:meth:`waflib.Context.Context.post_recurse` are called immediately before
+ and after a script has been executed.
+
+ :param dirs: List of directories to visit
+ :type dirs: list of string or space-separated string
+ :param name: Name of function to invoke from the wscript
+ :type name: string
+ :param mandatory: whether sub wscript files are required to exist
+ :type mandatory: bool
+ :param once: read the script file once for a particular context
+ :type once: bool
+ """
+ try:
+ cache = self.recurse_cache
+ except AttributeError:
+ cache = self.recurse_cache = {}
+
+ for d in Utils.to_list(dirs):
+
+ if not os.path.isabs(d):
+ # absolute paths only
+ d = os.path.join(self.path.abspath(), d)
+
+ WSCRIPT = os.path.join(d, WSCRIPT_FILE)
+ WSCRIPT_FUN = WSCRIPT + '_' + (name or self.fun)
+
+ node = self.root.find_node(WSCRIPT_FUN)
+ if node and (not once or node not in cache):
+ cache[node] = True
+ self.pre_recurse(node)
+ try:
+ function_code = node.read('r', encoding)
+ exec(compile(function_code, node.abspath(), 'exec'), self.exec_dict)
+ finally:
+ self.post_recurse(node)
+ elif not node:
+ node = self.root.find_node(WSCRIPT)
+ tup = (node, name or self.fun)
+ if node and (not once or tup not in cache):
+ cache[tup] = True
+ self.pre_recurse(node)
+ try:
+ wscript_module = load_module(node.abspath(), encoding=encoding)
+ user_function = getattr(wscript_module, (name or self.fun), None)
+ if not user_function:
+ if not mandatory:
+ continue
+ raise Errors.WafError('No function %r defined in %s' % (name or self.fun, node.abspath()))
+ user_function(self)
+ finally:
+ self.post_recurse(node)
+ elif not node:
+ if not mandatory:
+ continue
+ try:
+ os.listdir(d)
+ except OSError:
+ raise Errors.WafError('Cannot read the folder %r' % d)
+ raise Errors.WafError('No wscript file in directory %s' % d)
+
+ def log_command(self, cmd, kw):
+ if Logs.verbose:
+ fmt = os.environ.get('WAF_CMD_FORMAT')
+ if fmt == 'string':
+ if not isinstance(cmd, str):
+ cmd = Utils.shell_escape(cmd)
+ Logs.debug('runner: %r', cmd)
+ Logs.debug('runner_env: kw=%s', kw)
+
+ def exec_command(self, cmd, **kw):
+ """
+ Runs an external process and returns the exit status::
+
+ def run(tsk):
+ ret = tsk.generator.bld.exec_command('touch foo.txt')
+ return ret
+
+ If the context has the attribute 'log', then captures and logs the process stderr/stdout.
+ Unlike :py:meth:`waflib.Context.Context.cmd_and_log`, this method does not return the
+ stdout/stderr values captured.
+
+ :param cmd: command argument for subprocess.Popen
+ :type cmd: string or list
+ :param kw: keyword arguments for subprocess.Popen. The parameters input/timeout will be passed to wait/communicate.
+ :type kw: dict
+ :returns: process exit status
+ :rtype: integer
+ :raises: :py:class:`waflib.Errors.WafError` if an invalid executable is specified for a non-shell process
+ :raises: :py:class:`waflib.Errors.WafError` in case of execution failure
+ """
+ subprocess = Utils.subprocess
+ kw['shell'] = isinstance(cmd, str)
+ self.log_command(cmd, kw)
+
+ if self.logger:
+ self.logger.info(cmd)
+
+ if 'stdout' not in kw:
+ kw['stdout'] = subprocess.PIPE
+ if 'stderr' not in kw:
+ kw['stderr'] = subprocess.PIPE
+
+ if Logs.verbose and not kw['shell'] and not Utils.check_exe(cmd[0]):
+ raise Errors.WafError('Program %s not found!' % cmd[0])
+
+ cargs = {}
+ if 'timeout' in kw:
+ if sys.hexversion >= 0x3030000:
+ cargs['timeout'] = kw['timeout']
+ if not 'start_new_session' in kw:
+ kw['start_new_session'] = True
+ del kw['timeout']
+ if 'input' in kw:
+ if kw['input']:
+ cargs['input'] = kw['input']
+ kw['stdin'] = subprocess.PIPE
+ del kw['input']
+
+ if 'cwd' in kw:
+ if not isinstance(kw['cwd'], str):
+ kw['cwd'] = kw['cwd'].abspath()
+
+ encoding = kw.pop('decode_as', default_encoding)
+
+ try:
+ ret, out, err = Utils.run_process(cmd, kw, cargs)
+ except Exception as e:
+ raise Errors.WafError('Execution failure: %s' % str(e), ex=e)
+
+ if out:
+ if not isinstance(out, str):
+ out = out.decode(encoding, errors='replace')
+ if self.logger:
+ self.logger.debug('out: %s', out)
+ else:
+ Logs.info(out, extra={'stream':sys.stdout, 'c1': ''})
+ if err:
+ if not isinstance(err, str):
+ err = err.decode(encoding, errors='replace')
+ if self.logger:
+ self.logger.error('err: %s' % err)
+ else:
+ Logs.info(err, extra={'stream':sys.stderr, 'c1': ''})
+
+ return ret
+
+ def cmd_and_log(self, cmd, **kw):
+ """
+ Executes a process and returns stdout/stderr if the execution is successful.
+ An exception is thrown when the exit status is non-0. In that case, both stderr and stdout
+ will be bound to the WafError object (configuration tests)::
+
+ def configure(conf):
+ out = conf.cmd_and_log(['echo', 'hello'], output=waflib.Context.STDOUT, quiet=waflib.Context.BOTH)
+ (out, err) = conf.cmd_and_log(['echo', 'hello'], output=waflib.Context.BOTH)
+ (out, err) = conf.cmd_and_log(cmd, input='\\n'.encode(), output=waflib.Context.STDOUT)
+ try:
+ conf.cmd_and_log(['which', 'someapp'], output=waflib.Context.BOTH)
+ except Errors.WafError as e:
+ print(e.stdout, e.stderr)
+
+ :param cmd: args for subprocess.Popen
+ :type cmd: list or string
+ :param kw: keyword arguments for subprocess.Popen. The parameters input/timeout will be passed to wait/communicate.
+ :type kw: dict
+ :returns: a tuple containing the contents of stdout and stderr
+ :rtype: string
+ :raises: :py:class:`waflib.Errors.WafError` if an invalid executable is specified for a non-shell process
+ :raises: :py:class:`waflib.Errors.WafError` in case of execution failure; stdout/stderr/returncode are bound to the exception object
+ """
+ subprocess = Utils.subprocess
+ kw['shell'] = isinstance(cmd, str)
+ self.log_command(cmd, kw)
+
+ quiet = kw.pop('quiet', None)
+ to_ret = kw.pop('output', STDOUT)
+
+ if Logs.verbose and not kw['shell'] and not Utils.check_exe(cmd[0]):
+ raise Errors.WafError('Program %r not found!' % cmd[0])
+
+ kw['stdout'] = kw['stderr'] = subprocess.PIPE
+ if quiet is None:
+ self.to_log(cmd)
+
+ cargs = {}
+ if 'timeout' in kw:
+ if sys.hexversion >= 0x3030000:
+ cargs['timeout'] = kw['timeout']
+ if not 'start_new_session' in kw:
+ kw['start_new_session'] = True
+ del kw['timeout']
+ if 'input' in kw:
+ if kw['input']:
+ cargs['input'] = kw['input']
+ kw['stdin'] = subprocess.PIPE
+ del kw['input']
+
+ if 'cwd' in kw:
+ if not isinstance(kw['cwd'], str):
+ kw['cwd'] = kw['cwd'].abspath()
+
+ encoding = kw.pop('decode_as', default_encoding)
+
+ try:
+ ret, out, err = Utils.run_process(cmd, kw, cargs)
+ except Exception as e:
+ raise Errors.WafError('Execution failure: %s' % str(e), ex=e)
+
+ if not isinstance(out, str):
+ out = out.decode(encoding, errors='replace')
+ if not isinstance(err, str):
+ err = err.decode(encoding, errors='replace')
+
+ if out and quiet != STDOUT and quiet != BOTH:
+ self.to_log('out: %s' % out)
+ if err and quiet != STDERR and quiet != BOTH:
+ self.to_log('err: %s' % err)
+
+ if ret:
+ e = Errors.WafError('Command %r returned %r' % (cmd, ret))
+ e.returncode = ret
+ e.stderr = err
+ e.stdout = out
+ raise e
+
+ if to_ret == BOTH:
+ return (out, err)
+ elif to_ret == STDERR:
+ return err
+ return out
+
+ def fatal(self, msg, ex=None):
+ """
+ Prints an error message in red and stops command execution; this is
+ usually used in the configuration section::
+
+ def configure(conf):
+ conf.fatal('a requirement is missing')
+
+ :param msg: message to display
+ :type msg: string
+ :param ex: optional exception object
+ :type ex: exception
+ :raises: :py:class:`waflib.Errors.ConfigurationError`
+ """
+ if self.logger:
+ self.logger.info('from %s: %s' % (self.path.abspath(), msg))
+ try:
+ logfile = self.logger.handlers[0].baseFilename
+ except AttributeError:
+ pass
+ else:
+ if os.environ.get('WAF_PRINT_FAILURE_LOG'):
+ # see #1930
+ msg = 'Log from (%s):\n%s\n' % (logfile, Utils.readf(logfile))
+ else:
+ msg = '%s\n(complete log in %s)' % (msg, logfile)
+ raise self.errors.ConfigurationError(msg, ex=ex)
+
+ def to_log(self, msg):
+ """
+ Logs information to the logger (if present), or to stderr.
+ Empty messages are not printed::
+
+ def build(bld):
+ bld.to_log('starting the build')
+
+ Provide a logger on the context class or override this method if necessary.
+
+ :param msg: message
+ :type msg: string
+ """
+ if not msg:
+ return
+ if self.logger:
+ self.logger.info(msg)
+ else:
+ sys.stderr.write(str(msg))
+ sys.stderr.flush()
+
+
+ def msg(self, *k, **kw):
+ """
+ Prints a configuration message of the form ``msg: result``.
+ The second part of the message will be in colors. The output
+ can be disabled easly by setting ``in_msg`` to a positive value::
+
+ def configure(conf):
+ self.in_msg = 1
+ conf.msg('Checking for library foo', 'ok')
+ # no output
+
+ :param msg: message to display to the user
+ :type msg: string
+ :param result: result to display
+ :type result: string or boolean
+ :param color: color to use, see :py:const:`waflib.Logs.colors_lst`
+ :type color: string
+ """
+ try:
+ msg = kw['msg']
+ except KeyError:
+ msg = k[0]
+
+ self.start_msg(msg, **kw)
+
+ try:
+ result = kw['result']
+ except KeyError:
+ result = k[1]
+
+ color = kw.get('color')
+ if not isinstance(color, str):
+ color = result and 'GREEN' or 'YELLOW'
+
+ self.end_msg(result, color, **kw)
+
+ def start_msg(self, *k, **kw):
+ """
+ Prints the beginning of a 'Checking for xxx' message. See :py:meth:`waflib.Context.Context.msg`
+ """
+ if kw.get('quiet'):
+ return
+
+ msg = kw.get('msg') or k[0]
+ try:
+ if self.in_msg:
+ self.in_msg += 1
+ return
+ except AttributeError:
+ self.in_msg = 0
+ self.in_msg += 1
+
+ try:
+ self.line_just = max(self.line_just, len(msg))
+ except AttributeError:
+ self.line_just = max(40, len(msg))
+ for x in (self.line_just * '-', msg):
+ self.to_log(x)
+ Logs.pprint('NORMAL', "%s :" % msg.ljust(self.line_just), sep='')
+
+ def end_msg(self, *k, **kw):
+ """Prints the end of a 'Checking for' message. See :py:meth:`waflib.Context.Context.msg`"""
+ if kw.get('quiet'):
+ return
+ self.in_msg -= 1
+ if self.in_msg:
+ return
+
+ result = kw.get('result') or k[0]
+
+ defcolor = 'GREEN'
+ if result is True:
+ msg = 'ok'
+ elif not result:
+ msg = 'not found'
+ defcolor = 'YELLOW'
+ else:
+ msg = str(result)
+
+ self.to_log(msg)
+ try:
+ color = kw['color']
+ except KeyError:
+ if len(k) > 1 and k[1] in Logs.colors_lst:
+ # compatibility waf 1.7
+ color = k[1]
+ else:
+ color = defcolor
+ Logs.pprint(color, msg)
+
+ def load_special_tools(self, var, ban=[]):
+ """
+ Loads third-party extensions modules for certain programming languages
+ by trying to list certain files in the extras/ directory. This method
+ is typically called once for a programming language group, see for
+ example :py:mod:`waflib.Tools.compiler_c`
+
+ :param var: glob expression, for example 'cxx\\_\\*.py'
+ :type var: string
+ :param ban: list of exact file names to exclude
+ :type ban: list of string
+ """
+ if os.path.isdir(waf_dir):
+ lst = self.root.find_node(waf_dir).find_node('waflib/extras').ant_glob(var)
+ for x in lst:
+ if not x.name in ban:
+ load_tool(x.name.replace('.py', ''))
+ else:
+ from zipfile import PyZipFile
+ waflibs = PyZipFile(waf_dir)
+ lst = waflibs.namelist()
+ for x in lst:
+ if not re.match('waflib/extras/%s' % var.replace('*', '.*'), var):
+ continue
+ f = os.path.basename(x)
+ doban = False
+ for b in ban:
+ r = b.replace('*', '.*')
+ if re.match(r, f):
+ doban = True
+ if not doban:
+ f = f.replace('.py', '')
+ load_tool(f)
+
+cache_modules = {}
+"""
+Dictionary holding already loaded modules (wscript), indexed by their absolute path.
+The modules are added automatically by :py:func:`waflib.Context.load_module`
+"""
+
+def load_module(path, encoding=None):
+ """
+ Loads a wscript file as a python module. This method caches results in :py:attr:`waflib.Context.cache_modules`
+
+ :param path: file path
+ :type path: string
+ :return: Loaded Python module
+ :rtype: module
+ """
+ try:
+ return cache_modules[path]
+ except KeyError:
+ pass
+
+ module = imp.new_module(WSCRIPT_FILE)
+ try:
+ code = Utils.readf(path, m='r', encoding=encoding)
+ except EnvironmentError:
+ raise Errors.WafError('Could not read the file %r' % path)
+
+ module_dir = os.path.dirname(path)
+ sys.path.insert(0, module_dir)
+ try:
+ exec(compile(code, path, 'exec'), module.__dict__)
+ finally:
+ sys.path.remove(module_dir)
+
+ cache_modules[path] = module
+ return module
+
+def load_tool(tool, tooldir=None, ctx=None, with_sys_path=True):
+ """
+ Imports a Waf tool as a python module, and stores it in the dict :py:const:`waflib.Context.Context.tools`
+
+ :type tool: string
+ :param tool: Name of the tool
+ :type tooldir: list
+ :param tooldir: List of directories to search for the tool module
+ :type with_sys_path: boolean
+ :param with_sys_path: whether or not to search the regular sys.path, besides waf_dir and potentially given tooldirs
+ """
+ if tool == 'java':
+ tool = 'javaw' # jython
+ else:
+ tool = tool.replace('++', 'xx')
+
+ if not with_sys_path:
+ back_path = sys.path
+ sys.path = []
+ try:
+ if tooldir:
+ assert isinstance(tooldir, list)
+ sys.path = tooldir + sys.path
+ try:
+ __import__(tool)
+ except ImportError as e:
+ e.waf_sys_path = list(sys.path)
+ raise
+ finally:
+ for d in tooldir:
+ sys.path.remove(d)
+ ret = sys.modules[tool]
+ Context.tools[tool] = ret
+ return ret
+ else:
+ if not with_sys_path:
+ sys.path.insert(0, waf_dir)
+ try:
+ for x in ('waflib.Tools.%s', 'waflib.extras.%s', 'waflib.%s', '%s'):
+ try:
+ __import__(x % tool)
+ break
+ except ImportError:
+ x = None
+ else: # raise an exception
+ __import__(tool)
+ except ImportError as e:
+ e.waf_sys_path = list(sys.path)
+ raise
+ finally:
+ if not with_sys_path:
+ sys.path.remove(waf_dir)
+ ret = sys.modules[x % tool]
+ Context.tools[tool] = ret
+ return ret
+ finally:
+ if not with_sys_path:
+ sys.path += back_path
+
diff --git a/waflib/Errors.py b/waflib/Errors.py
new file mode 100644
index 0000000..bf75c1b
--- /dev/null
+++ b/waflib/Errors.py
@@ -0,0 +1,68 @@
+#!/usr/bin/env python
+# encoding: utf-8
+# Thomas Nagy, 2010-2018 (ita)
+
+"""
+Exceptions used in the Waf code
+"""
+
+import traceback, sys
+
+class WafError(Exception):
+ """Base class for all Waf errors"""
+ def __init__(self, msg='', ex=None):
+ """
+ :param msg: error message
+ :type msg: string
+ :param ex: exception causing this error (optional)
+ :type ex: exception
+ """
+ Exception.__init__(self)
+ self.msg = msg
+ assert not isinstance(msg, Exception)
+
+ self.stack = []
+ if ex:
+ if not msg:
+ self.msg = str(ex)
+ if isinstance(ex, WafError):
+ self.stack = ex.stack
+ else:
+ self.stack = traceback.extract_tb(sys.exc_info()[2])
+ self.stack += traceback.extract_stack()[:-1]
+ self.verbose_msg = ''.join(traceback.format_list(self.stack))
+
+ def __str__(self):
+ return str(self.msg)
+
+class BuildError(WafError):
+ """Error raised during the build and install phases"""
+ def __init__(self, error_tasks=[]):
+ """
+ :param error_tasks: tasks that could not complete normally
+ :type error_tasks: list of task objects
+ """
+ self.tasks = error_tasks
+ WafError.__init__(self, self.format_error())
+
+ def format_error(self):
+ """Formats the error messages from the tasks that failed"""
+ lst = ['Build failed']
+ for tsk in self.tasks:
+ txt = tsk.format_error()
+ if txt:
+ lst.append(txt)
+ return '\n'.join(lst)
+
+class ConfigurationError(WafError):
+ """Configuration exception raised in particular by :py:meth:`waflib.Context.Context.fatal`"""
+ pass
+
+class TaskRescan(WafError):
+ """Task-specific exception type signalling required signature recalculations"""
+ pass
+
+class TaskNotReady(WafError):
+ """Task-specific exception type signalling that task signatures cannot be computed"""
+ pass
+
diff --git a/waflib/Logs.py b/waflib/Logs.py
new file mode 100644
index 0000000..11dc34f
--- /dev/null
+++ b/waflib/Logs.py
@@ -0,0 +1,379 @@
+#!/usr/bin/env python
+# encoding: utf-8
+# Thomas Nagy, 2005-2018 (ita)
+
+"""
+logging, colors, terminal width and pretty-print
+"""
+
+import os, re, traceback, sys
+from waflib import Utils, ansiterm
+
+if not os.environ.get('NOSYNC', False):
+ # synchronized output is nearly mandatory to prevent garbled output
+ if sys.stdout.isatty() and id(sys.stdout) == id(sys.__stdout__):
+ sys.stdout = ansiterm.AnsiTerm(sys.stdout)
+ if sys.stderr.isatty() and id(sys.stderr) == id(sys.__stderr__):
+ sys.stderr = ansiterm.AnsiTerm(sys.stderr)
+
+# import the logging module after since it holds a reference on sys.stderr
+# in case someone uses the root logger
+import logging
+
+LOG_FORMAT = os.environ.get('WAF_LOG_FORMAT', '%(asctime)s %(c1)s%(zone)s%(c2)s %(message)s')
+HOUR_FORMAT = os.environ.get('WAF_HOUR_FORMAT', '%H:%M:%S')
+
+zones = []
+"""
+See :py:class:`waflib.Logs.log_filter`
+"""
+
+verbose = 0
+"""
+Global verbosity level, see :py:func:`waflib.Logs.debug` and :py:func:`waflib.Logs.error`
+"""
+
+colors_lst = {
+'USE' : True,
+'BOLD' :'\x1b[01;1m',
+'RED' :'\x1b[01;31m',
+'GREEN' :'\x1b[32m',
+'YELLOW':'\x1b[33m',
+'PINK' :'\x1b[35m',
+'BLUE' :'\x1b[01;34m',
+'CYAN' :'\x1b[36m',
+'GREY' :'\x1b[37m',
+'NORMAL':'\x1b[0m',
+'cursor_on' :'\x1b[?25h',
+'cursor_off' :'\x1b[?25l',
+}
+
+indicator = '\r\x1b[K%s%s%s'
+
+try:
+ unicode
+except NameError:
+ unicode = None
+
+def enable_colors(use):
+ """
+ If *1* is given, then the system will perform a few verifications
+ before enabling colors, such as checking whether the interpreter
+ is running in a terminal. A value of zero will disable colors,
+ and a value above *1* will force colors.
+
+ :param use: whether to enable colors or not
+ :type use: integer
+ """
+ if use == 1:
+ if not (sys.stderr.isatty() or sys.stdout.isatty()):
+ use = 0
+ if Utils.is_win32 and os.name != 'java':
+ term = os.environ.get('TERM', '') # has ansiterm
+ else:
+ term = os.environ.get('TERM', 'dumb')
+
+ if term in ('dumb', 'emacs'):
+ use = 0
+
+ if use >= 1:
+ os.environ['TERM'] = 'vt100'
+
+ colors_lst['USE'] = use
+
+# If console packages are available, replace the dummy function with a real
+# implementation
+try:
+ get_term_cols = ansiterm.get_term_cols
+except AttributeError:
+ def get_term_cols():
+ return 80
+
+get_term_cols.__doc__ = """
+ Returns the console width in characters.
+
+ :return: the number of characters per line
+ :rtype: int
+ """
+
+def get_color(cl):
+ """
+ Returns the ansi sequence corresponding to the given color name.
+ An empty string is returned when coloring is globally disabled.
+
+ :param cl: color name in capital letters
+ :type cl: string
+ """
+ if colors_lst['USE']:
+ return colors_lst.get(cl, '')
+ return ''
+
+class color_dict(object):
+ """attribute-based color access, eg: colors.PINK"""
+ def __getattr__(self, a):
+ return get_color(a)
+ def __call__(self, a):
+ return get_color(a)
+
+colors = color_dict()
+
+re_log = re.compile(r'(\w+): (.*)', re.M)
+class log_filter(logging.Filter):
+ """
+ Waf logs are of the form 'name: message', and can be filtered by 'waf --zones=name'.
+ For example, the following::
+
+ from waflib import Logs
+ Logs.debug('test: here is a message')
+
+ Will be displayed only when executing::
+
+ $ waf --zones=test
+ """
+ def __init__(self, name=''):
+ logging.Filter.__init__(self, name)
+
+ def filter(self, rec):
+ """
+ Filters log records by zone and by logging level
+
+ :param rec: log entry
+ """
+ rec.zone = rec.module
+ if rec.levelno >= logging.INFO:
+ return True
+
+ m = re_log.match(rec.msg)
+ if m:
+ rec.zone = m.group(1)
+ rec.msg = m.group(2)
+
+ if zones:
+ return getattr(rec, 'zone', '') in zones or '*' in zones
+ elif not verbose > 2:
+ return False
+ return True
+
+class log_handler(logging.StreamHandler):
+ """Dispatches messages to stderr/stdout depending on the severity level"""
+ def emit(self, record):
+ """
+ Delegates the functionality to :py:meth:`waflib.Log.log_handler.emit_override`
+ """
+ # default implementation
+ try:
+ try:
+ self.stream = record.stream
+ except AttributeError:
+ if record.levelno >= logging.WARNING:
+ record.stream = self.stream = sys.stderr
+ else:
+ record.stream = self.stream = sys.stdout
+ self.emit_override(record)
+ self.flush()
+ except (KeyboardInterrupt, SystemExit):
+ raise
+ except: # from the python library -_-
+ self.handleError(record)
+
+ def emit_override(self, record, **kw):
+ """
+ Writes the log record to the desired stream (stderr/stdout)
+ """
+ self.terminator = getattr(record, 'terminator', '\n')
+ stream = self.stream
+ if unicode:
+ # python2
+ msg = self.formatter.format(record)
+ fs = '%s' + self.terminator
+ try:
+ if (isinstance(msg, unicode) and getattr(stream, 'encoding', None)):
+ fs = fs.decode(stream.encoding)
+ try:
+ stream.write(fs % msg)
+ except UnicodeEncodeError:
+ stream.write((fs % msg).encode(stream.encoding))
+ else:
+ stream.write(fs % msg)
+ except UnicodeError:
+ stream.write((fs % msg).encode('utf-8'))
+ else:
+ logging.StreamHandler.emit(self, record)
+
+class formatter(logging.Formatter):
+ """Simple log formatter which handles colors"""
+ def __init__(self):
+ logging.Formatter.__init__(self, LOG_FORMAT, HOUR_FORMAT)
+
+ def format(self, rec):
+ """
+ Formats records and adds colors as needed. The records do not get
+ a leading hour format if the logging level is above *INFO*.
+ """
+ try:
+ msg = rec.msg.decode('utf-8')
+ except Exception:
+ msg = rec.msg
+
+ use = colors_lst['USE']
+ if (use == 1 and rec.stream.isatty()) or use == 2:
+
+ c1 = getattr(rec, 'c1', None)
+ if c1 is None:
+ c1 = ''
+ if rec.levelno >= logging.ERROR:
+ c1 = colors.RED
+ elif rec.levelno >= logging.WARNING:
+ c1 = colors.YELLOW
+ elif rec.levelno >= logging.INFO:
+ c1 = colors.GREEN
+ c2 = getattr(rec, 'c2', colors.NORMAL)
+ msg = '%s%s%s' % (c1, msg, c2)
+ else:
+ # remove single \r that make long lines in text files
+ # and other terminal commands
+ msg = re.sub(r'\r(?!\n)|\x1B\[(K|.*?(m|h|l))', '', msg)
+
+ if rec.levelno >= logging.INFO:
+ # the goal of this is to format without the leading "Logs, hour" prefix
+ if rec.args:
+ return msg % rec.args
+ return msg
+
+ rec.msg = msg
+ rec.c1 = colors.PINK
+ rec.c2 = colors.NORMAL
+ return logging.Formatter.format(self, rec)
+
+log = None
+"""global logger for Logs.debug, Logs.error, etc"""
+
+def debug(*k, **kw):
+ """
+ Wraps logging.debug and discards messages if the verbosity level :py:attr:`waflib.Logs.verbose` ≤ 0
+ """
+ if verbose:
+ k = list(k)
+ k[0] = k[0].replace('\n', ' ')
+ log.debug(*k, **kw)
+
+def error(*k, **kw):
+ """
+ Wrap logging.errors, adds the stack trace when the verbosity level :py:attr:`waflib.Logs.verbose` ≥ 2
+ """
+ log.error(*k, **kw)
+ if verbose > 2:
+ st = traceback.extract_stack()
+ if st:
+ st = st[:-1]
+ buf = []
+ for filename, lineno, name, line in st:
+ buf.append(' File %r, line %d, in %s' % (filename, lineno, name))
+ if line:
+ buf.append(' %s' % line.strip())
+ if buf:
+ log.error('\n'.join(buf))
+
+def warn(*k, **kw):
+ """
+ Wraps logging.warning
+ """
+ log.warning(*k, **kw)
+
+def info(*k, **kw):
+ """
+ Wraps logging.info
+ """
+ log.info(*k, **kw)
+
+def init_log():
+ """
+ Initializes the logger :py:attr:`waflib.Logs.log`
+ """
+ global log
+ log = logging.getLogger('waflib')
+ log.handlers = []
+ log.filters = []
+ hdlr = log_handler()
+ hdlr.setFormatter(formatter())
+ log.addHandler(hdlr)
+ log.addFilter(log_filter())
+ log.setLevel(logging.DEBUG)
+
+def make_logger(path, name):
+ """
+ Creates a simple logger, which is often used to redirect the context command output::
+
+ from waflib import Logs
+ bld.logger = Logs.make_logger('test.log', 'build')
+ bld.check(header_name='sadlib.h', features='cxx cprogram', mandatory=False)
+
+ # have the file closed immediately
+ Logs.free_logger(bld.logger)
+
+ # stop logging
+ bld.logger = None
+
+ The method finalize() of the command will try to free the logger, if any
+
+ :param path: file name to write the log output to
+ :type path: string
+ :param name: logger name (loggers are reused)
+ :type name: string
+ """
+ logger = logging.getLogger(name)
+ if sys.hexversion > 0x3000000:
+ encoding = sys.stdout.encoding
+ else:
+ encoding = None
+ hdlr = logging.FileHandler(path, 'w', encoding=encoding)
+ formatter = logging.Formatter('%(message)s')
+ hdlr.setFormatter(formatter)
+ logger.addHandler(hdlr)
+ logger.setLevel(logging.DEBUG)
+ return logger
+
+def make_mem_logger(name, to_log, size=8192):
+ """
+ Creates a memory logger to avoid writing concurrently to the main logger
+ """
+ from logging.handlers import MemoryHandler
+ logger = logging.getLogger(name)
+ hdlr = MemoryHandler(size, target=to_log)
+ formatter = logging.Formatter('%(message)s')
+ hdlr.setFormatter(formatter)
+ logger.addHandler(hdlr)
+ logger.memhandler = hdlr
+ logger.setLevel(logging.DEBUG)
+ return logger
+
+def free_logger(logger):
+ """
+ Frees the resources held by the loggers created through make_logger or make_mem_logger.
+ This is used for file cleanup and for handler removal (logger objects are re-used).
+ """
+ try:
+ for x in logger.handlers:
+ x.close()
+ logger.removeHandler(x)
+ except Exception:
+ pass
+
+def pprint(col, msg, label='', sep='\n'):
+ """
+ Prints messages in color immediately on stderr::
+
+ from waflib import Logs
+ Logs.pprint('RED', 'Something bad just happened')
+
+ :param col: color name to use in :py:const:`Logs.colors_lst`
+ :type col: string
+ :param msg: message to display
+ :type msg: string or a value that can be printed by %s
+ :param label: a message to add after the colored output
+ :type label: string
+ :param sep: a string to append at the end (line separator)
+ :type sep: string
+ """
+ info('%s%s%s %s', colors(col), msg, colors.NORMAL, label, extra={'terminator':sep})
+
diff --git a/waflib/Node.py b/waflib/Node.py
new file mode 100644
index 0000000..4ac1ea8
--- /dev/null
+++ b/waflib/Node.py
@@ -0,0 +1,970 @@
+#!/usr/bin/env python
+# encoding: utf-8
+# Thomas Nagy, 2005-2018 (ita)
+
+"""
+Node: filesystem structure
+
+#. Each file/folder is represented by exactly one node.
+
+#. Some potential class properties are stored on :py:class:`waflib.Build.BuildContext` : nodes to depend on, etc.
+ Unused class members can increase the `.wafpickle` file size sensibly.
+
+#. Node objects should never be created directly, use
+ the methods :py:func:`Node.make_node` or :py:func:`Node.find_node` for the low-level operations
+
+#. The methods :py:func:`Node.find_resource`, :py:func:`Node.find_dir` :py:func:`Node.find_or_declare` must be
+ used when a build context is present
+
+#. Each instance of :py:class:`waflib.Context.Context` has a unique :py:class:`Node` subclass required for serialization.
+ (:py:class:`waflib.Node.Nod3`, see the :py:class:`waflib.Context.Context` initializer). A reference to the context
+ owning a node is held as *self.ctx*
+"""
+
+import os, re, sys, shutil
+from waflib import Utils, Errors
+
+exclude_regs = '''
+**/*~
+**/#*#
+**/.#*
+**/%*%
+**/._*
+**/*.swp
+**/CVS
+**/CVS/**
+**/.cvsignore
+**/SCCS
+**/SCCS/**
+**/vssver.scc
+**/.svn
+**/.svn/**
+**/BitKeeper
+**/.git
+**/.git/**
+**/.gitignore
+**/.bzr
+**/.bzrignore
+**/.bzr/**
+**/.hg
+**/.hg/**
+**/_MTN
+**/_MTN/**
+**/.arch-ids
+**/{arch}
+**/_darcs
+**/_darcs/**
+**/.intlcache
+**/.DS_Store'''
+"""
+Ant patterns for files and folders to exclude while doing the
+recursive traversal in :py:meth:`waflib.Node.Node.ant_glob`
+"""
+
+def ant_matcher(s, ignorecase):
+ reflags = re.I if ignorecase else 0
+ ret = []
+ for x in Utils.to_list(s):
+ x = x.replace('\\', '/').replace('//', '/')
+ if x.endswith('/'):
+ x += '**'
+ accu = []
+ for k in x.split('/'):
+ if k == '**':
+ accu.append(k)
+ else:
+ k = k.replace('.', '[.]').replace('*','.*').replace('?', '.').replace('+', '\\+')
+ k = '^%s$' % k
+ try:
+ exp = re.compile(k, flags=reflags)
+ except Exception as e:
+ raise Errors.WafError('Invalid pattern: %s' % k, e)
+ else:
+ accu.append(exp)
+ ret.append(accu)
+ return ret
+
+def ant_sub_filter(name, nn):
+ ret = []
+ for lst in nn:
+ if not lst:
+ pass
+ elif lst[0] == '**':
+ ret.append(lst)
+ if len(lst) > 1:
+ if lst[1].match(name):
+ ret.append(lst[2:])
+ else:
+ ret.append([])
+ elif lst[0].match(name):
+ ret.append(lst[1:])
+ return ret
+
+def ant_sub_matcher(name, pats):
+ nacc = ant_sub_filter(name, pats[0])
+ nrej = ant_sub_filter(name, pats[1])
+ if [] in nrej:
+ nacc = []
+ return [nacc, nrej]
+
+class Node(object):
+ """
+ This class is organized in two parts:
+
+ * The basic methods meant for filesystem access (compute paths, create folders, etc)
+ * The methods bound to a :py:class:`waflib.Build.BuildContext` (require ``bld.srcnode`` and ``bld.bldnode``)
+ """
+
+ dict_class = dict
+ """
+ Subclasses can provide a dict class to enable case insensitivity for example.
+ """
+
+ __slots__ = ('name', 'parent', 'children', 'cache_abspath', 'cache_isdir')
+ def __init__(self, name, parent):
+ """
+ .. note:: Use :py:func:`Node.make_node` or :py:func:`Node.find_node` instead of calling this constructor
+ """
+ self.name = name
+ self.parent = parent
+ if parent:
+ if name in parent.children:
+ raise Errors.WafError('node %s exists in the parent files %r already' % (name, parent))
+ parent.children[name] = self
+
+ def __setstate__(self, data):
+ "Deserializes node information, used for persistence"
+ self.name = data[0]
+ self.parent = data[1]
+ if data[2] is not None:
+ # Issue 1480
+ self.children = self.dict_class(data[2])
+
+ def __getstate__(self):
+ "Serializes node information, used for persistence"
+ return (self.name, self.parent, getattr(self, 'children', None))
+
+ def __str__(self):
+ """
+ String representation (abspath), for debugging purposes
+
+ :rtype: string
+ """
+ return self.abspath()
+
+ def __repr__(self):
+ """
+ String representation (abspath), for debugging purposes
+
+ :rtype: string
+ """
+ return self.abspath()
+
+ def __copy__(self):
+ """
+ Provided to prevent nodes from being copied
+
+ :raises: :py:class:`waflib.Errors.WafError`
+ """
+ raise Errors.WafError('nodes are not supposed to be copied')
+
+ def read(self, flags='r', encoding='latin-1'):
+ """
+ Reads and returns the contents of the file represented by this node, see :py:func:`waflib.Utils.readf`::
+
+ def build(bld):
+ bld.path.find_node('wscript').read()
+
+ :param flags: Open mode
+ :type flags: string
+ :param encoding: encoding value for Python3
+ :type encoding: string
+ :rtype: string or bytes
+ :return: File contents
+ """
+ return Utils.readf(self.abspath(), flags, encoding)
+
+ def write(self, data, flags='w', encoding='latin-1'):
+ """
+ Writes data to the file represented by this node, see :py:func:`waflib.Utils.writef`::
+
+ def build(bld):
+ bld.path.make_node('foo.txt').write('Hello, world!')
+
+ :param data: data to write
+ :type data: string
+ :param flags: Write mode
+ :type flags: string
+ :param encoding: encoding value for Python3
+ :type encoding: string
+ """
+ Utils.writef(self.abspath(), data, flags, encoding)
+
+ def read_json(self, convert=True, encoding='utf-8'):
+ """
+ Reads and parses the contents of this node as JSON (Python ≥ 2.6)::
+
+ def build(bld):
+ bld.path.find_node('abc.json').read_json()
+
+ Note that this by default automatically decodes unicode strings on Python2, unlike what the Python JSON module does.
+
+ :type convert: boolean
+ :param convert: Prevents decoding of unicode strings on Python2
+ :type encoding: string
+ :param encoding: The encoding of the file to read. This default to UTF8 as per the JSON standard
+ :rtype: object
+ :return: Parsed file contents
+ """
+ import json # Python 2.6 and up
+ object_pairs_hook = None
+ if convert and sys.hexversion < 0x3000000:
+ try:
+ _type = unicode
+ except NameError:
+ _type = str
+
+ def convert(value):
+ if isinstance(value, list):
+ return [convert(element) for element in value]
+ elif isinstance(value, _type):
+ return str(value)
+ else:
+ return value
+
+ def object_pairs(pairs):
+ return dict((str(pair[0]), convert(pair[1])) for pair in pairs)
+
+ object_pairs_hook = object_pairs
+
+ return json.loads(self.read(encoding=encoding), object_pairs_hook=object_pairs_hook)
+
+ def write_json(self, data, pretty=True):
+ """
+ Writes a python object as JSON to disk (Python ≥ 2.6) as UTF-8 data (JSON standard)::
+
+ def build(bld):
+ bld.path.find_node('xyz.json').write_json(199)
+
+ :type data: object
+ :param data: The data to write to disk
+ :type pretty: boolean
+ :param pretty: Determines if the JSON will be nicely space separated
+ """
+ import json # Python 2.6 and up
+ indent = 2
+ separators = (',', ': ')
+ sort_keys = pretty
+ newline = os.linesep
+ if not pretty:
+ indent = None
+ separators = (',', ':')
+ newline = ''
+ output = json.dumps(data, indent=indent, separators=separators, sort_keys=sort_keys) + newline
+ self.write(output, encoding='utf-8')
+
+ def exists(self):
+ """
+ Returns whether the Node is present on the filesystem
+
+ :rtype: bool
+ """
+ return os.path.exists(self.abspath())
+
+ def isdir(self):
+ """
+ Returns whether the Node represents a folder
+
+ :rtype: bool
+ """
+ return os.path.isdir(self.abspath())
+
+ def chmod(self, val):
+ """
+ Changes the file/dir permissions::
+
+ def build(bld):
+ bld.path.chmod(493) # 0755
+ """
+ os.chmod(self.abspath(), val)
+
+ def delete(self, evict=True):
+ """
+ Removes the file/folder from the filesystem (equivalent to `rm -rf`), and remove this object from the Node tree.
+ Do not use this object after calling this method.
+ """
+ try:
+ try:
+ if os.path.isdir(self.abspath()):
+ shutil.rmtree(self.abspath())
+ else:
+ os.remove(self.abspath())
+ except OSError:
+ if os.path.exists(self.abspath()):
+ raise
+ finally:
+ if evict:
+ self.evict()
+
+ def evict(self):
+ """
+ Removes this node from the Node tree
+ """
+ del self.parent.children[self.name]
+
+ def suffix(self):
+ """
+ Returns the file rightmost extension, for example `a.b.c.d → .d`
+
+ :rtype: string
+ """
+ k = max(0, self.name.rfind('.'))
+ return self.name[k:]
+
+ def height(self):
+ """
+ Returns the depth in the folder hierarchy from the filesystem root or from all the file drives
+
+ :returns: filesystem depth
+ :rtype: integer
+ """
+ d = self
+ val = -1
+ while d:
+ d = d.parent
+ val += 1
+ return val
+
+ def listdir(self):
+ """
+ Lists the folder contents
+
+ :returns: list of file/folder names ordered alphabetically
+ :rtype: list of string
+ """
+ lst = Utils.listdir(self.abspath())
+ lst.sort()
+ return lst
+
+ def mkdir(self):
+ """
+ Creates a folder represented by this node. Intermediate folders are created as needed.
+
+ :raises: :py:class:`waflib.Errors.WafError` when the folder is missing
+ """
+ if self.isdir():
+ return
+
+ try:
+ self.parent.mkdir()
+ except OSError:
+ pass
+
+ if self.name:
+ try:
+ os.makedirs(self.abspath())
+ except OSError:
+ pass
+
+ if not self.isdir():
+ raise Errors.WafError('Could not create the directory %r' % self)
+
+ try:
+ self.children
+ except AttributeError:
+ self.children = self.dict_class()
+
+ def find_node(self, lst):
+ """
+ Finds a node on the file system (files or folders), and creates the corresponding Node objects if it exists
+
+ :param lst: relative path
+ :type lst: string or list of string
+ :returns: The corresponding Node object or None if no entry was found on the filesystem
+ :rtype: :py:class:´waflib.Node.Node´
+ """
+
+ if isinstance(lst, str):
+ lst = [x for x in Utils.split_path(lst) if x and x != '.']
+
+ if lst and lst[0].startswith('\\\\') and not self.parent:
+ node = self.ctx.root.make_node(lst[0])
+ node.cache_isdir = True
+ return node.find_node(lst[1:])
+
+ cur = self
+ for x in lst:
+ if x == '..':
+ cur = cur.parent or cur
+ continue
+
+ try:
+ ch = cur.children
+ except AttributeError:
+ cur.children = self.dict_class()
+ else:
+ try:
+ cur = ch[x]
+ continue
+ except KeyError:
+ pass
+
+ # optimistic: create the node first then look if it was correct to do so
+ cur = self.__class__(x, cur)
+ if not cur.exists():
+ cur.evict()
+ return None
+
+ if not cur.exists():
+ cur.evict()
+ return None
+
+ return cur
+
+ def make_node(self, lst):
+ """
+ Returns or creates a Node object corresponding to the input path without considering the filesystem.
+
+ :param lst: relative path
+ :type lst: string or list of string
+ :rtype: :py:class:´waflib.Node.Node´
+ """
+ if isinstance(lst, str):
+ lst = [x for x in Utils.split_path(lst) if x and x != '.']
+
+ cur = self
+ for x in lst:
+ if x == '..':
+ cur = cur.parent or cur
+ continue
+
+ try:
+ cur = cur.children[x]
+ except AttributeError:
+ cur.children = self.dict_class()
+ except KeyError:
+ pass
+ else:
+ continue
+ cur = self.__class__(x, cur)
+ return cur
+
+ def search_node(self, lst):
+ """
+ Returns a Node previously defined in the data structure. The filesystem is not considered.
+
+ :param lst: relative path
+ :type lst: string or list of string
+ :rtype: :py:class:´waflib.Node.Node´ or None if there is no entry in the Node datastructure
+ """
+ if isinstance(lst, str):
+ lst = [x for x in Utils.split_path(lst) if x and x != '.']
+
+ cur = self
+ for x in lst:
+ if x == '..':
+ cur = cur.parent or cur
+ else:
+ try:
+ cur = cur.children[x]
+ except (AttributeError, KeyError):
+ return None
+ return cur
+
+ def path_from(self, node):
+ """
+ Path of this node seen from the other::
+
+ def build(bld):
+ n1 = bld.path.find_node('foo/bar/xyz.txt')
+ n2 = bld.path.find_node('foo/stuff/')
+ n1.path_from(n2) # '../bar/xyz.txt'
+
+ :param node: path to use as a reference
+ :type node: :py:class:`waflib.Node.Node`
+ :returns: a relative path or an absolute one if that is better
+ :rtype: string
+ """
+ c1 = self
+ c2 = node
+
+ c1h = c1.height()
+ c2h = c2.height()
+
+ lst = []
+ up = 0
+
+ while c1h > c2h:
+ lst.append(c1.name)
+ c1 = c1.parent
+ c1h -= 1
+
+ while c2h > c1h:
+ up += 1
+ c2 = c2.parent
+ c2h -= 1
+
+ while not c1 is c2:
+ lst.append(c1.name)
+ up += 1
+
+ c1 = c1.parent
+ c2 = c2.parent
+
+ if c1.parent:
+ lst.extend(['..'] * up)
+ lst.reverse()
+ return os.sep.join(lst) or '.'
+ else:
+ return self.abspath()
+
+ def abspath(self):
+ """
+ Returns the absolute path. A cache is kept in the context as ``cache_node_abspath``
+
+ :rtype: string
+ """
+ try:
+ return self.cache_abspath
+ except AttributeError:
+ pass
+ # think twice before touching this (performance + complexity + correctness)
+
+ if not self.parent:
+ val = os.sep
+ elif not self.parent.name:
+ val = os.sep + self.name
+ else:
+ val = self.parent.abspath() + os.sep + self.name
+ self.cache_abspath = val
+ return val
+
+ if Utils.is_win32:
+ def abspath(self):
+ try:
+ return self.cache_abspath
+ except AttributeError:
+ pass
+ if not self.parent:
+ val = ''
+ elif not self.parent.name:
+ val = self.name + os.sep
+ else:
+ val = self.parent.abspath().rstrip(os.sep) + os.sep + self.name
+ self.cache_abspath = val
+ return val
+
+ def is_child_of(self, node):
+ """
+ Returns whether the object belongs to a subtree of the input node::
+
+ def build(bld):
+ node = bld.path.find_node('wscript')
+ node.is_child_of(bld.path) # True
+
+ :param node: path to use as a reference
+ :type node: :py:class:`waflib.Node.Node`
+ :rtype: bool
+ """
+ p = self
+ diff = self.height() - node.height()
+ while diff > 0:
+ diff -= 1
+ p = p.parent
+ return p is node
+
+ def ant_iter(self, accept=None, maxdepth=25, pats=[], dir=False, src=True, remove=True, quiet=False):
+ """
+ Recursive method used by :py:meth:`waflib.Node.ant_glob`.
+
+ :param accept: function used for accepting/rejecting a node, returns the patterns that can be still accepted in recursion
+ :type accept: function
+ :param maxdepth: maximum depth in the filesystem (25)
+ :type maxdepth: int
+ :param pats: list of patterns to accept and list of patterns to exclude
+ :type pats: tuple
+ :param dir: return folders too (False by default)
+ :type dir: bool
+ :param src: return files (True by default)
+ :type src: bool
+ :param remove: remove files/folders that do not exist (True by default)
+ :type remove: bool
+ :param quiet: disable build directory traversal warnings (verbose mode)
+ :type quiet: bool
+ :returns: A generator object to iterate from
+ :rtype: iterator
+ """
+ dircont = self.listdir()
+ dircont.sort()
+
+ try:
+ lst = set(self.children.keys())
+ except AttributeError:
+ self.children = self.dict_class()
+ else:
+ if remove:
+ for x in lst - set(dircont):
+ self.children[x].evict()
+
+ for name in dircont:
+ npats = accept(name, pats)
+ if npats and npats[0]:
+ accepted = [] in npats[0]
+
+ node = self.make_node([name])
+
+ isdir = node.isdir()
+ if accepted:
+ if isdir:
+ if dir:
+ yield node
+ elif src:
+ yield node
+
+ if isdir:
+ node.cache_isdir = True
+ if maxdepth:
+ for k in node.ant_iter(accept=accept, maxdepth=maxdepth - 1, pats=npats, dir=dir, src=src, remove=remove, quiet=quiet):
+ yield k
+
+ def ant_glob(self, *k, **kw):
+ """
+ Finds files across folders and returns Node objects:
+
+ * ``**/*`` find all files recursively
+ * ``**/*.class`` find all files ending by .class
+ * ``..`` find files having two dot characters
+
+ For example::
+
+ def configure(cfg):
+ # find all .cpp files
+ cfg.path.ant_glob('**/*.cpp')
+ # find particular files from the root filesystem (can be slow)
+ cfg.root.ant_glob('etc/*.txt')
+ # simple exclusion rule example
+ cfg.path.ant_glob('*.c*', excl=['*.c'], src=True, dir=False)
+
+ For more information about the patterns, consult http://ant.apache.org/manual/dirtasks.html
+ Please remember that the '..' sequence does not represent the parent directory::
+
+ def configure(cfg):
+ cfg.path.ant_glob('../*.h') # incorrect
+ cfg.path.parent.ant_glob('*.h') # correct
+
+ The Node structure is itself a filesystem cache, so certain precautions must
+ be taken while matching files in the build or installation phases.
+ Nodes objects that do have a corresponding file or folder are garbage-collected by default.
+ This garbage collection is usually required to prevent returning files that do not
+ exist anymore. Yet, this may also remove Node objects of files that are yet-to-be built.
+
+ This typically happens when trying to match files in the build directory,
+ but there are also cases when files are created in the source directory.
+ Run ``waf -v`` to display any warnings, and try consider passing ``remove=False``
+ when matching files in the build directory.
+
+ Since ant_glob can traverse both source and build folders, it is a best practice
+ to call this method only from the most specific build node::
+
+ def build(bld):
+ # traverses the build directory, may need ``remove=False``:
+ bld.path.ant_glob('project/dir/**/*.h')
+ # better, no accidental build directory traversal:
+ bld.path.find_node('project/dir').ant_glob('**/*.h') # best
+
+ In addition, files and folders are listed immediately. When matching files in the
+ build folders, consider passing ``generator=True`` so that the generator object
+ returned can defer computation to a later stage. For example::
+
+ def build(bld):
+ bld(rule='tar xvf ${SRC}', source='arch.tar')
+ bld.add_group()
+ gen = bld.bldnode.ant_glob("*.h", generator=True, remove=True)
+ # files will be listed only after the arch.tar is unpacked
+ bld(rule='ls ${SRC}', source=gen, name='XYZ')
+
+
+ :param incl: ant patterns or list of patterns to include
+ :type incl: string or list of strings
+ :param excl: ant patterns or list of patterns to exclude
+ :type excl: string or list of strings
+ :param dir: return folders too (False by default)
+ :type dir: bool
+ :param src: return files (True by default)
+ :type src: bool
+ :param maxdepth: maximum depth of recursion
+ :type maxdepth: int
+ :param ignorecase: ignore case while matching (False by default)
+ :type ignorecase: bool
+ :param generator: Whether to evaluate the Nodes lazily
+ :type generator: bool
+ :param remove: remove files/folders that do not exist (True by default)
+ :type remove: bool
+ :param quiet: disable build directory traversal warnings (verbose mode)
+ :type quiet: bool
+ :returns: The corresponding Node objects as a list or as a generator object (generator=True)
+ :rtype: by default, list of :py:class:`waflib.Node.Node` instances
+ """
+ src = kw.get('src', True)
+ dir = kw.get('dir')
+ excl = kw.get('excl', exclude_regs)
+ incl = k and k[0] or kw.get('incl', '**')
+ remove = kw.get('remove', True)
+ maxdepth = kw.get('maxdepth', 25)
+ ignorecase = kw.get('ignorecase', False)
+ quiet = kw.get('quiet', False)
+ pats = (ant_matcher(incl, ignorecase), ant_matcher(excl, ignorecase))
+
+ if kw.get('generator'):
+ return Utils.lazy_generator(self.ant_iter, (ant_sub_matcher, maxdepth, pats, dir, src, remove, quiet))
+
+ it = self.ant_iter(ant_sub_matcher, maxdepth, pats, dir, src, remove, quiet)
+ if kw.get('flat'):
+ # returns relative paths as a space-delimited string
+ # prefer Node objects whenever possible
+ return ' '.join(x.path_from(self) for x in it)
+ return list(it)
+
+ # ----------------------------------------------------------------------------
+ # the methods below require the source/build folders (bld.srcnode/bld.bldnode)
+
+ def is_src(self):
+ """
+ Returns True if the node is below the source directory. Note that ``!is_src() ≠ is_bld()``
+
+ :rtype: bool
+ """
+ cur = self
+ x = self.ctx.srcnode
+ y = self.ctx.bldnode
+ while cur.parent:
+ if cur is y:
+ return False
+ if cur is x:
+ return True
+ cur = cur.parent
+ return False
+
+ def is_bld(self):
+ """
+ Returns True if the node is below the build directory. Note that ``!is_bld() ≠ is_src()``
+
+ :rtype: bool
+ """
+ cur = self
+ y = self.ctx.bldnode
+ while cur.parent:
+ if cur is y:
+ return True
+ cur = cur.parent
+ return False
+
+ def get_src(self):
+ """
+ Returns the corresponding Node object in the source directory (or self if already
+ under the source directory). Use this method only if the purpose is to create
+ a Node object (this is common with folders but not with files, see ticket 1937)
+
+ :rtype: :py:class:`waflib.Node.Node`
+ """
+ cur = self
+ x = self.ctx.srcnode
+ y = self.ctx.bldnode
+ lst = []
+ while cur.parent:
+ if cur is y:
+ lst.reverse()
+ return x.make_node(lst)
+ if cur is x:
+ return self
+ lst.append(cur.name)
+ cur = cur.parent
+ return self
+
+ def get_bld(self):
+ """
+ Return the corresponding Node object in the build directory (or self if already
+ under the build directory). Use this method only if the purpose is to create
+ a Node object (this is common with folders but not with files, see ticket 1937)
+
+ :rtype: :py:class:`waflib.Node.Node`
+ """
+ cur = self
+ x = self.ctx.srcnode
+ y = self.ctx.bldnode
+ lst = []
+ while cur.parent:
+ if cur is y:
+ return self
+ if cur is x:
+ lst.reverse()
+ return self.ctx.bldnode.make_node(lst)
+ lst.append(cur.name)
+ cur = cur.parent
+ # the file is external to the current project, make a fake root in the current build directory
+ lst.reverse()
+ if lst and Utils.is_win32 and len(lst[0]) == 2 and lst[0].endswith(':'):
+ lst[0] = lst[0][0]
+ return self.ctx.bldnode.make_node(['__root__'] + lst)
+
+ def find_resource(self, lst):
+ """
+ Use this method in the build phase to find source files corresponding to the relative path given.
+
+ First it looks up the Node data structure to find any declared Node object in the build directory.
+ If None is found, it then considers the filesystem in the source directory.
+
+ :param lst: relative path
+ :type lst: string or list of string
+ :returns: the corresponding Node object or None
+ :rtype: :py:class:`waflib.Node.Node`
+ """
+ if isinstance(lst, str):
+ lst = [x for x in Utils.split_path(lst) if x and x != '.']
+
+ node = self.get_bld().search_node(lst)
+ if not node:
+ node = self.get_src().find_node(lst)
+ if node and node.isdir():
+ return None
+ return node
+
+ def find_or_declare(self, lst):
+ """
+ Use this method in the build phase to declare output files which
+ are meant to be written in the build directory.
+
+ This method creates the Node object and its parent folder
+ as needed.
+
+ :param lst: relative path
+ :type lst: string or list of string
+ """
+ if isinstance(lst, str) and os.path.isabs(lst):
+ node = self.ctx.root.make_node(lst)
+ else:
+ node = self.get_bld().make_node(lst)
+ node.parent.mkdir()
+ return node
+
+ def find_dir(self, lst):
+ """
+ Searches for a folder on the filesystem (see :py:meth:`waflib.Node.Node.find_node`)
+
+ :param lst: relative path
+ :type lst: string or list of string
+ :returns: The corresponding Node object or None if there is no such folder
+ :rtype: :py:class:`waflib.Node.Node`
+ """
+ if isinstance(lst, str):
+ lst = [x for x in Utils.split_path(lst) if x and x != '.']
+
+ node = self.find_node(lst)
+ if node and not node.isdir():
+ return None
+ return node
+
+ # helpers for building things
+ def change_ext(self, ext, ext_in=None):
+ """
+ Declares a build node with a distinct extension; this is uses :py:meth:`waflib.Node.Node.find_or_declare`
+
+ :return: A build node of the same path, but with a different extension
+ :rtype: :py:class:`waflib.Node.Node`
+ """
+ name = self.name
+ if ext_in is None:
+ k = name.rfind('.')
+ if k >= 0:
+ name = name[:k] + ext
+ else:
+ name = name + ext
+ else:
+ name = name[:- len(ext_in)] + ext
+
+ return self.parent.find_or_declare([name])
+
+ def bldpath(self):
+ """
+ Returns the relative path seen from the build directory ``src/foo.cpp``
+
+ :rtype: string
+ """
+ return self.path_from(self.ctx.bldnode)
+
+ def srcpath(self):
+ """
+ Returns the relative path seen from the source directory ``../src/foo.cpp``
+
+ :rtype: string
+ """
+ return self.path_from(self.ctx.srcnode)
+
+ def relpath(self):
+ """
+ If a file in the build directory, returns :py:meth:`waflib.Node.Node.bldpath`,
+ else returns :py:meth:`waflib.Node.Node.srcpath`
+
+ :rtype: string
+ """
+ cur = self
+ x = self.ctx.bldnode
+ while cur.parent:
+ if cur is x:
+ return self.bldpath()
+ cur = cur.parent
+ return self.srcpath()
+
+ def bld_dir(self):
+ """
+ Equivalent to self.parent.bldpath()
+
+ :rtype: string
+ """
+ return self.parent.bldpath()
+
+ def h_file(self):
+ """
+ See :py:func:`waflib.Utils.h_file`
+
+ :return: a hash representing the file contents
+ :rtype: string or bytes
+ """
+ return Utils.h_file(self.abspath())
+
+ def get_bld_sig(self):
+ """
+ Returns a signature (see :py:meth:`waflib.Node.Node.h_file`) for the purpose
+ of build dependency calculation. This method uses a per-context cache.
+
+ :return: a hash representing the object contents
+ :rtype: string or bytes
+ """
+ # previous behaviour can be set by returning self.ctx.node_sigs[self] when a build node
+ try:
+ cache = self.ctx.cache_sig
+ except AttributeError:
+ cache = self.ctx.cache_sig = {}
+ try:
+ ret = cache[self]
+ except KeyError:
+ p = self.abspath()
+ try:
+ ret = cache[self] = self.h_file()
+ except EnvironmentError:
+ if self.isdir():
+ # allow folders as build nodes, do not use the creation time
+ st = os.stat(p)
+ ret = cache[self] = Utils.h_list([p, st.st_ino, st.st_mode])
+ return ret
+ raise
+ return ret
+
+pickle_lock = Utils.threading.Lock()
+"""Lock mandatory for thread-safe node serialization"""
+
+class Nod3(Node):
+ """Mandatory subclass for thread-safe node serialization"""
+ pass # do not remove
+
+
diff --git a/waflib/Options.py b/waflib/Options.py
new file mode 100644
index 0000000..ad802d4
--- /dev/null
+++ b/waflib/Options.py
@@ -0,0 +1,342 @@
+#!/usr/bin/env python
+# encoding: utf-8
+# Scott Newton, 2005 (scottn)
+# Thomas Nagy, 2006-2018 (ita)
+
+"""
+Support for waf command-line options
+
+Provides default and command-line options, as well the command
+that reads the ``options`` wscript function.
+"""
+
+import os, tempfile, optparse, sys, re
+from waflib import Logs, Utils, Context, Errors
+
+options = optparse.Values()
+"""
+A global dictionary representing user-provided command-line options::
+
+ $ waf --foo=bar
+"""
+
+commands = []
+"""
+List of commands to execute extracted from the command-line. This list
+is consumed during the execution by :py:func:`waflib.Scripting.run_commands`.
+"""
+
+envvars = []
+"""
+List of environment variable declarations placed after the Waf executable name.
+These are detected by searching for "=" in the remaining arguments.
+You probably do not want to use this.
+"""
+
+lockfile = os.environ.get('WAFLOCK', '.lock-waf_%s_build' % sys.platform)
+"""
+Name of the lock file that marks a project as configured
+"""
+
+class opt_parser(optparse.OptionParser):
+ """
+ Command-line options parser.
+ """
+ def __init__(self, ctx, allow_unknown=False):
+ optparse.OptionParser.__init__(self, conflict_handler='resolve', add_help_option=False,
+ version='waf %s (%s)' % (Context.WAFVERSION, Context.WAFREVISION))
+ self.formatter.width = Logs.get_term_cols()
+ self.ctx = ctx
+ self.allow_unknown = allow_unknown
+
+ def _process_args(self, largs, rargs, values):
+ """
+ Custom _process_args to allow unknown options according to the allow_unknown status
+ """
+ while rargs:
+ try:
+ optparse.OptionParser._process_args(self,largs,rargs,values)
+ except (optparse.BadOptionError, optparse.AmbiguousOptionError) as e:
+ if self.allow_unknown:
+ largs.append(e.opt_str)
+ else:
+ self.error(str(e))
+
+ def print_usage(self, file=None):
+ return self.print_help(file)
+
+ def get_usage(self):
+ """
+ Builds the message to print on ``waf --help``
+
+ :rtype: string
+ """
+ cmds_str = {}
+ for cls in Context.classes:
+ if not cls.cmd or cls.cmd == 'options' or cls.cmd.startswith( '_' ):
+ continue
+
+ s = cls.__doc__ or ''
+ cmds_str[cls.cmd] = s
+
+ if Context.g_module:
+ for (k, v) in Context.g_module.__dict__.items():
+ if k in ('options', 'init', 'shutdown'):
+ continue
+
+ if type(v) is type(Context.create_context):
+ if v.__doc__ and not k.startswith('_'):
+ cmds_str[k] = v.__doc__
+
+ just = 0
+ for k in cmds_str:
+ just = max(just, len(k))
+
+ lst = [' %s: %s' % (k.ljust(just), v) for (k, v) in cmds_str.items()]
+ lst.sort()
+ ret = '\n'.join(lst)
+
+ return '''waf [commands] [options]
+
+Main commands (example: ./waf build -j4)
+%s
+''' % ret
+
+
+class OptionsContext(Context.Context):
+ """
+ Collects custom options from wscript files and parses the command line.
+ Sets the global :py:const:`waflib.Options.commands` and :py:const:`waflib.Options.options` values.
+ """
+ cmd = 'options'
+ fun = 'options'
+
+ def __init__(self, **kw):
+ super(OptionsContext, self).__init__(**kw)
+
+ self.parser = opt_parser(self)
+ """Instance of :py:class:`waflib.Options.opt_parser`"""
+
+ self.option_groups = {}
+
+ jobs = self.jobs()
+ p = self.add_option
+ color = os.environ.get('NOCOLOR', '') and 'no' or 'auto'
+ if os.environ.get('CLICOLOR', '') == '0':
+ color = 'no'
+ elif os.environ.get('CLICOLOR_FORCE', '') == '1':
+ color = 'yes'
+ p('-c', '--color', dest='colors', default=color, action='store', help='whether to use colors (yes/no/auto) [default: auto]', choices=('yes', 'no', 'auto'))
+ p('-j', '--jobs', dest='jobs', default=jobs, type='int', help='amount of parallel jobs (%r)' % jobs)
+ p('-k', '--keep', dest='keep', default=0, action='count', help='continue despite errors (-kk to try harder)')
+ p('-v', '--verbose', dest='verbose', default=0, action='count', help='verbosity level -v -vv or -vvv [default: 0]')
+ p('--zones', dest='zones', default='', action='store', help='debugging zones (task_gen, deps, tasks, etc)')
+ p('--profile', dest='profile', default=0, action='store_true', help=optparse.SUPPRESS_HELP)
+ p('--pdb', dest='pdb', default=0, action='store_true', help=optparse.SUPPRESS_HELP)
+ p('-h', '--help', dest='whelp', default=0, action='store_true', help="show this help message and exit")
+
+ gr = self.add_option_group('Configuration options')
+ self.option_groups['configure options'] = gr
+
+ gr.add_option('-o', '--out', action='store', default='', help='build dir for the project', dest='out')
+ gr.add_option('-t', '--top', action='store', default='', help='src dir for the project', dest='top')
+
+ gr.add_option('--no-lock-in-run', action='store_true', default='', help=optparse.SUPPRESS_HELP, dest='no_lock_in_run')
+ gr.add_option('--no-lock-in-out', action='store_true', default='', help=optparse.SUPPRESS_HELP, dest='no_lock_in_out')
+ gr.add_option('--no-lock-in-top', action='store_true', default='', help=optparse.SUPPRESS_HELP, dest='no_lock_in_top')
+
+ default_prefix = getattr(Context.g_module, 'default_prefix', os.environ.get('PREFIX'))
+ if not default_prefix:
+ if Utils.unversioned_sys_platform() == 'win32':
+ d = tempfile.gettempdir()
+ default_prefix = d[0].upper() + d[1:]
+ # win32 preserves the case, but gettempdir does not
+ else:
+ default_prefix = '/usr/local/'
+ gr.add_option('--prefix', dest='prefix', default=default_prefix, help='installation prefix [default: %r]' % default_prefix)
+ gr.add_option('--bindir', dest='bindir', help='bindir')
+ gr.add_option('--libdir', dest='libdir', help='libdir')
+
+ gr = self.add_option_group('Build and installation options')
+ self.option_groups['build and install options'] = gr
+ gr.add_option('-p', '--progress', dest='progress_bar', default=0, action='count', help= '-p: progress bar; -pp: ide output')
+ gr.add_option('--targets', dest='targets', default='', action='store', help='task generators, e.g. "target1,target2"')
+
+ gr = self.add_option_group('Step options')
+ self.option_groups['step options'] = gr
+ gr.add_option('--files', dest='files', default='', action='store', help='files to process, by regexp, e.g. "*/main.c,*/test/main.o"')
+
+ default_destdir = os.environ.get('DESTDIR', '')
+
+ gr = self.add_option_group('Installation and uninstallation options')
+ self.option_groups['install/uninstall options'] = gr
+ gr.add_option('--destdir', help='installation root [default: %r]' % default_destdir, default=default_destdir, dest='destdir')
+ gr.add_option('-f', '--force', dest='force', default=False, action='store_true', help='force file installation')
+ gr.add_option('--distcheck-args', metavar='ARGS', help='arguments to pass to distcheck', default=None, action='store')
+
+ def jobs(self):
+ """
+ Finds the optimal amount of cpu cores to use for parallel jobs.
+ At runtime the options can be obtained from :py:const:`waflib.Options.options` ::
+
+ from waflib.Options import options
+ njobs = options.jobs
+
+ :return: the amount of cpu cores
+ :rtype: int
+ """
+ count = int(os.environ.get('JOBS', 0))
+ if count < 1:
+ if 'NUMBER_OF_PROCESSORS' in os.environ:
+ # on Windows, use the NUMBER_OF_PROCESSORS environment variable
+ count = int(os.environ.get('NUMBER_OF_PROCESSORS', 1))
+ else:
+ # on everything else, first try the POSIX sysconf values
+ if hasattr(os, 'sysconf_names'):
+ if 'SC_NPROCESSORS_ONLN' in os.sysconf_names:
+ count = int(os.sysconf('SC_NPROCESSORS_ONLN'))
+ elif 'SC_NPROCESSORS_CONF' in os.sysconf_names:
+ count = int(os.sysconf('SC_NPROCESSORS_CONF'))
+ if not count and os.name not in ('nt', 'java'):
+ try:
+ tmp = self.cmd_and_log(['sysctl', '-n', 'hw.ncpu'], quiet=0)
+ except Errors.WafError:
+ pass
+ else:
+ if re.match('^[0-9]+$', tmp):
+ count = int(tmp)
+ if count < 1:
+ count = 1
+ elif count > 1024:
+ count = 1024
+ return count
+
+ def add_option(self, *k, **kw):
+ """
+ Wraps ``optparse.add_option``::
+
+ def options(ctx):
+ ctx.add_option('-u', '--use', dest='use', default=False,
+ action='store_true', help='a boolean option')
+
+ :rtype: optparse option object
+ """
+ return self.parser.add_option(*k, **kw)
+
+ def add_option_group(self, *k, **kw):
+ """
+ Wraps ``optparse.add_option_group``::
+
+ def options(ctx):
+ gr = ctx.add_option_group('some options')
+ gr.add_option('-u', '--use', dest='use', default=False, action='store_true')
+
+ :rtype: optparse option group object
+ """
+ try:
+ gr = self.option_groups[k[0]]
+ except KeyError:
+ gr = self.parser.add_option_group(*k, **kw)
+ self.option_groups[k[0]] = gr
+ return gr
+
+ def get_option_group(self, opt_str):
+ """
+ Wraps ``optparse.get_option_group``::
+
+ def options(ctx):
+ gr = ctx.get_option_group('configure options')
+ gr.add_option('-o', '--out', action='store', default='',
+ help='build dir for the project', dest='out')
+
+ :rtype: optparse option group object
+ """
+ try:
+ return self.option_groups[opt_str]
+ except KeyError:
+ for group in self.parser.option_groups:
+ if group.title == opt_str:
+ return group
+ return None
+
+ def sanitize_path(self, path, cwd=None):
+ if not cwd:
+ cwd = Context.launch_dir
+ p = os.path.expanduser(path)
+ p = os.path.join(cwd, p)
+ p = os.path.normpath(p)
+ p = os.path.abspath(p)
+ return p
+
+ def parse_cmd_args(self, _args=None, cwd=None, allow_unknown=False):
+ """
+ Just parse the arguments
+ """
+ self.parser.allow_unknown = allow_unknown
+ (options, leftover_args) = self.parser.parse_args(args=_args)
+ envvars = []
+ commands = []
+ for arg in leftover_args:
+ if '=' in arg:
+ envvars.append(arg)
+ elif arg != 'options':
+ commands.append(arg)
+
+ for name in 'top out destdir prefix bindir libdir'.split():
+ # those paths are usually expanded from Context.launch_dir
+ if getattr(options, name, None):
+ path = self.sanitize_path(getattr(options, name), cwd)
+ setattr(options, name, path)
+ return options, commands, envvars
+
+ def init_module_vars(self, arg_options, arg_commands, arg_envvars):
+ options.__dict__.clear()
+ del commands[:]
+ del envvars[:]
+
+ options.__dict__.update(arg_options.__dict__)
+ commands.extend(arg_commands)
+ envvars.extend(arg_envvars)
+
+ for var in envvars:
+ (name, value) = var.split('=', 1)
+ os.environ[name.strip()] = value
+
+ def init_logs(self, options, commands, envvars):
+ Logs.verbose = options.verbose
+ if options.verbose >= 1:
+ self.load('errcheck')
+
+ colors = {'yes' : 2, 'auto' : 1, 'no' : 0}[options.colors]
+ Logs.enable_colors(colors)
+
+ if options.zones:
+ Logs.zones = options.zones.split(',')
+ if not Logs.verbose:
+ Logs.verbose = 1
+ elif Logs.verbose > 0:
+ Logs.zones = ['runner']
+ if Logs.verbose > 2:
+ Logs.zones = ['*']
+
+ def parse_args(self, _args=None):
+ """
+ Parses arguments from a list which is not necessarily the command-line.
+ Initializes the module variables options, commands and envvars
+ If help is requested, prints it and exit the application
+
+ :param _args: arguments
+ :type _args: list of strings
+ """
+ options, commands, envvars = self.parse_cmd_args()
+ self.init_logs(options, commands, envvars)
+ self.init_module_vars(options, commands, envvars)
+
+ def execute(self):
+ """
+ See :py:func:`waflib.Context.Context.execute`
+ """
+ super(OptionsContext, self).execute()
+ self.parse_args()
+ Utils.alloc_process_pool(options.jobs)
+
diff --git a/waflib/README.md b/waflib/README.md
new file mode 100644
index 0000000..c5361b9
--- /dev/null
+++ b/waflib/README.md
@@ -0,0 +1,24 @@
+Autowaf
+=======
+
+This is autowaf, a bundle of waf and a few extensions intended to be easy to
+use directly as source code in a project. Using this as a submodule or subtree
+named `waflib` in a project allows waf to be used without including binary
+encoded data in the waf script. This gets along with revision control and
+distributions better, among other advantages, without losing
+self-containedness.
+
+To use this in a project, add this repository as a directory named `waflib` in
+the top level of the project, and link or copy `waf` to the top level.
+
+Two waf extras are also included: `autowaf.py` and `lv2.py`.
+
+The `autowaf.py` module is a kitchen sink of Python utilities for building
+consistent packages, and can be imported in a wcript as
+`waflib.extras.autowaf`.
+
+The `lv2.py` extra defines options for LV2 plugin installation paths. It can
+be used by calling `opt.load('lv2')` and `conf.load('lv2')` in the appropriate
+locations in a wscript.
+
+ -- David Robillard <d@drobilla.net>
diff --git a/waflib/Runner.py b/waflib/Runner.py
new file mode 100644
index 0000000..5d27669
--- /dev/null
+++ b/waflib/Runner.py
@@ -0,0 +1,617 @@
+#!/usr/bin/env python
+# encoding: utf-8
+# Thomas Nagy, 2005-2018 (ita)
+
+"""
+Runner.py: Task scheduling and execution
+"""
+
+import heapq, traceback
+try:
+ from queue import Queue, PriorityQueue
+except ImportError:
+ from Queue import Queue
+ try:
+ from Queue import PriorityQueue
+ except ImportError:
+ class PriorityQueue(Queue):
+ def _init(self, maxsize):
+ self.maxsize = maxsize
+ self.queue = []
+ def _put(self, item):
+ heapq.heappush(self.queue, item)
+ def _get(self):
+ return heapq.heappop(self.queue)
+
+from waflib import Utils, Task, Errors, Logs
+
+GAP = 5
+"""
+Wait for at least ``GAP * njobs`` before trying to enqueue more tasks to run
+"""
+
+class PriorityTasks(object):
+ def __init__(self):
+ self.lst = []
+ def __len__(self):
+ return len(self.lst)
+ def __iter__(self):
+ return iter(self.lst)
+ def __str__(self):
+ return 'PriorityTasks: [%s]' % '\n '.join(str(x) for x in self.lst)
+ def clear(self):
+ self.lst = []
+ def append(self, task):
+ heapq.heappush(self.lst, task)
+ def appendleft(self, task):
+ "Deprecated, do not use"
+ heapq.heappush(self.lst, task)
+ def pop(self):
+ return heapq.heappop(self.lst)
+ def extend(self, lst):
+ if self.lst:
+ for x in lst:
+ self.append(x)
+ else:
+ if isinstance(lst, list):
+ self.lst = lst
+ heapq.heapify(lst)
+ else:
+ self.lst = lst.lst
+
+class Consumer(Utils.threading.Thread):
+ """
+ Daemon thread object that executes a task. It shares a semaphore with
+ the coordinator :py:class:`waflib.Runner.Spawner`. There is one
+ instance per task to consume.
+ """
+ def __init__(self, spawner, task):
+ Utils.threading.Thread.__init__(self)
+ self.task = task
+ """Task to execute"""
+ self.spawner = spawner
+ """Coordinator object"""
+ self.setDaemon(1)
+ self.start()
+ def run(self):
+ """
+ Processes a single task
+ """
+ try:
+ if not self.spawner.master.stop:
+ self.spawner.master.process_task(self.task)
+ finally:
+ self.spawner.sem.release()
+ self.spawner.master.out.put(self.task)
+ self.task = None
+ self.spawner = None
+
+class Spawner(Utils.threading.Thread):
+ """
+ Daemon thread that consumes tasks from :py:class:`waflib.Runner.Parallel` producer and
+ spawns a consuming thread :py:class:`waflib.Runner.Consumer` for each
+ :py:class:`waflib.Task.Task` instance.
+ """
+ def __init__(self, master):
+ Utils.threading.Thread.__init__(self)
+ self.master = master
+ """:py:class:`waflib.Runner.Parallel` producer instance"""
+ self.sem = Utils.threading.Semaphore(master.numjobs)
+ """Bounded semaphore that prevents spawning more than *n* concurrent consumers"""
+ self.setDaemon(1)
+ self.start()
+ def run(self):
+ """
+ Spawns new consumers to execute tasks by delegating to :py:meth:`waflib.Runner.Spawner.loop`
+ """
+ try:
+ self.loop()
+ except Exception:
+ # Python 2 prints unnecessary messages when shutting down
+ # we also want to stop the thread properly
+ pass
+ def loop(self):
+ """
+ Consumes task objects from the producer; ends when the producer has no more
+ task to provide.
+ """
+ master = self.master
+ while 1:
+ task = master.ready.get()
+ self.sem.acquire()
+ if not master.stop:
+ task.log_display(task.generator.bld)
+ Consumer(self, task)
+
+class Parallel(object):
+ """
+ Schedule the tasks obtained from the build context for execution.
+ """
+ def __init__(self, bld, j=2):
+ """
+ The initialization requires a build context reference
+ for computing the total number of jobs.
+ """
+
+ self.numjobs = j
+ """
+ Amount of parallel consumers to use
+ """
+
+ self.bld = bld
+ """
+ Instance of :py:class:`waflib.Build.BuildContext`
+ """
+
+ self.outstanding = PriorityTasks()
+ """Heap of :py:class:`waflib.Task.Task` that may be ready to be executed"""
+
+ self.postponed = PriorityTasks()
+ """Heap of :py:class:`waflib.Task.Task` which are not ready to run for non-DAG reasons"""
+
+ self.incomplete = set()
+ """List of :py:class:`waflib.Task.Task` waiting for dependent tasks to complete (DAG)"""
+
+ self.ready = PriorityQueue(0)
+ """List of :py:class:`waflib.Task.Task` ready to be executed by consumers"""
+
+ self.out = Queue(0)
+ """List of :py:class:`waflib.Task.Task` returned by the task consumers"""
+
+ self.count = 0
+ """Amount of tasks that may be processed by :py:class:`waflib.Runner.TaskConsumer`"""
+
+ self.processed = 0
+ """Amount of tasks processed"""
+
+ self.stop = False
+ """Error flag to stop the build"""
+
+ self.error = []
+ """Tasks that could not be executed"""
+
+ self.biter = None
+ """Task iterator which must give groups of parallelizable tasks when calling ``next()``"""
+
+ self.dirty = False
+ """
+ Flag that indicates that the build cache must be saved when a task was executed
+ (calls :py:meth:`waflib.Build.BuildContext.store`)"""
+
+ self.revdeps = Utils.defaultdict(set)
+ """
+ The reverse dependency graph of dependencies obtained from Task.run_after
+ """
+
+ self.spawner = None
+ """
+ Coordinating daemon thread that spawns thread consumers
+ """
+ if self.numjobs > 1:
+ self.spawner = Spawner(self)
+
+ def get_next_task(self):
+ """
+ Obtains the next Task instance to run
+
+ :rtype: :py:class:`waflib.Task.Task`
+ """
+ if not self.outstanding:
+ return None
+ return self.outstanding.pop()
+
+ def postpone(self, tsk):
+ """
+ Adds the task to the list :py:attr:`waflib.Runner.Parallel.postponed`.
+ The order is scrambled so as to consume as many tasks in parallel as possible.
+
+ :param tsk: task instance
+ :type tsk: :py:class:`waflib.Task.Task`
+ """
+ self.postponed.append(tsk)
+
+ def refill_task_list(self):
+ """
+ Pulls a next group of tasks to execute in :py:attr:`waflib.Runner.Parallel.outstanding`.
+ Ensures that all tasks in the current build group are complete before processing the next one.
+ """
+ while self.count > self.numjobs * GAP:
+ self.get_out()
+
+ while not self.outstanding:
+ if self.count:
+ self.get_out()
+ if self.outstanding:
+ break
+ elif self.postponed:
+ try:
+ cond = self.deadlock == self.processed
+ except AttributeError:
+ pass
+ else:
+ if cond:
+ # The most common reason is conflicting build order declaration
+ # for example: "X run_after Y" and "Y run_after X"
+ # Another can be changing "run_after" dependencies while the build is running
+ # for example: updating "tsk.run_after" in the "runnable_status" method
+ lst = []
+ for tsk in self.postponed:
+ deps = [id(x) for x in tsk.run_after if not x.hasrun]
+ lst.append('%s\t-> %r' % (repr(tsk), deps))
+ if not deps:
+ lst.append('\n task %r dependencies are done, check its *runnable_status*?' % id(tsk))
+ raise Errors.WafError('Deadlock detected: check the task build order%s' % ''.join(lst))
+ self.deadlock = self.processed
+
+ if self.postponed:
+ self.outstanding.extend(self.postponed)
+ self.postponed.clear()
+ elif not self.count:
+ if self.incomplete:
+ for x in self.incomplete:
+ for k in x.run_after:
+ if not k.hasrun:
+ break
+ else:
+ # dependency added after the build started without updating revdeps
+ self.incomplete.remove(x)
+ self.outstanding.append(x)
+ break
+ else:
+ if self.stop or self.error:
+ break
+ raise Errors.WafError('Broken revdeps detected on %r' % self.incomplete)
+ else:
+ tasks = next(self.biter)
+ ready, waiting = self.prio_and_split(tasks)
+ self.outstanding.extend(ready)
+ self.incomplete.update(waiting)
+ self.total = self.bld.total()
+ break
+
+ def add_more_tasks(self, tsk):
+ """
+ If a task provides :py:attr:`waflib.Task.Task.more_tasks`, then the tasks contained
+ in that list are added to the current build and will be processed before the next build group.
+
+ The priorities for dependent tasks are not re-calculated globally
+
+ :param tsk: task instance
+ :type tsk: :py:attr:`waflib.Task.Task`
+ """
+ if getattr(tsk, 'more_tasks', None):
+ more = set(tsk.more_tasks)
+ groups_done = set()
+ def iteri(a, b):
+ for x in a:
+ yield x
+ for x in b:
+ yield x
+
+ # Update the dependency tree
+ # this assumes that task.run_after values were updated
+ for x in iteri(self.outstanding, self.incomplete):
+ for k in x.run_after:
+ if isinstance(k, Task.TaskGroup):
+ if k not in groups_done:
+ groups_done.add(k)
+ for j in k.prev & more:
+ self.revdeps[j].add(k)
+ elif k in more:
+ self.revdeps[k].add(x)
+
+ ready, waiting = self.prio_and_split(tsk.more_tasks)
+ self.outstanding.extend(ready)
+ self.incomplete.update(waiting)
+ self.total += len(tsk.more_tasks)
+
+ def mark_finished(self, tsk):
+ def try_unfreeze(x):
+ # DAG ancestors are likely to be in the incomplete set
+ # This assumes that the run_after contents have not changed
+ # after the build starts, else a deadlock may occur
+ if x in self.incomplete:
+ # TODO remove dependencies to free some memory?
+ # x.run_after.remove(tsk)
+ for k in x.run_after:
+ if not k.hasrun:
+ break
+ else:
+ self.incomplete.remove(x)
+ self.outstanding.append(x)
+
+ if tsk in self.revdeps:
+ for x in self.revdeps[tsk]:
+ if isinstance(x, Task.TaskGroup):
+ x.prev.remove(tsk)
+ if not x.prev:
+ for k in x.next:
+ # TODO necessary optimization?
+ k.run_after.remove(x)
+ try_unfreeze(k)
+ # TODO necessary optimization?
+ x.next = []
+ else:
+ try_unfreeze(x)
+ del self.revdeps[tsk]
+
+ if hasattr(tsk, 'semaphore'):
+ sem = tsk.semaphore
+ sem.release(tsk)
+ while sem.waiting and not sem.is_locked():
+ # take a frozen task, make it ready to run
+ x = sem.waiting.pop()
+ self._add_task(x)
+
+ def get_out(self):
+ """
+ Waits for a Task that task consumers add to :py:attr:`waflib.Runner.Parallel.out` after execution.
+ Adds more Tasks if necessary through :py:attr:`waflib.Runner.Parallel.add_more_tasks`.
+
+ :rtype: :py:attr:`waflib.Task.Task`
+ """
+ tsk = self.out.get()
+ if not self.stop:
+ self.add_more_tasks(tsk)
+ self.mark_finished(tsk)
+
+ self.count -= 1
+ self.dirty = True
+ return tsk
+
+ def add_task(self, tsk):
+ """
+ Enqueue a Task to :py:attr:`waflib.Runner.Parallel.ready` so that consumers can run them.
+
+ :param tsk: task instance
+ :type tsk: :py:attr:`waflib.Task.Task`
+ """
+ # TODO change in waf 2.1
+ self.ready.put(tsk)
+
+ def _add_task(self, tsk):
+ if hasattr(tsk, 'semaphore'):
+ sem = tsk.semaphore
+ try:
+ sem.acquire(tsk)
+ except IndexError:
+ sem.waiting.add(tsk)
+ return
+
+ self.count += 1
+ self.processed += 1
+ if self.numjobs == 1:
+ tsk.log_display(tsk.generator.bld)
+ try:
+ self.process_task(tsk)
+ finally:
+ self.out.put(tsk)
+ else:
+ self.add_task(tsk)
+
+ def process_task(self, tsk):
+ """
+ Processes a task and attempts to stop the build in case of errors
+ """
+ tsk.process()
+ if tsk.hasrun != Task.SUCCESS:
+ self.error_handler(tsk)
+
+ def skip(self, tsk):
+ """
+ Mark a task as skipped/up-to-date
+ """
+ tsk.hasrun = Task.SKIPPED
+ self.mark_finished(tsk)
+
+ def cancel(self, tsk):
+ """
+ Mark a task as failed because of unsatisfiable dependencies
+ """
+ tsk.hasrun = Task.CANCELED
+ self.mark_finished(tsk)
+
+ def error_handler(self, tsk):
+ """
+ Called when a task cannot be executed. The flag :py:attr:`waflib.Runner.Parallel.stop` is set,
+ unless the build is executed with::
+
+ $ waf build -k
+
+ :param tsk: task instance
+ :type tsk: :py:attr:`waflib.Task.Task`
+ """
+ if not self.bld.keep:
+ self.stop = True
+ self.error.append(tsk)
+
+ def task_status(self, tsk):
+ """
+ Obtains the task status to decide whether to run it immediately or not.
+
+ :return: the exit status, for example :py:attr:`waflib.Task.ASK_LATER`
+ :rtype: integer
+ """
+ try:
+ return tsk.runnable_status()
+ except Exception:
+ self.processed += 1
+ tsk.err_msg = traceback.format_exc()
+ if not self.stop and self.bld.keep:
+ self.skip(tsk)
+ if self.bld.keep == 1:
+ # if -k stop on the first exception, if -kk try to go as far as possible
+ if Logs.verbose > 1 or not self.error:
+ self.error.append(tsk)
+ self.stop = True
+ else:
+ if Logs.verbose > 1:
+ self.error.append(tsk)
+ return Task.EXCEPTION
+
+ tsk.hasrun = Task.EXCEPTION
+ self.error_handler(tsk)
+
+ return Task.EXCEPTION
+
+ def start(self):
+ """
+ Obtains Task instances from the BuildContext instance and adds the ones that need to be executed to
+ :py:class:`waflib.Runner.Parallel.ready` so that the :py:class:`waflib.Runner.Spawner` consumer thread
+ has them executed. Obtains the executed Tasks back from :py:class:`waflib.Runner.Parallel.out`
+ and marks the build as failed by setting the ``stop`` flag.
+ If only one job is used, then executes the tasks one by one, without consumers.
+ """
+ self.total = self.bld.total()
+
+ while not self.stop:
+
+ self.refill_task_list()
+
+ # consider the next task
+ tsk = self.get_next_task()
+ if not tsk:
+ if self.count:
+ # tasks may add new ones after they are run
+ continue
+ else:
+ # no tasks to run, no tasks running, time to exit
+ break
+
+ if tsk.hasrun:
+ # if the task is marked as "run", just skip it
+ self.processed += 1
+ continue
+
+ if self.stop: # stop immediately after a failure is detected
+ break
+
+ st = self.task_status(tsk)
+ if st == Task.RUN_ME:
+ self._add_task(tsk)
+ elif st == Task.ASK_LATER:
+ self.postpone(tsk)
+ elif st == Task.SKIP_ME:
+ self.processed += 1
+ self.skip(tsk)
+ self.add_more_tasks(tsk)
+ elif st == Task.CANCEL_ME:
+ # A dependency problem has occurred, and the
+ # build is most likely run with `waf -k`
+ if Logs.verbose > 1:
+ self.error.append(tsk)
+ self.processed += 1
+ self.cancel(tsk)
+
+ # self.count represents the tasks that have been made available to the consumer threads
+ # collect all the tasks after an error else the message may be incomplete
+ while self.error and self.count:
+ self.get_out()
+
+ self.ready.put(None)
+ if not self.stop:
+ assert not self.count
+ assert not self.postponed
+ assert not self.incomplete
+
+ def prio_and_split(self, tasks):
+ """
+ Label input tasks with priority values, and return a pair containing
+ the tasks that are ready to run and the tasks that are necessarily
+ waiting for other tasks to complete.
+
+ The priority system is really meant as an optional layer for optimization:
+ dependency cycles are found quickly, and builds should be more efficient.
+ A high priority number means that a task is processed first.
+
+ This method can be overridden to disable the priority system::
+
+ def prio_and_split(self, tasks):
+ return tasks, []
+
+ :return: A pair of task lists
+ :rtype: tuple
+ """
+ # to disable:
+ #return tasks, []
+ for x in tasks:
+ x.visited = 0
+
+ reverse = self.revdeps
+
+ groups_done = set()
+ for x in tasks:
+ for k in x.run_after:
+ if isinstance(k, Task.TaskGroup):
+ if k not in groups_done:
+ groups_done.add(k)
+ for j in k.prev:
+ reverse[j].add(k)
+ else:
+ reverse[k].add(x)
+
+ # the priority number is not the tree depth
+ def visit(n):
+ if isinstance(n, Task.TaskGroup):
+ return sum(visit(k) for k in n.next)
+
+ if n.visited == 0:
+ n.visited = 1
+
+ if n in reverse:
+ rev = reverse[n]
+ n.prio_order = n.tree_weight + len(rev) + sum(visit(k) for k in rev)
+ else:
+ n.prio_order = n.tree_weight
+
+ n.visited = 2
+ elif n.visited == 1:
+ raise Errors.WafError('Dependency cycle found!')
+ return n.prio_order
+
+ for x in tasks:
+ if x.visited != 0:
+ # must visit all to detect cycles
+ continue
+ try:
+ visit(x)
+ except Errors.WafError:
+ self.debug_cycles(tasks, reverse)
+
+ ready = []
+ waiting = []
+ for x in tasks:
+ for k in x.run_after:
+ if not k.hasrun:
+ waiting.append(x)
+ break
+ else:
+ ready.append(x)
+ return (ready, waiting)
+
+ def debug_cycles(self, tasks, reverse):
+ tmp = {}
+ for x in tasks:
+ tmp[x] = 0
+
+ def visit(n, acc):
+ if isinstance(n, Task.TaskGroup):
+ for k in n.next:
+ visit(k, acc)
+ return
+ if tmp[n] == 0:
+ tmp[n] = 1
+ for k in reverse.get(n, []):
+ visit(k, [n] + acc)
+ tmp[n] = 2
+ elif tmp[n] == 1:
+ lst = []
+ for tsk in acc:
+ lst.append(repr(tsk))
+ if tsk is n:
+ # exclude prior nodes, we want the minimum cycle
+ break
+ raise Errors.WafError('Task dependency cycle in "run_after" constraints: %s' % ''.join(lst))
+ for x in tasks:
+ visit(x, [])
+
diff --git a/waflib/Scripting.py b/waflib/Scripting.py
new file mode 100644
index 0000000..ae17a8b
--- /dev/null
+++ b/waflib/Scripting.py
@@ -0,0 +1,620 @@
+#!/usr/bin/env python
+# encoding: utf-8
+# Thomas Nagy, 2005-2018 (ita)
+
+"Module called for configuring, compiling and installing targets"
+
+from __future__ import with_statement
+
+import os, shlex, shutil, traceback, errno, sys, stat
+from waflib import Utils, Configure, Logs, Options, ConfigSet, Context, Errors, Build, Node
+
+build_dir_override = None
+
+no_climb_commands = ['configure']
+
+default_cmd = "build"
+
+def waf_entry_point(current_directory, version, wafdir):
+ """
+ This is the main entry point, all Waf execution starts here.
+
+ :param current_directory: absolute path representing the current directory
+ :type current_directory: string
+ :param version: version number
+ :type version: string
+ :param wafdir: absolute path representing the directory of the waf library
+ :type wafdir: string
+ """
+ Logs.init_log()
+
+ if Context.WAFVERSION != version:
+ Logs.error('Waf script %r and library %r do not match (directory %r)', version, Context.WAFVERSION, wafdir)
+ sys.exit(1)
+
+ # Store current directory before any chdir
+ Context.waf_dir = wafdir
+ Context.run_dir = Context.launch_dir = current_directory
+ start_dir = current_directory
+ no_climb = os.environ.get('NOCLIMB')
+
+ if len(sys.argv) > 1:
+ # os.path.join handles absolute paths
+ # if sys.argv[1] is not an absolute path, then it is relative to the current working directory
+ potential_wscript = os.path.join(current_directory, sys.argv[1])
+ if os.path.basename(potential_wscript) == Context.WSCRIPT_FILE and os.path.isfile(potential_wscript):
+ # need to explicitly normalize the path, as it may contain extra '/.'
+ path = os.path.normpath(os.path.dirname(potential_wscript))
+ start_dir = os.path.abspath(path)
+ no_climb = True
+ sys.argv.pop(1)
+
+ ctx = Context.create_context('options')
+ (options, commands, env) = ctx.parse_cmd_args(allow_unknown=True)
+ if options.top:
+ start_dir = Context.run_dir = Context.top_dir = options.top
+ no_climb = True
+ if options.out:
+ Context.out_dir = options.out
+
+ # if 'configure' is in the commands, do not search any further
+ if not no_climb:
+ for k in no_climb_commands:
+ for y in commands:
+ if y.startswith(k):
+ no_climb = True
+ break
+
+ # try to find a lock file (if the project was configured)
+ # at the same time, store the first wscript file seen
+ cur = start_dir
+ while cur:
+ try:
+ lst = os.listdir(cur)
+ except OSError:
+ lst = []
+ Logs.error('Directory %r is unreadable!', cur)
+ if Options.lockfile in lst:
+ env = ConfigSet.ConfigSet()
+ try:
+ env.load(os.path.join(cur, Options.lockfile))
+ ino = os.stat(cur)[stat.ST_INO]
+ except EnvironmentError:
+ pass
+ else:
+ # check if the folder was not moved
+ for x in (env.run_dir, env.top_dir, env.out_dir):
+ if not x:
+ continue
+ if Utils.is_win32:
+ if cur == x:
+ load = True
+ break
+ else:
+ # if the filesystem features symlinks, compare the inode numbers
+ try:
+ ino2 = os.stat(x)[stat.ST_INO]
+ except OSError:
+ pass
+ else:
+ if ino == ino2:
+ load = True
+ break
+ else:
+ Logs.warn('invalid lock file in %s', cur)
+ load = False
+
+ if load:
+ Context.run_dir = env.run_dir
+ Context.top_dir = env.top_dir
+ Context.out_dir = env.out_dir
+ break
+
+ if not Context.run_dir:
+ if Context.WSCRIPT_FILE in lst:
+ Context.run_dir = cur
+
+ next = os.path.dirname(cur)
+ if next == cur:
+ break
+ cur = next
+
+ if no_climb:
+ break
+
+ wscript = os.path.normpath(os.path.join(Context.run_dir, Context.WSCRIPT_FILE))
+ if not os.path.exists(wscript):
+ if options.whelp:
+ Logs.warn('These are the generic options (no wscript/project found)')
+ ctx.parser.print_help()
+ sys.exit(0)
+ Logs.error('Waf: Run from a folder containing a %r file (or try -h for the generic options)', Context.WSCRIPT_FILE)
+ sys.exit(1)
+
+ try:
+ os.chdir(Context.run_dir)
+ except OSError:
+ Logs.error('Waf: The folder %r is unreadable', Context.run_dir)
+ sys.exit(1)
+
+ try:
+ set_main_module(wscript)
+ except Errors.WafError as e:
+ Logs.pprint('RED', e.verbose_msg)
+ Logs.error(str(e))
+ sys.exit(1)
+ except Exception as e:
+ Logs.error('Waf: The wscript in %r is unreadable', Context.run_dir)
+ traceback.print_exc(file=sys.stdout)
+ sys.exit(2)
+
+ if options.profile:
+ import cProfile, pstats
+ cProfile.runctx('from waflib import Scripting; Scripting.run_commands()', {}, {}, 'profi.txt')
+ p = pstats.Stats('profi.txt')
+ p.sort_stats('time').print_stats(75) # or 'cumulative'
+ else:
+ try:
+ try:
+ run_commands()
+ except:
+ if options.pdb:
+ import pdb
+ type, value, tb = sys.exc_info()
+ traceback.print_exc()
+ pdb.post_mortem(tb)
+ else:
+ raise
+ except Errors.WafError as e:
+ if Logs.verbose > 1:
+ Logs.pprint('RED', e.verbose_msg)
+ Logs.error(e.msg)
+ sys.exit(1)
+ except SystemExit:
+ raise
+ except Exception as e:
+ traceback.print_exc(file=sys.stdout)
+ sys.exit(2)
+ except KeyboardInterrupt:
+ Logs.pprint('RED', 'Interrupted')
+ sys.exit(68)
+
+def set_main_module(file_path):
+ """
+ Read the main wscript file into :py:const:`waflib.Context.Context.g_module` and
+ bind default functions such as ``init``, ``dist``, ``distclean`` if not defined.
+ Called by :py:func:`waflib.Scripting.waf_entry_point` during the initialization.
+
+ :param file_path: absolute path representing the top-level wscript file
+ :type file_path: string
+ """
+ Context.g_module = Context.load_module(file_path)
+ Context.g_module.root_path = file_path
+
+ # note: to register the module globally, use the following:
+ # sys.modules['wscript_main'] = g_module
+
+ def set_def(obj):
+ name = obj.__name__
+ if not name in Context.g_module.__dict__:
+ setattr(Context.g_module, name, obj)
+ for k in (dist, distclean, distcheck):
+ set_def(k)
+ # add dummy init and shutdown functions if they're not defined
+ if not 'init' in Context.g_module.__dict__:
+ Context.g_module.init = Utils.nada
+ if not 'shutdown' in Context.g_module.__dict__:
+ Context.g_module.shutdown = Utils.nada
+ if not 'options' in Context.g_module.__dict__:
+ Context.g_module.options = Utils.nada
+
+def parse_options():
+ """
+ Parses the command-line options and initialize the logging system.
+ Called by :py:func:`waflib.Scripting.waf_entry_point` during the initialization.
+ """
+ ctx = Context.create_context('options')
+ ctx.execute()
+ if not Options.commands:
+ if isinstance(default_cmd, list):
+ Options.commands.extend(default_cmd)
+ else:
+ Options.commands.append(default_cmd)
+ if Options.options.whelp:
+ ctx.parser.print_help()
+ sys.exit(0)
+
+def run_command(cmd_name):
+ """
+ Executes a single Waf command. Called by :py:func:`waflib.Scripting.run_commands`.
+
+ :param cmd_name: command to execute, like ``build``
+ :type cmd_name: string
+ """
+ ctx = Context.create_context(cmd_name)
+ ctx.log_timer = Utils.Timer()
+ ctx.options = Options.options # provided for convenience
+ ctx.cmd = cmd_name
+ try:
+ ctx.execute()
+ finally:
+ # Issue 1374
+ ctx.finalize()
+ return ctx
+
+def run_commands():
+ """
+ Execute the Waf commands that were given on the command-line, and the other options
+ Called by :py:func:`waflib.Scripting.waf_entry_point` during the initialization, and executed
+ after :py:func:`waflib.Scripting.parse_options`.
+ """
+ parse_options()
+ run_command('init')
+ while Options.commands:
+ cmd_name = Options.commands.pop(0)
+ ctx = run_command(cmd_name)
+ Logs.info('%r finished successfully (%s)', cmd_name, ctx.log_timer)
+ run_command('shutdown')
+
+###########################################################################################
+
+def distclean_dir(dirname):
+ """
+ Distclean function called in the particular case when::
+
+ top == out
+
+ :param dirname: absolute path of the folder to clean
+ :type dirname: string
+ """
+ for (root, dirs, files) in os.walk(dirname):
+ for f in files:
+ if f.endswith(('.o', '.moc', '.exe')):
+ fname = os.path.join(root, f)
+ try:
+ os.remove(fname)
+ except OSError:
+ Logs.warn('Could not remove %r', fname)
+
+ for x in (Context.DBFILE, 'config.log'):
+ try:
+ os.remove(x)
+ except OSError:
+ pass
+
+ try:
+ shutil.rmtree(Build.CACHE_DIR)
+ except OSError:
+ pass
+
+def distclean(ctx):
+ '''removes build folders and data'''
+
+ def remove_and_log(k, fun):
+ try:
+ fun(k)
+ except EnvironmentError as e:
+ if e.errno != errno.ENOENT:
+ Logs.warn('Could not remove %r', k)
+
+ # remove waf cache folders on the top-level
+ if not Options.commands:
+ for k in os.listdir('.'):
+ for x in '.waf-2 waf-2 .waf3-2 waf3-2'.split():
+ if k.startswith(x):
+ remove_and_log(k, shutil.rmtree)
+
+ # remove a build folder, if any
+ cur = '.'
+ if ctx.options.no_lock_in_top:
+ cur = ctx.options.out
+
+ try:
+ lst = os.listdir(cur)
+ except OSError:
+ Logs.warn('Could not read %r', cur)
+ return
+
+ if Options.lockfile in lst:
+ f = os.path.join(cur, Options.lockfile)
+ try:
+ env = ConfigSet.ConfigSet(f)
+ except EnvironmentError:
+ Logs.warn('Could not read %r', f)
+ return
+
+ if not env.out_dir or not env.top_dir:
+ Logs.warn('Invalid lock file %r', f)
+ return
+
+ if env.out_dir == env.top_dir:
+ distclean_dir(env.out_dir)
+ else:
+ remove_and_log(env.out_dir, shutil.rmtree)
+
+ for k in (env.out_dir, env.top_dir, env.run_dir):
+ p = os.path.join(k, Options.lockfile)
+ remove_and_log(p, os.remove)
+
+class Dist(Context.Context):
+ '''creates an archive containing the project source code'''
+ cmd = 'dist'
+ fun = 'dist'
+ algo = 'tar.bz2'
+ ext_algo = {}
+
+ def execute(self):
+ """
+ See :py:func:`waflib.Context.Context.execute`
+ """
+ self.recurse([os.path.dirname(Context.g_module.root_path)])
+ self.archive()
+
+ def archive(self):
+ """
+ Creates the source archive.
+ """
+ import tarfile
+
+ arch_name = self.get_arch_name()
+
+ try:
+ self.base_path
+ except AttributeError:
+ self.base_path = self.path
+
+ node = self.base_path.make_node(arch_name)
+ try:
+ node.delete()
+ except OSError:
+ pass
+
+ files = self.get_files()
+
+ if self.algo.startswith('tar.'):
+ tar = tarfile.open(node.abspath(), 'w:' + self.algo.replace('tar.', ''))
+
+ for x in files:
+ self.add_tar_file(x, tar)
+ tar.close()
+ elif self.algo == 'zip':
+ import zipfile
+ zip = zipfile.ZipFile(node.abspath(), 'w', compression=zipfile.ZIP_DEFLATED)
+
+ for x in files:
+ archive_name = self.get_base_name() + '/' + x.path_from(self.base_path)
+ zip.write(x.abspath(), archive_name, zipfile.ZIP_DEFLATED)
+ zip.close()
+ else:
+ self.fatal('Valid algo types are tar.bz2, tar.gz, tar.xz or zip')
+
+ try:
+ from hashlib import sha256
+ except ImportError:
+ digest = ''
+ else:
+ digest = ' (sha256=%r)' % sha256(node.read(flags='rb')).hexdigest()
+
+ Logs.info('New archive created: %s%s', self.arch_name, digest)
+
+ def get_tar_path(self, node):
+ """
+ Return the path to use for a node in the tar archive, the purpose of this
+ is to let subclases resolve symbolic links or to change file names
+
+ :return: absolute path
+ :rtype: string
+ """
+ return node.abspath()
+
+ def add_tar_file(self, x, tar):
+ """
+ Adds a file to the tar archive. Symlinks are not verified.
+
+ :param x: file path
+ :param tar: tar file object
+ """
+ p = self.get_tar_path(x)
+ tinfo = tar.gettarinfo(name=p, arcname=self.get_tar_prefix() + '/' + x.path_from(self.base_path))
+ tinfo.uid = 0
+ tinfo.gid = 0
+ tinfo.uname = 'root'
+ tinfo.gname = 'root'
+
+ if os.path.isfile(p):
+ with open(p, 'rb') as f:
+ tar.addfile(tinfo, fileobj=f)
+ else:
+ tar.addfile(tinfo)
+
+ def get_tar_prefix(self):
+ """
+ Returns the base path for files added into the archive tar file
+
+ :rtype: string
+ """
+ try:
+ return self.tar_prefix
+ except AttributeError:
+ return self.get_base_name()
+
+ def get_arch_name(self):
+ """
+ Returns the archive file name.
+ Set the attribute *arch_name* to change the default value::
+
+ def dist(ctx):
+ ctx.arch_name = 'ctx.tar.bz2'
+
+ :rtype: string
+ """
+ try:
+ self.arch_name
+ except AttributeError:
+ self.arch_name = self.get_base_name() + '.' + self.ext_algo.get(self.algo, self.algo)
+ return self.arch_name
+
+ def get_base_name(self):
+ """
+ Returns the default name of the main directory in the archive, which is set to *appname-version*.
+ Set the attribute *base_name* to change the default value::
+
+ def dist(ctx):
+ ctx.base_name = 'files'
+
+ :rtype: string
+ """
+ try:
+ self.base_name
+ except AttributeError:
+ appname = getattr(Context.g_module, Context.APPNAME, 'noname')
+ version = getattr(Context.g_module, Context.VERSION, '1.0')
+ self.base_name = appname + '-' + version
+ return self.base_name
+
+ def get_excl(self):
+ """
+ Returns the patterns to exclude for finding the files in the top-level directory.
+ Set the attribute *excl* to change the default value::
+
+ def dist(ctx):
+ ctx.excl = 'build **/*.o **/*.class'
+
+ :rtype: string
+ """
+ try:
+ return self.excl
+ except AttributeError:
+ self.excl = Node.exclude_regs + ' **/waf-2.* **/.waf-2.* **/waf3-2.* **/.waf3-2.* **/*~ **/*.rej **/*.orig **/*.pyc **/*.pyo **/*.bak **/*.swp **/.lock-w*'
+ if Context.out_dir:
+ nd = self.root.find_node(Context.out_dir)
+ if nd:
+ self.excl += ' ' + nd.path_from(self.base_path)
+ return self.excl
+
+ def get_files(self):
+ """
+ Files to package are searched automatically by :py:func:`waflib.Node.Node.ant_glob`.
+ Set *files* to prevent this behaviour::
+
+ def dist(ctx):
+ ctx.files = ctx.path.find_node('wscript')
+
+ Files are also searched from the directory 'base_path', to change it, set::
+
+ def dist(ctx):
+ ctx.base_path = path
+
+ :rtype: list of :py:class:`waflib.Node.Node`
+ """
+ try:
+ files = self.files
+ except AttributeError:
+ files = self.base_path.ant_glob('**/*', excl=self.get_excl())
+ return files
+
+def dist(ctx):
+ '''makes a tarball for redistributing the sources'''
+ pass
+
+class DistCheck(Dist):
+ """creates an archive with dist, then tries to build it"""
+ fun = 'distcheck'
+ cmd = 'distcheck'
+
+ def execute(self):
+ """
+ See :py:func:`waflib.Context.Context.execute`
+ """
+ self.recurse([os.path.dirname(Context.g_module.root_path)])
+ self.archive()
+ self.check()
+
+ def make_distcheck_cmd(self, tmpdir):
+ cfg = []
+ if Options.options.distcheck_args:
+ cfg = shlex.split(Options.options.distcheck_args)
+ else:
+ cfg = [x for x in sys.argv if x.startswith('-')]
+ cmd = [sys.executable, sys.argv[0], 'configure', 'build', 'install', 'uninstall', '--destdir=' + tmpdir] + cfg
+ return cmd
+
+ def check(self):
+ """
+ Creates the archive, uncompresses it and tries to build the project
+ """
+ import tempfile, tarfile
+
+ with tarfile.open(self.get_arch_name()) as t:
+ for x in t:
+ t.extract(x)
+
+ instdir = tempfile.mkdtemp('.inst', self.get_base_name())
+ cmd = self.make_distcheck_cmd(instdir)
+ ret = Utils.subprocess.Popen(cmd, cwd=self.get_base_name()).wait()
+ if ret:
+ raise Errors.WafError('distcheck failed with code %r' % ret)
+
+ if os.path.exists(instdir):
+ raise Errors.WafError('distcheck succeeded, but files were left in %s' % instdir)
+
+ shutil.rmtree(self.get_base_name())
+
+
+def distcheck(ctx):
+ '''checks if the project compiles (tarball from 'dist')'''
+ pass
+
+def autoconfigure(execute_method):
+ """
+ Decorator that enables context commands to run *configure* as needed.
+ """
+ def execute(self):
+ """
+ Wraps :py:func:`waflib.Context.Context.execute` on the context class
+ """
+ if not Configure.autoconfig:
+ return execute_method(self)
+
+ env = ConfigSet.ConfigSet()
+ do_config = False
+ try:
+ env.load(os.path.join(Context.top_dir, Options.lockfile))
+ except EnvironmentError:
+ Logs.warn('Configuring the project')
+ do_config = True
+ else:
+ if env.run_dir != Context.run_dir:
+ do_config = True
+ else:
+ h = 0
+ for f in env.files:
+ try:
+ h = Utils.h_list((h, Utils.readf(f, 'rb')))
+ except EnvironmentError:
+ do_config = True
+ break
+ else:
+ do_config = h != env.hash
+
+ if do_config:
+ cmd = env.config_cmd or 'configure'
+ if Configure.autoconfig == 'clobber':
+ tmp = Options.options.__dict__
+ launch_dir_tmp = Context.launch_dir
+ if env.options:
+ Options.options.__dict__ = env.options
+ Context.launch_dir = env.launch_dir
+ try:
+ run_command(cmd)
+ finally:
+ Options.options.__dict__ = tmp
+ Context.launch_dir = launch_dir_tmp
+ else:
+ run_command(cmd)
+ run_command(self.cmd)
+ else:
+ return execute_method(self)
+ return execute
+Build.BuildContext.execute = autoconfigure(Build.BuildContext.execute)
+
diff --git a/waflib/Task.py b/waflib/Task.py
new file mode 100644
index 0000000..cb49a73
--- /dev/null
+++ b/waflib/Task.py
@@ -0,0 +1,1406 @@
+#!/usr/bin/env python
+# encoding: utf-8
+# Thomas Nagy, 2005-2018 (ita)
+
+"""
+Tasks represent atomic operations such as processes.
+"""
+
+import os, re, sys, tempfile, traceback
+from waflib import Utils, Logs, Errors
+
+# task states
+NOT_RUN = 0
+"""The task was not executed yet"""
+
+MISSING = 1
+"""The task has been executed but the files have not been created"""
+
+CRASHED = 2
+"""The task execution returned a non-zero exit status"""
+
+EXCEPTION = 3
+"""An exception occurred in the task execution"""
+
+CANCELED = 4
+"""A dependency for the task is missing so it was cancelled"""
+
+SKIPPED = 8
+"""The task did not have to be executed"""
+
+SUCCESS = 9
+"""The task was successfully executed"""
+
+ASK_LATER = -1
+"""The task is not ready to be executed"""
+
+SKIP_ME = -2
+"""The task does not need to be executed"""
+
+RUN_ME = -3
+"""The task must be executed"""
+
+CANCEL_ME = -4
+"""The task cannot be executed because of a dependency problem"""
+
+COMPILE_TEMPLATE_SHELL = '''
+def f(tsk):
+ env = tsk.env
+ gen = tsk.generator
+ bld = gen.bld
+ cwdx = tsk.get_cwd()
+ p = env.get_flat
+ def to_list(xx):
+ if isinstance(xx, str): return [xx]
+ return xx
+ tsk.last_cmd = cmd = \'\'\' %s \'\'\' % s
+ return tsk.exec_command(cmd, cwd=cwdx, env=env.env or None)
+'''
+
+COMPILE_TEMPLATE_NOSHELL = '''
+def f(tsk):
+ env = tsk.env
+ gen = tsk.generator
+ bld = gen.bld
+ cwdx = tsk.get_cwd()
+ def to_list(xx):
+ if isinstance(xx, str): return [xx]
+ return xx
+ def merge(lst1, lst2):
+ if lst1 and lst2:
+ return lst1[:-1] + [lst1[-1] + lst2[0]] + lst2[1:]
+ return lst1 + lst2
+ lst = []
+ %s
+ if '' in lst:
+ lst = [x for x in lst if x]
+ tsk.last_cmd = lst
+ return tsk.exec_command(lst, cwd=cwdx, env=env.env or None)
+'''
+
+COMPILE_TEMPLATE_SIG_VARS = '''
+def f(tsk):
+ sig = tsk.generator.bld.hash_env_vars(tsk.env, tsk.vars)
+ tsk.m.update(sig)
+ env = tsk.env
+ gen = tsk.generator
+ bld = gen.bld
+ cwdx = tsk.get_cwd()
+ p = env.get_flat
+ buf = []
+ %s
+ tsk.m.update(repr(buf).encode())
+'''
+
+classes = {}
+"""
+The metaclass :py:class:`waflib.Task.store_task_type` stores all class tasks
+created by user scripts or Waf tools to this dict. It maps class names to class objects.
+"""
+
+class store_task_type(type):
+ """
+ Metaclass: store the task classes into the dict pointed by the
+ class attribute 'register' which defaults to :py:const:`waflib.Task.classes`,
+
+ The attribute 'run_str' is compiled into a method 'run' bound to the task class.
+ """
+ def __init__(cls, name, bases, dict):
+ super(store_task_type, cls).__init__(name, bases, dict)
+ name = cls.__name__
+
+ if name != 'evil' and name != 'Task':
+ if getattr(cls, 'run_str', None):
+ # if a string is provided, convert it to a method
+ (f, dvars) = compile_fun(cls.run_str, cls.shell)
+ cls.hcode = Utils.h_cmd(cls.run_str)
+ cls.orig_run_str = cls.run_str
+ # change the name of run_str or it is impossible to subclass with a function
+ cls.run_str = None
+ cls.run = f
+ # process variables
+ cls.vars = list(set(cls.vars + dvars))
+ cls.vars.sort()
+ if cls.vars:
+ fun = compile_sig_vars(cls.vars)
+ if fun:
+ cls.sig_vars = fun
+ elif getattr(cls, 'run', None) and not 'hcode' in cls.__dict__:
+ # getattr(cls, 'hcode') would look in the upper classes
+ cls.hcode = Utils.h_cmd(cls.run)
+
+ # be creative
+ getattr(cls, 'register', classes)[name] = cls
+
+evil = store_task_type('evil', (object,), {})
+"Base class provided to avoid writing a metaclass, so the code can run in python 2.6 and 3.x unmodified"
+
+class Task(evil):
+ """
+ Task objects represents actions to perform such as commands to execute by calling the `run` method.
+
+ Detecting when to execute a task occurs in the method :py:meth:`waflib.Task.Task.runnable_status`.
+
+ Detecting which tasks to execute is performed through a hash value returned by
+ :py:meth:`waflib.Task.Task.signature`. The task signature is persistent from build to build.
+ """
+ vars = []
+ """ConfigSet variables that should trigger a rebuild (class attribute used for :py:meth:`waflib.Task.Task.sig_vars`)"""
+
+ always_run = False
+ """Specify whether task instances must always be executed or not (class attribute)"""
+
+ shell = False
+ """Execute the command with the shell (class attribute)"""
+
+ color = 'GREEN'
+ """Color for the console display, see :py:const:`waflib.Logs.colors_lst`"""
+
+ ext_in = []
+ """File extensions that objects of this task class may use"""
+
+ ext_out = []
+ """File extensions that objects of this task class may create"""
+
+ before = []
+ """The instances of this class are executed before the instances of classes whose names are in this list"""
+
+ after = []
+ """The instances of this class are executed after the instances of classes whose names are in this list"""
+
+ hcode = Utils.SIG_NIL
+ """String representing an additional hash for the class representation"""
+
+ keep_last_cmd = False
+ """Whether to keep the last command executed on the instance after execution.
+ This may be useful for certain extensions but it can a lot of memory.
+ """
+
+ weight = 0
+ """Optional weight to tune the priority for task instances.
+ The higher, the earlier. The weight only applies to single task objects."""
+
+ tree_weight = 0
+ """Optional weight to tune the priority of task instances and whole subtrees.
+ The higher, the earlier."""
+
+ prio_order = 0
+ """Priority order set by the scheduler on instances during the build phase.
+ You most likely do not need to set it.
+ """
+
+ __slots__ = ('hasrun', 'generator', 'env', 'inputs', 'outputs', 'dep_nodes', 'run_after')
+
+ def __init__(self, *k, **kw):
+ self.hasrun = NOT_RUN
+ try:
+ self.generator = kw['generator']
+ except KeyError:
+ self.generator = self
+
+ self.env = kw['env']
+ """:py:class:`waflib.ConfigSet.ConfigSet` object (make sure to provide one)"""
+
+ self.inputs = []
+ """List of input nodes, which represent the files used by the task instance"""
+
+ self.outputs = []
+ """List of output nodes, which represent the files created by the task instance"""
+
+ self.dep_nodes = []
+ """List of additional nodes to depend on"""
+
+ self.run_after = set()
+ """Set of tasks that must be executed before this one"""
+
+ def __lt__(self, other):
+ return self.priority() > other.priority()
+ def __le__(self, other):
+ return self.priority() >= other.priority()
+ def __gt__(self, other):
+ return self.priority() < other.priority()
+ def __ge__(self, other):
+ return self.priority() <= other.priority()
+
+ def get_cwd(self):
+ """
+ :return: current working directory
+ :rtype: :py:class:`waflib.Node.Node`
+ """
+ bld = self.generator.bld
+ ret = getattr(self, 'cwd', None) or getattr(bld, 'cwd', bld.bldnode)
+ if isinstance(ret, str):
+ if os.path.isabs(ret):
+ ret = bld.root.make_node(ret)
+ else:
+ ret = self.generator.path.make_node(ret)
+ return ret
+
+ def quote_flag(self, x):
+ """
+ Surround a process argument by quotes so that a list of arguments can be written to a file
+
+ :param x: flag
+ :type x: string
+ :return: quoted flag
+ :rtype: string
+ """
+ old = x
+ if '\\' in x:
+ x = x.replace('\\', '\\\\')
+ if '"' in x:
+ x = x.replace('"', '\\"')
+ if old != x or ' ' in x or '\t' in x or "'" in x:
+ x = '"%s"' % x
+ return x
+
+ def priority(self):
+ """
+ Priority of execution; the higher, the earlier
+
+ :return: the priority value
+ :rtype: a tuple of numeric values
+ """
+ return (self.weight + self.prio_order, - getattr(self.generator, 'tg_idx_count', 0))
+
+ def split_argfile(self, cmd):
+ """
+ Splits a list of process commands into the executable part and its list of arguments
+
+ :return: a tuple containing the executable first and then the rest of arguments
+ :rtype: tuple
+ """
+ return ([cmd[0]], [self.quote_flag(x) for x in cmd[1:]])
+
+ def exec_command(self, cmd, **kw):
+ """
+ Wrapper for :py:meth:`waflib.Context.Context.exec_command`.
+ This version set the current working directory (``build.variant_dir``),
+ applies PATH settings (if self.env.PATH is provided), and can run long
+ commands through a temporary ``@argfile``.
+
+ :param cmd: process command to execute
+ :type cmd: list of string (best) or string (process will use a shell)
+ :return: the return code
+ :rtype: int
+
+ Optional parameters:
+
+ #. cwd: current working directory (Node or string)
+ #. stdout: set to None to prevent waf from capturing the process standard output
+ #. stderr: set to None to prevent waf from capturing the process standard error
+ #. timeout: timeout value (Python 3)
+ """
+ if not 'cwd' in kw:
+ kw['cwd'] = self.get_cwd()
+
+ if hasattr(self, 'timeout'):
+ kw['timeout'] = self.timeout
+
+ if self.env.PATH:
+ env = kw['env'] = dict(kw.get('env') or self.env.env or os.environ)
+ env['PATH'] = self.env.PATH if isinstance(self.env.PATH, str) else os.pathsep.join(self.env.PATH)
+
+ if hasattr(self, 'stdout'):
+ kw['stdout'] = self.stdout
+ if hasattr(self, 'stderr'):
+ kw['stderr'] = self.stderr
+
+ if not isinstance(cmd, str):
+ if Utils.is_win32:
+ # win32 compares the resulting length http://support.microsoft.com/kb/830473
+ too_long = sum([len(arg) for arg in cmd]) + len(cmd) > 8192
+ else:
+ # non-win32 counts the amount of arguments (200k)
+ too_long = len(cmd) > 200000
+
+ if too_long and getattr(self, 'allow_argsfile', True):
+ # Shunt arguments to a temporary file if the command is too long.
+ cmd, args = self.split_argfile(cmd)
+ try:
+ (fd, tmp) = tempfile.mkstemp()
+ os.write(fd, '\r\n'.join(args).encode())
+ os.close(fd)
+ if Logs.verbose:
+ Logs.debug('argfile: @%r -> %r', tmp, args)
+ return self.generator.bld.exec_command(cmd + ['@' + tmp], **kw)
+ finally:
+ try:
+ os.remove(tmp)
+ except OSError:
+ # anti-virus and indexers can keep files open -_-
+ pass
+ return self.generator.bld.exec_command(cmd, **kw)
+
+ def process(self):
+ """
+ Runs the task and handles errors
+
+ :return: 0 or None if everything is fine
+ :rtype: integer
+ """
+ # remove the task signature immediately before it is executed
+ # so that the task will be executed again in case of failure
+ try:
+ del self.generator.bld.task_sigs[self.uid()]
+ except KeyError:
+ pass
+
+ try:
+ ret = self.run()
+ except Exception:
+ self.err_msg = traceback.format_exc()
+ self.hasrun = EXCEPTION
+ else:
+ if ret:
+ self.err_code = ret
+ self.hasrun = CRASHED
+ else:
+ try:
+ self.post_run()
+ except Errors.WafError:
+ pass
+ except Exception:
+ self.err_msg = traceback.format_exc()
+ self.hasrun = EXCEPTION
+ else:
+ self.hasrun = SUCCESS
+
+ if self.hasrun != SUCCESS and self.scan:
+ # rescan dependencies on next run
+ try:
+ del self.generator.bld.imp_sigs[self.uid()]
+ except KeyError:
+ pass
+
+ def log_display(self, bld):
+ "Writes the execution status on the context logger"
+ if self.generator.bld.progress_bar == 3:
+ return
+
+ s = self.display()
+ if s:
+ if bld.logger:
+ logger = bld.logger
+ else:
+ logger = Logs
+
+ if self.generator.bld.progress_bar == 1:
+ c1 = Logs.colors.cursor_off
+ c2 = Logs.colors.cursor_on
+ logger.info(s, extra={'stream': sys.stderr, 'terminator':'', 'c1': c1, 'c2' : c2})
+ else:
+ logger.info(s, extra={'terminator':'', 'c1': '', 'c2' : ''})
+
+ def display(self):
+ """
+ Returns an execution status for the console, the progress bar, or the IDE output.
+
+ :rtype: string
+ """
+ col1 = Logs.colors(self.color)
+ col2 = Logs.colors.NORMAL
+ master = self.generator.bld.producer
+
+ def cur():
+ # the current task position, computed as late as possible
+ return master.processed - master.ready.qsize()
+
+ if self.generator.bld.progress_bar == 1:
+ return self.generator.bld.progress_line(cur(), master.total, col1, col2)
+
+ if self.generator.bld.progress_bar == 2:
+ ela = str(self.generator.bld.timer)
+ try:
+ ins = ','.join([n.name for n in self.inputs])
+ except AttributeError:
+ ins = ''
+ try:
+ outs = ','.join([n.name for n in self.outputs])
+ except AttributeError:
+ outs = ''
+ return '|Total %s|Current %s|Inputs %s|Outputs %s|Time %s|\n' % (master.total, cur(), ins, outs, ela)
+
+ s = str(self)
+ if not s:
+ return None
+
+ total = master.total
+ n = len(str(total))
+ fs = '[%%%dd/%%%dd] %%s%%s%%s%%s\n' % (n, n)
+ kw = self.keyword()
+ if kw:
+ kw += ' '
+ return fs % (cur(), total, kw, col1, s, col2)
+
+ def hash_constraints(self):
+ """
+ Identifies a task type for all the constraints relevant for the scheduler: precedence, file production
+
+ :return: a hash value
+ :rtype: string
+ """
+ return (tuple(self.before), tuple(self.after), tuple(self.ext_in), tuple(self.ext_out), self.__class__.__name__, self.hcode)
+
+ def format_error(self):
+ """
+ Returns an error message to display the build failure reasons
+
+ :rtype: string
+ """
+ if Logs.verbose:
+ msg = ': %r\n%r' % (self, getattr(self, 'last_cmd', ''))
+ else:
+ msg = ' (run with -v to display more information)'
+ name = getattr(self.generator, 'name', '')
+ if getattr(self, "err_msg", None):
+ return self.err_msg
+ elif not self.hasrun:
+ return 'task in %r was not executed for some reason: %r' % (name, self)
+ elif self.hasrun == CRASHED:
+ try:
+ return ' -> task in %r failed with exit status %r%s' % (name, self.err_code, msg)
+ except AttributeError:
+ return ' -> task in %r failed%s' % (name, msg)
+ elif self.hasrun == MISSING:
+ return ' -> missing files in %r%s' % (name, msg)
+ elif self.hasrun == CANCELED:
+ return ' -> %r canceled because of missing dependencies' % name
+ else:
+ return 'invalid status for task in %r: %r' % (name, self.hasrun)
+
+ def colon(self, var1, var2):
+ """
+ Enable scriptlet expressions of the form ${FOO_ST:FOO}
+ If the first variable (FOO_ST) is empty, then an empty list is returned
+
+ The results will be slightly different if FOO_ST is a list, for example::
+
+ env.FOO = ['p1', 'p2']
+ env.FOO_ST = '-I%s'
+ # ${FOO_ST:FOO} returns
+ ['-Ip1', '-Ip2']
+
+ env.FOO_ST = ['-a', '-b']
+ # ${FOO_ST:FOO} returns
+ ['-a', '-b', 'p1', '-a', '-b', 'p2']
+ """
+ tmp = self.env[var1]
+ if not tmp:
+ return []
+
+ if isinstance(var2, str):
+ it = self.env[var2]
+ else:
+ it = var2
+ if isinstance(tmp, str):
+ return [tmp % x for x in it]
+ else:
+ lst = []
+ for y in it:
+ lst.extend(tmp)
+ lst.append(y)
+ return lst
+
+ def __str__(self):
+ "string to display to the user"
+ name = self.__class__.__name__
+ if self.outputs:
+ if name.endswith(('lib', 'program')) or not self.inputs:
+ node = self.outputs[0]
+ return node.path_from(node.ctx.launch_node())
+ if not (self.inputs or self.outputs):
+ return self.__class__.__name__
+ if len(self.inputs) == 1:
+ node = self.inputs[0]
+ return node.path_from(node.ctx.launch_node())
+
+ src_str = ' '.join([a.path_from(a.ctx.launch_node()) for a in self.inputs])
+ tgt_str = ' '.join([a.path_from(a.ctx.launch_node()) for a in self.outputs])
+ if self.outputs:
+ sep = ' -> '
+ else:
+ sep = ''
+ return '%s: %s%s%s' % (self.__class__.__name__, src_str, sep, tgt_str)
+
+ def keyword(self):
+ "Display keyword used to prettify the console outputs"
+ name = self.__class__.__name__
+ if name.endswith(('lib', 'program')):
+ return 'Linking'
+ if len(self.inputs) == 1 and len(self.outputs) == 1:
+ return 'Compiling'
+ if not self.inputs:
+ if self.outputs:
+ return 'Creating'
+ else:
+ return 'Running'
+ return 'Processing'
+
+ def __repr__(self):
+ "for debugging purposes"
+ try:
+ ins = ",".join([x.name for x in self.inputs])
+ outs = ",".join([x.name for x in self.outputs])
+ except AttributeError:
+ ins = ",".join([str(x) for x in self.inputs])
+ outs = ",".join([str(x) for x in self.outputs])
+ return "".join(['\n\t{task %r: ' % id(self), self.__class__.__name__, " ", ins, " -> ", outs, '}'])
+
+ def uid(self):
+ """
+ Returns an identifier used to determine if tasks are up-to-date. Since the
+ identifier will be stored between executions, it must be:
+
+ - unique for a task: no two tasks return the same value (for a given build context)
+ - the same for a given task instance
+
+ By default, the node paths, the class name, and the function are used
+ as inputs to compute a hash.
+
+ The pointer to the object (python built-in 'id') will change between build executions,
+ and must be avoided in such hashes.
+
+ :return: hash value
+ :rtype: string
+ """
+ try:
+ return self.uid_
+ except AttributeError:
+ m = Utils.md5(self.__class__.__name__)
+ up = m.update
+ for x in self.inputs + self.outputs:
+ up(x.abspath())
+ self.uid_ = m.digest()
+ return self.uid_
+
+ def set_inputs(self, inp):
+ """
+ Appends the nodes to the *inputs* list
+
+ :param inp: input nodes
+ :type inp: node or list of nodes
+ """
+ if isinstance(inp, list):
+ self.inputs += inp
+ else:
+ self.inputs.append(inp)
+
+ def set_outputs(self, out):
+ """
+ Appends the nodes to the *outputs* list
+
+ :param out: output nodes
+ :type out: node or list of nodes
+ """
+ if isinstance(out, list):
+ self.outputs += out
+ else:
+ self.outputs.append(out)
+
+ def set_run_after(self, task):
+ """
+ Run this task only after the given *task*.
+
+ Calling this method from :py:meth:`waflib.Task.Task.runnable_status` may cause
+ build deadlocks; see :py:meth:`waflib.Tools.fc.fc.runnable_status` for details.
+
+ :param task: task
+ :type task: :py:class:`waflib.Task.Task`
+ """
+ assert isinstance(task, Task)
+ self.run_after.add(task)
+
+ def signature(self):
+ """
+ Task signatures are stored between build executions, they are use to track the changes
+ made to the input nodes (not to the outputs!). The signature hashes data from various sources:
+
+ * explicit dependencies: files listed in the inputs (list of node objects) :py:meth:`waflib.Task.Task.sig_explicit_deps`
+ * implicit dependencies: list of nodes returned by scanner methods (when present) :py:meth:`waflib.Task.Task.sig_implicit_deps`
+ * hashed data: variables/values read from task.vars/task.env :py:meth:`waflib.Task.Task.sig_vars`
+
+ If the signature is expected to give a different result, clear the cache kept in ``self.cache_sig``::
+
+ from waflib import Task
+ class cls(Task.Task):
+ def signature(self):
+ sig = super(Task.Task, self).signature()
+ delattr(self, 'cache_sig')
+ return super(Task.Task, self).signature()
+
+ :return: the signature value
+ :rtype: string or bytes
+ """
+ try:
+ return self.cache_sig
+ except AttributeError:
+ pass
+
+ self.m = Utils.md5(self.hcode)
+
+ # explicit deps
+ self.sig_explicit_deps()
+
+ # env vars
+ self.sig_vars()
+
+ # implicit deps / scanner results
+ if self.scan:
+ try:
+ self.sig_implicit_deps()
+ except Errors.TaskRescan:
+ return self.signature()
+
+ ret = self.cache_sig = self.m.digest()
+ return ret
+
+ def runnable_status(self):
+ """
+ Returns the Task status
+
+ :return: a task state in :py:const:`waflib.Task.RUN_ME`,
+ :py:const:`waflib.Task.SKIP_ME`, :py:const:`waflib.Task.CANCEL_ME` or :py:const:`waflib.Task.ASK_LATER`.
+ :rtype: int
+ """
+ bld = self.generator.bld
+ if bld.is_install < 0:
+ return SKIP_ME
+
+ for t in self.run_after:
+ if not t.hasrun:
+ return ASK_LATER
+ elif t.hasrun < SKIPPED:
+ # a dependency has an error
+ return CANCEL_ME
+
+ # first compute the signature
+ try:
+ new_sig = self.signature()
+ except Errors.TaskNotReady:
+ return ASK_LATER
+
+ # compare the signature to a signature computed previously
+ key = self.uid()
+ try:
+ prev_sig = bld.task_sigs[key]
+ except KeyError:
+ Logs.debug('task: task %r must run: it was never run before or the task code changed', self)
+ return RUN_ME
+
+ if new_sig != prev_sig:
+ Logs.debug('task: task %r must run: the task signature changed', self)
+ return RUN_ME
+
+ # compare the signatures of the outputs
+ for node in self.outputs:
+ sig = bld.node_sigs.get(node)
+ if not sig:
+ Logs.debug('task: task %r must run: an output node has no signature', self)
+ return RUN_ME
+ if sig != key:
+ Logs.debug('task: task %r must run: an output node was produced by another task', self)
+ return RUN_ME
+ if not node.exists():
+ Logs.debug('task: task %r must run: an output node does not exist', self)
+ return RUN_ME
+
+ return (self.always_run and RUN_ME) or SKIP_ME
+
+ def post_run(self):
+ """
+ Called after successful execution to record that the task has run by
+ updating the entry in :py:attr:`waflib.Build.BuildContext.task_sigs`.
+ """
+ bld = self.generator.bld
+ for node in self.outputs:
+ if not node.exists():
+ self.hasrun = MISSING
+ self.err_msg = '-> missing file: %r' % node.abspath()
+ raise Errors.WafError(self.err_msg)
+ bld.node_sigs[node] = self.uid() # make sure this task produced the files in question
+ bld.task_sigs[self.uid()] = self.signature()
+ if not self.keep_last_cmd:
+ try:
+ del self.last_cmd
+ except AttributeError:
+ pass
+
+ def sig_explicit_deps(self):
+ """
+ Used by :py:meth:`waflib.Task.Task.signature`; it hashes :py:attr:`waflib.Task.Task.inputs`
+ and :py:attr:`waflib.Task.Task.dep_nodes` signatures.
+ """
+ bld = self.generator.bld
+ upd = self.m.update
+
+ # the inputs
+ for x in self.inputs + self.dep_nodes:
+ upd(x.get_bld_sig())
+
+ # manual dependencies, they can slow down the builds
+ if bld.deps_man:
+ additional_deps = bld.deps_man
+ for x in self.inputs + self.outputs:
+ try:
+ d = additional_deps[x]
+ except KeyError:
+ continue
+
+ for v in d:
+ try:
+ v = v.get_bld_sig()
+ except AttributeError:
+ if hasattr(v, '__call__'):
+ v = v() # dependency is a function, call it
+ upd(v)
+
+ def sig_deep_inputs(self):
+ """
+ Enable rebuilds on input files task signatures. Not used by default.
+
+ Example: hashes of output programs can be unchanged after being re-linked,
+ despite the libraries being different. This method can thus prevent stale unit test
+ results (waf_unit_test.py).
+
+ Hashing input file timestamps is another possibility for the implementation.
+ This may cause unnecessary rebuilds when input tasks are frequently executed.
+ Here is an implementation example::
+
+ lst = []
+ for node in self.inputs + self.dep_nodes:
+ st = os.stat(node.abspath())
+ lst.append(st.st_mtime)
+ lst.append(st.st_size)
+ self.m.update(Utils.h_list(lst))
+
+ The downside of the implementation is that it absolutely requires all build directory
+ files to be declared within the current build.
+ """
+ bld = self.generator.bld
+ lst = [bld.task_sigs[bld.node_sigs[node]] for node in (self.inputs + self.dep_nodes) if node.is_bld()]
+ self.m.update(Utils.h_list(lst))
+
+ def sig_vars(self):
+ """
+ Used by :py:meth:`waflib.Task.Task.signature`; it hashes :py:attr:`waflib.Task.Task.env` variables/values
+ When overriding this method, and if scriptlet expressions are used, make sure to follow
+ the code in :py:meth:`waflib.Task.Task.compile_sig_vars` to enable dependencies on scriptlet results.
+
+ This method may be replaced on subclasses by the metaclass to force dependencies on scriptlet code.
+ """
+ sig = self.generator.bld.hash_env_vars(self.env, self.vars)
+ self.m.update(sig)
+
+ scan = None
+ """
+ This method, when provided, returns a tuple containing:
+
+ * a list of nodes corresponding to real files
+ * a list of names for files not found in path_lst
+
+ For example::
+
+ from waflib.Task import Task
+ class mytask(Task):
+ def scan(self, node):
+ return ([], [])
+
+ The first and second lists in the tuple are stored in :py:attr:`waflib.Build.BuildContext.node_deps` and
+ :py:attr:`waflib.Build.BuildContext.raw_deps` respectively.
+ """
+
+ def sig_implicit_deps(self):
+ """
+ Used by :py:meth:`waflib.Task.Task.signature`; it hashes node signatures
+ obtained by scanning for dependencies (:py:meth:`waflib.Task.Task.scan`).
+
+ The exception :py:class:`waflib.Errors.TaskRescan` is thrown
+ when a file has changed. In this case, the method :py:meth:`waflib.Task.Task.signature` is called
+ once again, and return here to call :py:meth:`waflib.Task.Task.scan` and searching for dependencies.
+ """
+ bld = self.generator.bld
+
+ # get the task signatures from previous runs
+ key = self.uid()
+ prev = bld.imp_sigs.get(key, [])
+
+ # for issue #379
+ if prev:
+ try:
+ if prev == self.compute_sig_implicit_deps():
+ return prev
+ except Errors.TaskNotReady:
+ raise
+ except EnvironmentError:
+ # when a file was renamed, remove the stale nodes (headers in folders without source files)
+ # this will break the order calculation for headers created during the build in the source directory (should be uncommon)
+ # the behaviour will differ when top != out
+ for x in bld.node_deps.get(self.uid(), []):
+ if not x.is_bld() and not x.exists():
+ try:
+ del x.parent.children[x.name]
+ except KeyError:
+ pass
+ del bld.imp_sigs[key]
+ raise Errors.TaskRescan('rescan')
+
+ # no previous run or the signature of the dependencies has changed, rescan the dependencies
+ (bld.node_deps[key], bld.raw_deps[key]) = self.scan()
+ if Logs.verbose:
+ Logs.debug('deps: scanner for %s: %r; unresolved: %r', self, bld.node_deps[key], bld.raw_deps[key])
+
+ # recompute the signature and return it
+ try:
+ bld.imp_sigs[key] = self.compute_sig_implicit_deps()
+ except EnvironmentError:
+ for k in bld.node_deps.get(self.uid(), []):
+ if not k.exists():
+ Logs.warn('Dependency %r for %r is missing: check the task declaration and the build order!', k, self)
+ raise
+
+ def compute_sig_implicit_deps(self):
+ """
+ Used by :py:meth:`waflib.Task.Task.sig_implicit_deps` for computing the actual hash of the
+ :py:class:`waflib.Node.Node` returned by the scanner.
+
+ :return: a hash value for the implicit dependencies
+ :rtype: string or bytes
+ """
+ upd = self.m.update
+ self.are_implicit_nodes_ready()
+
+ # scanner returns a node that does not have a signature
+ # just *ignore* the error and let them figure out from the compiler output
+ # waf -k behaviour
+ for k in self.generator.bld.node_deps.get(self.uid(), []):
+ upd(k.get_bld_sig())
+ return self.m.digest()
+
+ def are_implicit_nodes_ready(self):
+ """
+ For each node returned by the scanner, see if there is a task that creates it,
+ and infer the build order
+
+ This has a low performance impact on null builds (1.86s->1.66s) thanks to caching (28s->1.86s)
+ """
+ bld = self.generator.bld
+ try:
+ cache = bld.dct_implicit_nodes
+ except AttributeError:
+ bld.dct_implicit_nodes = cache = {}
+
+ # one cache per build group
+ try:
+ dct = cache[bld.current_group]
+ except KeyError:
+ dct = cache[bld.current_group] = {}
+ for tsk in bld.cur_tasks:
+ for x in tsk.outputs:
+ dct[x] = tsk
+
+ modified = False
+ for x in bld.node_deps.get(self.uid(), []):
+ if x in dct:
+ self.run_after.add(dct[x])
+ modified = True
+
+ if modified:
+ for tsk in self.run_after:
+ if not tsk.hasrun:
+ #print "task is not ready..."
+ raise Errors.TaskNotReady('not ready')
+if sys.hexversion > 0x3000000:
+ def uid(self):
+ try:
+ return self.uid_
+ except AttributeError:
+ m = Utils.md5(self.__class__.__name__.encode('latin-1', 'xmlcharrefreplace'))
+ up = m.update
+ for x in self.inputs + self.outputs:
+ up(x.abspath().encode('latin-1', 'xmlcharrefreplace'))
+ self.uid_ = m.digest()
+ return self.uid_
+ uid.__doc__ = Task.uid.__doc__
+ Task.uid = uid
+
+def is_before(t1, t2):
+ """
+ Returns a non-zero value if task t1 is to be executed before task t2::
+
+ t1.ext_out = '.h'
+ t2.ext_in = '.h'
+ t2.after = ['t1']
+ t1.before = ['t2']
+ waflib.Task.is_before(t1, t2) # True
+
+ :param t1: Task object
+ :type t1: :py:class:`waflib.Task.Task`
+ :param t2: Task object
+ :type t2: :py:class:`waflib.Task.Task`
+ """
+ to_list = Utils.to_list
+ for k in to_list(t2.ext_in):
+ if k in to_list(t1.ext_out):
+ return 1
+
+ if t1.__class__.__name__ in to_list(t2.after):
+ return 1
+
+ if t2.__class__.__name__ in to_list(t1.before):
+ return 1
+
+ return 0
+
+def set_file_constraints(tasks):
+ """
+ Updates the ``run_after`` attribute of all tasks based on the task inputs and outputs
+
+ :param tasks: tasks
+ :type tasks: list of :py:class:`waflib.Task.Task`
+ """
+ ins = Utils.defaultdict(set)
+ outs = Utils.defaultdict(set)
+ for x in tasks:
+ for a in x.inputs:
+ ins[a].add(x)
+ for a in x.dep_nodes:
+ ins[a].add(x)
+ for a in x.outputs:
+ outs[a].add(x)
+
+ links = set(ins.keys()).intersection(outs.keys())
+ for k in links:
+ for a in ins[k]:
+ a.run_after.update(outs[k])
+
+
+class TaskGroup(object):
+ """
+ Wrap nxm task order constraints into a single object
+ to prevent the creation of large list/set objects
+
+ This is an optimization
+ """
+ def __init__(self, prev, next):
+ self.prev = prev
+ self.next = next
+ self.done = False
+
+ def get_hasrun(self):
+ for k in self.prev:
+ if not k.hasrun:
+ return NOT_RUN
+ return SUCCESS
+
+ hasrun = property(get_hasrun, None)
+
+def set_precedence_constraints(tasks):
+ """
+ Updates the ``run_after`` attribute of all tasks based on the after/before/ext_out/ext_in attributes
+
+ :param tasks: tasks
+ :type tasks: list of :py:class:`waflib.Task.Task`
+ """
+ cstr_groups = Utils.defaultdict(list)
+ for x in tasks:
+ h = x.hash_constraints()
+ cstr_groups[h].append(x)
+
+ keys = list(cstr_groups.keys())
+ maxi = len(keys)
+
+ # this list should be short
+ for i in range(maxi):
+ t1 = cstr_groups[keys[i]][0]
+ for j in range(i + 1, maxi):
+ t2 = cstr_groups[keys[j]][0]
+
+ # add the constraints based on the comparisons
+ if is_before(t1, t2):
+ a = i
+ b = j
+ elif is_before(t2, t1):
+ a = j
+ b = i
+ else:
+ continue
+
+ a = cstr_groups[keys[a]]
+ b = cstr_groups[keys[b]]
+
+ if len(a) < 2 or len(b) < 2:
+ for x in b:
+ x.run_after.update(a)
+ else:
+ group = TaskGroup(set(a), set(b))
+ for x in b:
+ x.run_after.add(group)
+
+def funex(c):
+ """
+ Compiles a scriptlet expression into a Python function
+
+ :param c: function to compile
+ :type c: string
+ :return: the function 'f' declared in the input string
+ :rtype: function
+ """
+ dc = {}
+ exec(c, dc)
+ return dc['f']
+
+re_cond = re.compile(r'(?P<var>\w+)|(?P<or>\|)|(?P<and>&)')
+re_novar = re.compile(r'^(SRC|TGT)\W+.*?$')
+reg_act = re.compile(r'(?P<backslash>\\)|(?P<dollar>\$\$)|(?P<subst>\$\{(?P<var>\w+)(?P<code>.*?)\})', re.M)
+def compile_fun_shell(line):
+ """
+ Creates a compiled function to execute a process through a sub-shell
+ """
+ extr = []
+ def repl(match):
+ g = match.group
+ if g('dollar'):
+ return "$"
+ elif g('backslash'):
+ return '\\\\'
+ elif g('subst'):
+ extr.append((g('var'), g('code')))
+ return "%s"
+ return None
+ line = reg_act.sub(repl, line) or line
+ dvars = []
+ def add_dvar(x):
+ if x not in dvars:
+ dvars.append(x)
+
+ def replc(m):
+ # performs substitutions and populates dvars
+ if m.group('and'):
+ return ' and '
+ elif m.group('or'):
+ return ' or '
+ else:
+ x = m.group('var')
+ add_dvar(x)
+ return 'env[%r]' % x
+
+ parm = []
+ app = parm.append
+ for (var, meth) in extr:
+ if var == 'SRC':
+ if meth:
+ app('tsk.inputs%s' % meth)
+ else:
+ app('" ".join([a.path_from(cwdx) for a in tsk.inputs])')
+ elif var == 'TGT':
+ if meth:
+ app('tsk.outputs%s' % meth)
+ else:
+ app('" ".join([a.path_from(cwdx) for a in tsk.outputs])')
+ elif meth:
+ if meth.startswith(':'):
+ add_dvar(var)
+ m = meth[1:]
+ if m == 'SRC':
+ m = '[a.path_from(cwdx) for a in tsk.inputs]'
+ elif m == 'TGT':
+ m = '[a.path_from(cwdx) for a in tsk.outputs]'
+ elif re_novar.match(m):
+ m = '[tsk.inputs%s]' % m[3:]
+ elif re_novar.match(m):
+ m = '[tsk.outputs%s]' % m[3:]
+ else:
+ add_dvar(m)
+ if m[:3] not in ('tsk', 'gen', 'bld'):
+ m = '%r' % m
+ app('" ".join(tsk.colon(%r, %s))' % (var, m))
+ elif meth.startswith('?'):
+ # In A?B|C output env.A if one of env.B or env.C is non-empty
+ expr = re_cond.sub(replc, meth[1:])
+ app('p(%r) if (%s) else ""' % (var, expr))
+ else:
+ call = '%s%s' % (var, meth)
+ add_dvar(call)
+ app(call)
+ else:
+ add_dvar(var)
+ app("p('%s')" % var)
+ if parm:
+ parm = "%% (%s) " % (',\n\t\t'.join(parm))
+ else:
+ parm = ''
+
+ c = COMPILE_TEMPLATE_SHELL % (line, parm)
+ Logs.debug('action: %s', c.strip().splitlines())
+ return (funex(c), dvars)
+
+reg_act_noshell = re.compile(r"(?P<space>\s+)|(?P<subst>\$\{(?P<var>\w+)(?P<code>.*?)\})|(?P<text>([^$ \t\n\r\f\v]|\$\$)+)", re.M)
+def compile_fun_noshell(line):
+ """
+ Creates a compiled function to execute a process without a sub-shell
+ """
+ buf = []
+ dvars = []
+ merge = False
+ app = buf.append
+
+ def add_dvar(x):
+ if x not in dvars:
+ dvars.append(x)
+
+ def replc(m):
+ # performs substitutions and populates dvars
+ if m.group('and'):
+ return ' and '
+ elif m.group('or'):
+ return ' or '
+ else:
+ x = m.group('var')
+ add_dvar(x)
+ return 'env[%r]' % x
+
+ for m in reg_act_noshell.finditer(line):
+ if m.group('space'):
+ merge = False
+ continue
+ elif m.group('text'):
+ app('[%r]' % m.group('text').replace('$$', '$'))
+ elif m.group('subst'):
+ var = m.group('var')
+ code = m.group('code')
+ if var == 'SRC':
+ if code:
+ app('[tsk.inputs%s]' % code)
+ else:
+ app('[a.path_from(cwdx) for a in tsk.inputs]')
+ elif var == 'TGT':
+ if code:
+ app('[tsk.outputs%s]' % code)
+ else:
+ app('[a.path_from(cwdx) for a in tsk.outputs]')
+ elif code:
+ if code.startswith(':'):
+ # a composed variable ${FOO:OUT}
+ add_dvar(var)
+ m = code[1:]
+ if m == 'SRC':
+ m = '[a.path_from(cwdx) for a in tsk.inputs]'
+ elif m == 'TGT':
+ m = '[a.path_from(cwdx) for a in tsk.outputs]'
+ elif re_novar.match(m):
+ m = '[tsk.inputs%s]' % m[3:]
+ elif re_novar.match(m):
+ m = '[tsk.outputs%s]' % m[3:]
+ else:
+ add_dvar(m)
+ if m[:3] not in ('tsk', 'gen', 'bld'):
+ m = '%r' % m
+ app('tsk.colon(%r, %s)' % (var, m))
+ elif code.startswith('?'):
+ # In A?B|C output env.A if one of env.B or env.C is non-empty
+ expr = re_cond.sub(replc, code[1:])
+ app('to_list(env[%r] if (%s) else [])' % (var, expr))
+ else:
+ # plain code such as ${tsk.inputs[0].abspath()}
+ call = '%s%s' % (var, code)
+ add_dvar(call)
+ app('to_list(%s)' % call)
+ else:
+ # a plain variable such as # a plain variable like ${AR}
+ app('to_list(env[%r])' % var)
+ add_dvar(var)
+ if merge:
+ tmp = 'merge(%s, %s)' % (buf[-2], buf[-1])
+ del buf[-1]
+ buf[-1] = tmp
+ merge = True # next turn
+
+ buf = ['lst.extend(%s)' % x for x in buf]
+ fun = COMPILE_TEMPLATE_NOSHELL % "\n\t".join(buf)
+ Logs.debug('action: %s', fun.strip().splitlines())
+ return (funex(fun), dvars)
+
+def compile_fun(line, shell=False):
+ """
+ Parses a string expression such as '${CC} ${SRC} -o ${TGT}' and returns a pair containing:
+
+ * The function created (compiled) for use as :py:meth:`waflib.Task.Task.run`
+ * The list of variables that must cause rebuilds when *env* data is modified
+
+ for example::
+
+ from waflib.Task import compile_fun
+ compile_fun('cxx', '${CXX} -o ${TGT[0]} ${SRC} -I ${SRC[0].parent.bldpath()}')
+
+ def build(bld):
+ bld(source='wscript', rule='echo "foo\\${SRC[0].name}\\bar"')
+
+ The env variables (CXX, ..) on the task must not hold dicts so as to preserve a consistent order.
+ The reserved keywords ``TGT`` and ``SRC`` represent the task input and output nodes
+
+ """
+ if isinstance(line, str):
+ if line.find('<') > 0 or line.find('>') > 0 or line.find('&&') > 0:
+ shell = True
+ else:
+ dvars_lst = []
+ funs_lst = []
+ for x in line:
+ if isinstance(x, str):
+ fun, dvars = compile_fun(x, shell)
+ dvars_lst += dvars
+ funs_lst.append(fun)
+ else:
+ # assume a function to let through
+ funs_lst.append(x)
+ def composed_fun(task):
+ for x in funs_lst:
+ ret = x(task)
+ if ret:
+ return ret
+ return None
+ return composed_fun, dvars_lst
+ if shell:
+ return compile_fun_shell(line)
+ else:
+ return compile_fun_noshell(line)
+
+def compile_sig_vars(vars):
+ """
+ This method produces a sig_vars method suitable for subclasses that provide
+ scriptlet code in their run_str code.
+ If no such method can be created, this method returns None.
+
+ The purpose of the sig_vars method returned is to ensures
+ that rebuilds occur whenever the contents of the expression changes.
+ This is the case B below::
+
+ import time
+ # case A: regular variables
+ tg = bld(rule='echo ${FOO}')
+ tg.env.FOO = '%s' % time.time()
+ # case B
+ bld(rule='echo ${gen.foo}', foo='%s' % time.time())
+
+ :param vars: env variables such as CXXFLAGS or gen.foo
+ :type vars: list of string
+ :return: A sig_vars method relevant for dependencies if adequate, else None
+ :rtype: A function, or None in most cases
+ """
+ buf = []
+ for x in sorted(vars):
+ if x[:3] in ('tsk', 'gen', 'bld'):
+ buf.append('buf.append(%s)' % x)
+ if buf:
+ return funex(COMPILE_TEMPLATE_SIG_VARS % '\n\t'.join(buf))
+ return None
+
+def task_factory(name, func=None, vars=None, color='GREEN', ext_in=[], ext_out=[], before=[], after=[], shell=False, scan=None):
+ """
+ Returns a new task subclass with the function ``run`` compiled from the line given.
+
+ :param func: method run
+ :type func: string or function
+ :param vars: list of variables to hash
+ :type vars: list of string
+ :param color: color to use
+ :type color: string
+ :param shell: when *func* is a string, enable/disable the use of the shell
+ :type shell: bool
+ :param scan: method scan
+ :type scan: function
+ :rtype: :py:class:`waflib.Task.Task`
+ """
+
+ params = {
+ 'vars': vars or [], # function arguments are static, and this one may be modified by the class
+ 'color': color,
+ 'name': name,
+ 'shell': shell,
+ 'scan': scan,
+ }
+
+ if isinstance(func, str) or isinstance(func, tuple):
+ params['run_str'] = func
+ else:
+ params['run'] = func
+
+ cls = type(Task)(name, (Task,), params)
+ classes[name] = cls
+
+ if ext_in:
+ cls.ext_in = Utils.to_list(ext_in)
+ if ext_out:
+ cls.ext_out = Utils.to_list(ext_out)
+ if before:
+ cls.before = Utils.to_list(before)
+ if after:
+ cls.after = Utils.to_list(after)
+
+ return cls
+
+def deep_inputs(cls):
+ """
+ Task class decorator to enable rebuilds on input files task signatures
+ """
+ def sig_explicit_deps(self):
+ Task.sig_explicit_deps(self)
+ Task.sig_deep_inputs(self)
+ cls.sig_explicit_deps = sig_explicit_deps
+ return cls
+
+TaskBase = Task
+"Provided for compatibility reasons, TaskBase should not be used"
+
+class TaskSemaphore(object):
+ """
+ Task semaphores provide a simple and efficient way of throttling the amount of
+ a particular task to run concurrently. The throttling value is capped
+ by the amount of maximum jobs, so for example, a `TaskSemaphore(10)`
+ has no effect in a `-j2` build.
+
+ Task semaphores are typically specified on the task class level::
+
+ class compile(waflib.Task.Task):
+ semaphore = waflib.Task.TaskSemaphore(2)
+ run_str = 'touch ${TGT}'
+
+ Task semaphores are meant to be used by the build scheduler in the main
+ thread, so there are no guarantees of thread safety.
+ """
+ def __init__(self, num):
+ """
+ :param num: maximum value of concurrent tasks
+ :type num: int
+ """
+ self.num = num
+ self.locking = set()
+ self.waiting = set()
+
+ def is_locked(self):
+ """Returns True if this semaphore cannot be acquired by more tasks"""
+ return len(self.locking) >= self.num
+
+ def acquire(self, tsk):
+ """
+ Mark the semaphore as used by the given task (not re-entrant).
+
+ :param tsk: task object
+ :type tsk: :py:class:`waflib.Task.Task`
+ :raises: :py:class:`IndexError` in case the resource is already acquired
+ """
+ if self.is_locked():
+ raise IndexError('Cannot lock more %r' % self.locking)
+ self.locking.add(tsk)
+
+ def release(self, tsk):
+ """
+ Mark the semaphore as unused by the given task.
+
+ :param tsk: task object
+ :type tsk: :py:class:`waflib.Task.Task`
+ :raises: :py:class:`KeyError` in case the resource is not acquired by the task
+ """
+ self.locking.remove(tsk)
+
diff --git a/waflib/TaskGen.py b/waflib/TaskGen.py
new file mode 100644
index 0000000..532b7d5
--- /dev/null
+++ b/waflib/TaskGen.py
@@ -0,0 +1,917 @@
+#!/usr/bin/env python
+# encoding: utf-8
+# Thomas Nagy, 2005-2018 (ita)
+
+"""
+Task generators
+
+The class :py:class:`waflib.TaskGen.task_gen` encapsulates the creation of task objects (low-level code)
+The instances can have various parameters, but the creation of task nodes (Task.py)
+is deferred. To achieve this, various methods are called from the method "apply"
+"""
+
+import copy, re, os, functools
+from waflib import Task, Utils, Logs, Errors, ConfigSet, Node
+
+feats = Utils.defaultdict(set)
+"""remember the methods declaring features"""
+
+HEADER_EXTS = ['.h', '.hpp', '.hxx', '.hh']
+
+class task_gen(object):
+ """
+ Instances of this class create :py:class:`waflib.Task.Task` when
+ calling the method :py:meth:`waflib.TaskGen.task_gen.post` from the main thread.
+ A few notes:
+
+ * The methods to call (*self.meths*) can be specified dynamically (removing, adding, ..)
+ * The 'features' are used to add methods to self.meths and then execute them
+ * The attribute 'path' is a node representing the location of the task generator
+ * The tasks created are added to the attribute *tasks*
+ * The attribute 'idx' is a counter of task generators in the same path
+ """
+
+ mappings = Utils.ordered_iter_dict()
+ """Mappings are global file extension mappings that are retrieved in the order of definition"""
+
+ prec = Utils.defaultdict(set)
+ """Dict that holds the precedence execution rules for task generator methods"""
+
+ def __init__(self, *k, **kw):
+ """
+ Task generator objects predefine various attributes (source, target) for possible
+ processing by process_rule (make-like rules) or process_source (extensions, misc methods)
+
+ Tasks are stored on the attribute 'tasks'. They are created by calling methods
+ listed in ``self.meths`` or referenced in the attribute ``features``
+ A topological sort is performed to execute the methods in correct order.
+
+ The extra key/value elements passed in ``kw`` are set as attributes
+ """
+ self.source = []
+ self.target = ''
+
+ self.meths = []
+ """
+ List of method names to execute (internal)
+ """
+
+ self.features = []
+ """
+ List of feature names for bringing new methods in
+ """
+
+ self.tasks = []
+ """
+ Tasks created are added to this list
+ """
+
+ if not 'bld' in kw:
+ # task generators without a build context :-/
+ self.env = ConfigSet.ConfigSet()
+ self.idx = 0
+ self.path = None
+ else:
+ self.bld = kw['bld']
+ self.env = self.bld.env.derive()
+ self.path = kw.get('path', self.bld.path) # by default, emulate chdir when reading scripts
+
+ # Provide a unique index per folder
+ # This is part of a measure to prevent output file name collisions
+ path = self.path.abspath()
+ try:
+ self.idx = self.bld.idx[path] = self.bld.idx.get(path, 0) + 1
+ except AttributeError:
+ self.bld.idx = {}
+ self.idx = self.bld.idx[path] = 1
+
+ # Record the global task generator count
+ try:
+ self.tg_idx_count = self.bld.tg_idx_count = self.bld.tg_idx_count + 1
+ except AttributeError:
+ self.tg_idx_count = self.bld.tg_idx_count = 1
+
+ for key, val in kw.items():
+ setattr(self, key, val)
+
+ def __str__(self):
+ """Debugging helper"""
+ return "<task_gen %r declared in %s>" % (self.name, self.path.abspath())
+
+ def __repr__(self):
+ """Debugging helper"""
+ lst = []
+ for x in self.__dict__:
+ if x not in ('env', 'bld', 'compiled_tasks', 'tasks'):
+ lst.append("%s=%s" % (x, repr(getattr(self, x))))
+ return "bld(%s) in %s" % (", ".join(lst), self.path.abspath())
+
+ def get_cwd(self):
+ """
+ Current working directory for the task generator, defaults to the build directory.
+ This is still used in a few places but it should disappear at some point as the classes
+ define their own working directory.
+
+ :rtype: :py:class:`waflib.Node.Node`
+ """
+ return self.bld.bldnode
+
+ def get_name(self):
+ """
+ If the attribute ``name`` is not set on the instance,
+ the name is computed from the target name::
+
+ def build(bld):
+ x = bld(name='foo')
+ x.get_name() # foo
+ y = bld(target='bar')
+ y.get_name() # bar
+
+ :rtype: string
+ :return: name of this task generator
+ """
+ try:
+ return self._name
+ except AttributeError:
+ if isinstance(self.target, list):
+ lst = [str(x) for x in self.target]
+ name = self._name = ','.join(lst)
+ else:
+ name = self._name = str(self.target)
+ return name
+ def set_name(self, name):
+ self._name = name
+
+ name = property(get_name, set_name)
+
+ def to_list(self, val):
+ """
+ Ensures that a parameter is a list, see :py:func:`waflib.Utils.to_list`
+
+ :type val: string or list of string
+ :param val: input to return as a list
+ :rtype: list
+ """
+ if isinstance(val, str):
+ return val.split()
+ else:
+ return val
+
+ def post(self):
+ """
+ Creates tasks for this task generators. The following operations are performed:
+
+ #. The body of this method is called only once and sets the attribute ``posted``
+ #. The attribute ``features`` is used to add more methods in ``self.meths``
+ #. The methods are sorted by the precedence table ``self.prec`` or `:waflib:attr:waflib.TaskGen.task_gen.prec`
+ #. The methods are then executed in order
+ #. The tasks created are added to :py:attr:`waflib.TaskGen.task_gen.tasks`
+ """
+ if getattr(self, 'posted', None):
+ return False
+ self.posted = True
+
+ keys = set(self.meths)
+ keys.update(feats['*'])
+
+ # add the methods listed in the features
+ self.features = Utils.to_list(self.features)
+ for x in self.features:
+ st = feats[x]
+ if st:
+ keys.update(st)
+ elif not x in Task.classes:
+ Logs.warn('feature %r does not exist - bind at least one method to it?', x)
+
+ # copy the precedence table
+ prec = {}
+ prec_tbl = self.prec
+ for x in prec_tbl:
+ if x in keys:
+ prec[x] = prec_tbl[x]
+
+ # elements disconnected
+ tmp = []
+ for a in keys:
+ for x in prec.values():
+ if a in x:
+ break
+ else:
+ tmp.append(a)
+
+ tmp.sort(reverse=True)
+
+ # topological sort
+ out = []
+ while tmp:
+ e = tmp.pop()
+ if e in keys:
+ out.append(e)
+ try:
+ nlst = prec[e]
+ except KeyError:
+ pass
+ else:
+ del prec[e]
+ for x in nlst:
+ for y in prec:
+ if x in prec[y]:
+ break
+ else:
+ tmp.append(x)
+ tmp.sort(reverse=True)
+
+ if prec:
+ buf = ['Cycle detected in the method execution:']
+ for k, v in prec.items():
+ buf.append('- %s after %s' % (k, [x for x in v if x in prec]))
+ raise Errors.WafError('\n'.join(buf))
+ self.meths = out
+
+ # then we run the methods in order
+ Logs.debug('task_gen: posting %s %d', self, id(self))
+ for x in out:
+ try:
+ v = getattr(self, x)
+ except AttributeError:
+ raise Errors.WafError('%r is not a valid task generator method' % x)
+ Logs.debug('task_gen: -> %s (%d)', x, id(self))
+ v()
+
+ Logs.debug('task_gen: posted %s', self.name)
+ return True
+
+ def get_hook(self, node):
+ """
+ Returns the ``@extension`` method to call for a Node of a particular extension.
+
+ :param node: Input file to process
+ :type node: :py:class:`waflib.Tools.Node.Node`
+ :return: A method able to process the input node by looking at the extension
+ :rtype: function
+ """
+ name = node.name
+ for k in self.mappings:
+ try:
+ if name.endswith(k):
+ return self.mappings[k]
+ except TypeError:
+ # regexps objects
+ if k.match(name):
+ return self.mappings[k]
+ keys = list(self.mappings.keys())
+ raise Errors.WafError("File %r has no mapping in %r (load a waf tool?)" % (node, keys))
+
+ def create_task(self, name, src=None, tgt=None, **kw):
+ """
+ Creates task instances.
+
+ :param name: task class name
+ :type name: string
+ :param src: input nodes
+ :type src: list of :py:class:`waflib.Tools.Node.Node`
+ :param tgt: output nodes
+ :type tgt: list of :py:class:`waflib.Tools.Node.Node`
+ :return: A task object
+ :rtype: :py:class:`waflib.Task.Task`
+ """
+ task = Task.classes[name](env=self.env.derive(), generator=self)
+ if src:
+ task.set_inputs(src)
+ if tgt:
+ task.set_outputs(tgt)
+ task.__dict__.update(kw)
+ self.tasks.append(task)
+ return task
+
+ def clone(self, env):
+ """
+ Makes a copy of a task generator. Once the copy is made, it is necessary to ensure that the
+ it does not create the same output files as the original, or the same files may
+ be compiled several times.
+
+ :param env: A configuration set
+ :type env: :py:class:`waflib.ConfigSet.ConfigSet`
+ :return: A copy
+ :rtype: :py:class:`waflib.TaskGen.task_gen`
+ """
+ newobj = self.bld()
+ for x in self.__dict__:
+ if x in ('env', 'bld'):
+ continue
+ elif x in ('path', 'features'):
+ setattr(newobj, x, getattr(self, x))
+ else:
+ setattr(newobj, x, copy.copy(getattr(self, x)))
+
+ newobj.posted = False
+ if isinstance(env, str):
+ newobj.env = self.bld.all_envs[env].derive()
+ else:
+ newobj.env = env.derive()
+
+ return newobj
+
+def declare_chain(name='', rule=None, reentrant=None, color='BLUE',
+ ext_in=[], ext_out=[], before=[], after=[], decider=None, scan=None, install_path=None, shell=False):
+ """
+ Creates a new mapping and a task class for processing files by extension.
+ See Tools/flex.py for an example.
+
+ :param name: name for the task class
+ :type name: string
+ :param rule: function to execute or string to be compiled in a function
+ :type rule: string or function
+ :param reentrant: re-inject the output file in the process (done automatically, set to 0 to disable)
+ :type reentrant: int
+ :param color: color for the task output
+ :type color: string
+ :param ext_in: execute the task only after the files of such extensions are created
+ :type ext_in: list of string
+ :param ext_out: execute the task only before files of such extensions are processed
+ :type ext_out: list of string
+ :param before: execute instances of this task before classes of the given names
+ :type before: list of string
+ :param after: execute instances of this task after classes of the given names
+ :type after: list of string
+ :param decider: if present, function that returns a list of output file extensions (overrides ext_out for output files, but not for the build order)
+ :type decider: function
+ :param scan: scanner function for the task
+ :type scan: function
+ :param install_path: installation path for the output nodes
+ :type install_path: string
+ """
+ ext_in = Utils.to_list(ext_in)
+ ext_out = Utils.to_list(ext_out)
+ if not name:
+ name = rule
+ cls = Task.task_factory(name, rule, color=color, ext_in=ext_in, ext_out=ext_out, before=before, after=after, scan=scan, shell=shell)
+
+ def x_file(self, node):
+ if ext_in:
+ _ext_in = ext_in[0]
+
+ tsk = self.create_task(name, node)
+ cnt = 0
+
+ ext = decider(self, node) if decider else cls.ext_out
+ for x in ext:
+ k = node.change_ext(x, ext_in=_ext_in)
+ tsk.outputs.append(k)
+
+ if reentrant != None:
+ if cnt < int(reentrant):
+ self.source.append(k)
+ else:
+ # reinject downstream files into the build
+ for y in self.mappings: # ~ nfile * nextensions :-/
+ if k.name.endswith(y):
+ self.source.append(k)
+ break
+ cnt += 1
+
+ if install_path:
+ self.install_task = self.add_install_files(install_to=install_path, install_from=tsk.outputs)
+ return tsk
+
+ for x in cls.ext_in:
+ task_gen.mappings[x] = x_file
+ return x_file
+
+def taskgen_method(func):
+ """
+ Decorator that registers method as a task generator method.
+ The function must accept a task generator as first parameter::
+
+ from waflib.TaskGen import taskgen_method
+ @taskgen_method
+ def mymethod(self):
+ pass
+
+ :param func: task generator method to add
+ :type func: function
+ :rtype: function
+ """
+ setattr(task_gen, func.__name__, func)
+ return func
+
+def feature(*k):
+ """
+ Decorator that registers a task generator method that will be executed when the
+ object attribute ``feature`` contains the corresponding key(s)::
+
+ from waflib.Task import feature
+ @feature('myfeature')
+ def myfunction(self):
+ print('that is my feature!')
+ def build(bld):
+ bld(features='myfeature')
+
+ :param k: feature names
+ :type k: list of string
+ """
+ def deco(func):
+ setattr(task_gen, func.__name__, func)
+ for name in k:
+ feats[name].update([func.__name__])
+ return func
+ return deco
+
+def before_method(*k):
+ """
+ Decorator that registera task generator method which will be executed
+ before the functions of given name(s)::
+
+ from waflib.TaskGen import feature, before
+ @feature('myfeature')
+ @before_method('fun2')
+ def fun1(self):
+ print('feature 1!')
+ @feature('myfeature')
+ def fun2(self):
+ print('feature 2!')
+ def build(bld):
+ bld(features='myfeature')
+
+ :param k: method names
+ :type k: list of string
+ """
+ def deco(func):
+ setattr(task_gen, func.__name__, func)
+ for fun_name in k:
+ task_gen.prec[func.__name__].add(fun_name)
+ return func
+ return deco
+before = before_method
+
+def after_method(*k):
+ """
+ Decorator that registers a task generator method which will be executed
+ after the functions of given name(s)::
+
+ from waflib.TaskGen import feature, after
+ @feature('myfeature')
+ @after_method('fun2')
+ def fun1(self):
+ print('feature 1!')
+ @feature('myfeature')
+ def fun2(self):
+ print('feature 2!')
+ def build(bld):
+ bld(features='myfeature')
+
+ :param k: method names
+ :type k: list of string
+ """
+ def deco(func):
+ setattr(task_gen, func.__name__, func)
+ for fun_name in k:
+ task_gen.prec[fun_name].add(func.__name__)
+ return func
+ return deco
+after = after_method
+
+def extension(*k):
+ """
+ Decorator that registers a task generator method which will be invoked during
+ the processing of source files for the extension given::
+
+ from waflib import Task
+ class mytask(Task):
+ run_str = 'cp ${SRC} ${TGT}'
+ @extension('.moo')
+ def create_maa_file(self, node):
+ self.create_task('mytask', node, node.change_ext('.maa'))
+ def build(bld):
+ bld(source='foo.moo')
+ """
+ def deco(func):
+ setattr(task_gen, func.__name__, func)
+ for x in k:
+ task_gen.mappings[x] = func
+ return func
+ return deco
+
+@taskgen_method
+def to_nodes(self, lst, path=None):
+ """
+ Flatten the input list of string/nodes/lists into a list of nodes.
+
+ It is used by :py:func:`waflib.TaskGen.process_source` and :py:func:`waflib.TaskGen.process_rule`.
+ It is designed for source files, for folders, see :py:func:`waflib.Tools.ccroot.to_incnodes`:
+
+ :param lst: input list
+ :type lst: list of string and nodes
+ :param path: path from which to search the nodes (by default, :py:attr:`waflib.TaskGen.task_gen.path`)
+ :type path: :py:class:`waflib.Tools.Node.Node`
+ :rtype: list of :py:class:`waflib.Tools.Node.Node`
+ """
+ tmp = []
+ path = path or self.path
+ find = path.find_resource
+
+ if isinstance(lst, Node.Node):
+ lst = [lst]
+
+ for x in Utils.to_list(lst):
+ if isinstance(x, str):
+ node = find(x)
+ elif hasattr(x, 'name'):
+ node = x
+ else:
+ tmp.extend(self.to_nodes(x))
+ continue
+ if not node:
+ raise Errors.WafError('source not found: %r in %r' % (x, self))
+ tmp.append(node)
+ return tmp
+
+@feature('*')
+def process_source(self):
+ """
+ Processes each element in the attribute ``source`` by extension.
+
+ #. The *source* list is converted through :py:meth:`waflib.TaskGen.to_nodes` to a list of :py:class:`waflib.Node.Node` first.
+ #. File extensions are mapped to methods having the signature: ``def meth(self, node)`` by :py:meth:`waflib.TaskGen.extension`
+ #. The method is retrieved through :py:meth:`waflib.TaskGen.task_gen.get_hook`
+ #. When called, the methods may modify self.source to append more source to process
+ #. The mappings can map an extension or a filename (see the code below)
+ """
+ self.source = self.to_nodes(getattr(self, 'source', []))
+ for node in self.source:
+ self.get_hook(node)(self, node)
+
+@feature('*')
+@before_method('process_source')
+def process_rule(self):
+ """
+ Processes the attribute ``rule``. When present, :py:meth:`waflib.TaskGen.process_source` is disabled::
+
+ def build(bld):
+ bld(rule='cp ${SRC} ${TGT}', source='wscript', target='bar.txt')
+
+ Main attributes processed:
+
+ * rule: command to execute, it can be a tuple of strings for multiple commands
+ * chmod: permissions for the resulting files (integer value such as Utils.O755)
+ * shell: set to False to execute the command directly (default is True to use a shell)
+ * scan: scanner function
+ * vars: list of variables to trigger rebuilds, such as CFLAGS
+ * cls_str: string to display when executing the task
+ * cls_keyword: label to display when executing the task
+ * cache_rule: by default, try to re-use similar classes, set to False to disable
+ * source: list of Node or string objects representing the source files required by this task
+ * target: list of Node or string objects representing the files that this task creates
+ * cwd: current working directory (Node or string)
+ * stdout: standard output, set to None to prevent waf from capturing the text
+ * stderr: standard error, set to None to prevent waf from capturing the text
+ * timeout: timeout for command execution (Python 3)
+ * always: whether to always run the command (False by default)
+ * deep_inputs: whether the task must depend on the input file tasks too (False by default)
+ """
+ if not getattr(self, 'rule', None):
+ return
+
+ # create the task class
+ name = str(getattr(self, 'name', None) or self.target or getattr(self.rule, '__name__', self.rule))
+
+ # or we can put the class in a cache for performance reasons
+ try:
+ cache = self.bld.cache_rule_attr
+ except AttributeError:
+ cache = self.bld.cache_rule_attr = {}
+
+ chmod = getattr(self, 'chmod', None)
+ shell = getattr(self, 'shell', True)
+ color = getattr(self, 'color', 'BLUE')
+ scan = getattr(self, 'scan', None)
+ _vars = getattr(self, 'vars', [])
+ cls_str = getattr(self, 'cls_str', None)
+ cls_keyword = getattr(self, 'cls_keyword', None)
+ use_cache = getattr(self, 'cache_rule', 'True')
+ deep_inputs = getattr(self, 'deep_inputs', False)
+
+ scan_val = has_deps = hasattr(self, 'deps')
+ if scan:
+ scan_val = id(scan)
+
+ key = Utils.h_list((name, self.rule, chmod, shell, color, cls_str, cls_keyword, scan_val, _vars, deep_inputs))
+
+ cls = None
+ if use_cache:
+ try:
+ cls = cache[key]
+ except KeyError:
+ pass
+ if not cls:
+ rule = self.rule
+ if chmod is not None:
+ def chmod_fun(tsk):
+ for x in tsk.outputs:
+ os.chmod(x.abspath(), tsk.generator.chmod)
+ if isinstance(rule, tuple):
+ rule = list(rule)
+ rule.append(chmod_fun)
+ rule = tuple(rule)
+ else:
+ rule = (rule, chmod_fun)
+
+ cls = Task.task_factory(name, rule, _vars, shell=shell, color=color)
+
+ if cls_str:
+ setattr(cls, '__str__', self.cls_str)
+
+ if cls_keyword:
+ setattr(cls, 'keyword', self.cls_keyword)
+
+ if deep_inputs:
+ Task.deep_inputs(cls)
+
+ if scan:
+ cls.scan = self.scan
+ elif has_deps:
+ def scan(self):
+ nodes = []
+ for x in self.generator.to_list(getattr(self.generator, 'deps', None)):
+ node = self.generator.path.find_resource(x)
+ if not node:
+ self.generator.bld.fatal('Could not find %r (was it declared?)' % x)
+ nodes.append(node)
+ return [nodes, []]
+ cls.scan = scan
+
+ if use_cache:
+ cache[key] = cls
+
+ # now create one instance
+ tsk = self.create_task(name)
+
+ for x in ('after', 'before', 'ext_in', 'ext_out'):
+ setattr(tsk, x, getattr(self, x, []))
+
+ if hasattr(self, 'stdout'):
+ tsk.stdout = self.stdout
+
+ if hasattr(self, 'stderr'):
+ tsk.stderr = self.stderr
+
+ if getattr(self, 'timeout', None):
+ tsk.timeout = self.timeout
+
+ if getattr(self, 'always', None):
+ tsk.always_run = True
+
+ if getattr(self, 'target', None):
+ if isinstance(self.target, str):
+ self.target = self.target.split()
+ if not isinstance(self.target, list):
+ self.target = [self.target]
+ for x in self.target:
+ if isinstance(x, str):
+ tsk.outputs.append(self.path.find_or_declare(x))
+ else:
+ x.parent.mkdir() # if a node was given, create the required folders
+ tsk.outputs.append(x)
+ if getattr(self, 'install_path', None):
+ self.install_task = self.add_install_files(install_to=self.install_path,
+ install_from=tsk.outputs, chmod=getattr(self, 'chmod', Utils.O644))
+
+ if getattr(self, 'source', None):
+ tsk.inputs = self.to_nodes(self.source)
+ # bypass the execution of process_source by setting the source to an empty list
+ self.source = []
+
+ if getattr(self, 'cwd', None):
+ tsk.cwd = self.cwd
+
+ if isinstance(tsk.run, functools.partial):
+ # Python documentation says: "partial objects defined in classes
+ # behave like static methods and do not transform into bound
+ # methods during instance attribute look-up."
+ tsk.run = functools.partial(tsk.run, tsk)
+
+@feature('seq')
+def sequence_order(self):
+ """
+ Adds a strict sequential constraint between the tasks generated by task generators.
+ It works because task generators are posted in order.
+ It will not post objects which belong to other folders.
+
+ Example::
+
+ bld(features='javac seq')
+ bld(features='jar seq')
+
+ To start a new sequence, set the attribute seq_start, for example::
+
+ obj = bld(features='seq')
+ obj.seq_start = True
+
+ Note that the method is executed in last position. This is more an
+ example than a widely-used solution.
+ """
+ if self.meths and self.meths[-1] != 'sequence_order':
+ self.meths.append('sequence_order')
+ return
+
+ if getattr(self, 'seq_start', None):
+ return
+
+ # all the tasks previously declared must be run before these
+ if getattr(self.bld, 'prev', None):
+ self.bld.prev.post()
+ for x in self.bld.prev.tasks:
+ for y in self.tasks:
+ y.set_run_after(x)
+
+ self.bld.prev = self
+
+
+re_m4 = re.compile(r'@(\w+)@', re.M)
+
+class subst_pc(Task.Task):
+ """
+ Creates *.pc* files from *.pc.in*. The task is executed whenever an input variable used
+ in the substitution changes.
+ """
+
+ def force_permissions(self):
+ "Private for the time being, we will probably refactor this into run_str=[run1,chmod]"
+ if getattr(self.generator, 'chmod', None):
+ for x in self.outputs:
+ os.chmod(x.abspath(), self.generator.chmod)
+
+ def run(self):
+ "Substitutes variables in a .in file"
+
+ if getattr(self.generator, 'is_copy', None):
+ for i, x in enumerate(self.outputs):
+ x.write(self.inputs[i].read('rb'), 'wb')
+ stat = os.stat(self.inputs[i].abspath()) # Preserve mtime of the copy
+ os.utime(self.outputs[i].abspath(), (stat.st_atime, stat.st_mtime))
+ self.force_permissions()
+ return None
+
+ if getattr(self.generator, 'fun', None):
+ ret = self.generator.fun(self)
+ if not ret:
+ self.force_permissions()
+ return ret
+
+ code = self.inputs[0].read(encoding=getattr(self.generator, 'encoding', 'latin-1'))
+ if getattr(self.generator, 'subst_fun', None):
+ code = self.generator.subst_fun(self, code)
+ if code is not None:
+ self.outputs[0].write(code, encoding=getattr(self.generator, 'encoding', 'latin-1'))
+ self.force_permissions()
+ return None
+
+ # replace all % by %% to prevent errors by % signs
+ code = code.replace('%', '%%')
+
+ # extract the vars foo into lst and replace @foo@ by %(foo)s
+ lst = []
+ def repl(match):
+ g = match.group
+ if g(1):
+ lst.append(g(1))
+ return "%%(%s)s" % g(1)
+ return ''
+ code = getattr(self.generator, 're_m4', re_m4).sub(repl, code)
+
+ try:
+ d = self.generator.dct
+ except AttributeError:
+ d = {}
+ for x in lst:
+ tmp = getattr(self.generator, x, '') or self.env[x] or self.env[x.upper()]
+ try:
+ tmp = ''.join(tmp)
+ except TypeError:
+ tmp = str(tmp)
+ d[x] = tmp
+
+ code = code % d
+ self.outputs[0].write(code, encoding=getattr(self.generator, 'encoding', 'latin-1'))
+ self.generator.bld.raw_deps[self.uid()] = lst
+
+ # make sure the signature is updated
+ try:
+ delattr(self, 'cache_sig')
+ except AttributeError:
+ pass
+
+ self.force_permissions()
+
+ def sig_vars(self):
+ """
+ Compute a hash (signature) of the variables used in the substitution
+ """
+ bld = self.generator.bld
+ env = self.env
+ upd = self.m.update
+
+ if getattr(self.generator, 'fun', None):
+ upd(Utils.h_fun(self.generator.fun).encode())
+ if getattr(self.generator, 'subst_fun', None):
+ upd(Utils.h_fun(self.generator.subst_fun).encode())
+
+ # raw_deps: persistent custom values returned by the scanner
+ vars = self.generator.bld.raw_deps.get(self.uid(), [])
+
+ # hash both env vars and task generator attributes
+ act_sig = bld.hash_env_vars(env, vars)
+ upd(act_sig)
+
+ lst = [getattr(self.generator, x, '') for x in vars]
+ upd(Utils.h_list(lst))
+
+ return self.m.digest()
+
+@extension('.pc.in')
+def add_pcfile(self, node):
+ """
+ Processes *.pc.in* files to *.pc*. Installs the results to ``${PREFIX}/lib/pkgconfig/`` by default
+
+ def build(bld):
+ bld(source='foo.pc.in', install_path='${LIBDIR}/pkgconfig/')
+ """
+ tsk = self.create_task('subst_pc', node, node.change_ext('.pc', '.pc.in'))
+ self.install_task = self.add_install_files(
+ install_to=getattr(self, 'install_path', '${LIBDIR}/pkgconfig/'), install_from=tsk.outputs)
+
+class subst(subst_pc):
+ pass
+
+@feature('subst')
+@before_method('process_source', 'process_rule')
+def process_subst(self):
+ """
+ Defines a transformation that substitutes the contents of *source* files to *target* files::
+
+ def build(bld):
+ bld(
+ features='subst',
+ source='foo.c.in',
+ target='foo.c',
+ install_path='${LIBDIR}/pkgconfig',
+ VAR = 'val'
+ )
+
+ The input files are supposed to contain macros of the form *@VAR@*, where *VAR* is an argument
+ of the task generator object.
+
+ This method overrides the processing by :py:meth:`waflib.TaskGen.process_source`.
+ """
+
+ src = Utils.to_list(getattr(self, 'source', []))
+ if isinstance(src, Node.Node):
+ src = [src]
+ tgt = Utils.to_list(getattr(self, 'target', []))
+ if isinstance(tgt, Node.Node):
+ tgt = [tgt]
+ if len(src) != len(tgt):
+ raise Errors.WafError('invalid number of source/target for %r' % self)
+
+ for x, y in zip(src, tgt):
+ if not x or not y:
+ raise Errors.WafError('null source or target for %r' % self)
+ a, b = None, None
+
+ if isinstance(x, str) and isinstance(y, str) and x == y:
+ a = self.path.find_node(x)
+ b = self.path.get_bld().make_node(y)
+ if not os.path.isfile(b.abspath()):
+ b.parent.mkdir()
+ else:
+ if isinstance(x, str):
+ a = self.path.find_resource(x)
+ elif isinstance(x, Node.Node):
+ a = x
+ if isinstance(y, str):
+ b = self.path.find_or_declare(y)
+ elif isinstance(y, Node.Node):
+ b = y
+
+ if not a:
+ raise Errors.WafError('could not find %r for %r' % (x, self))
+
+ tsk = self.create_task('subst', a, b)
+ for k in ('after', 'before', 'ext_in', 'ext_out'):
+ val = getattr(self, k, None)
+ if val:
+ setattr(tsk, k, val)
+
+ # paranoid safety measure for the general case foo.in->foo.h with ambiguous dependencies
+ for xt in HEADER_EXTS:
+ if b.name.endswith(xt):
+ tsk.ext_in = tsk.ext_in + ['.h']
+ break
+
+ inst_to = getattr(self, 'install_path', None)
+ if inst_to:
+ self.install_task = self.add_install_files(install_to=inst_to,
+ install_from=b, chmod=getattr(self, 'chmod', Utils.O644))
+
+ self.source = []
+
diff --git a/waflib/Tools/__init__.py b/waflib/Tools/__init__.py
new file mode 100644
index 0000000..079df35
--- /dev/null
+++ b/waflib/Tools/__init__.py
@@ -0,0 +1,3 @@
+#!/usr/bin/env python
+# encoding: utf-8
+# Thomas Nagy, 2005-2018 (ita)
diff --git a/waflib/Tools/ar.py b/waflib/Tools/ar.py
new file mode 100644
index 0000000..b39b645
--- /dev/null
+++ b/waflib/Tools/ar.py
@@ -0,0 +1,24 @@
+#!/usr/bin/env python
+# encoding: utf-8
+# Thomas Nagy, 2006-2018 (ita)
+# Ralf Habacker, 2006 (rh)
+
+"""
+The **ar** program creates static libraries. This tool is almost always loaded
+from others (C, C++, D, etc) for static library support.
+"""
+
+from waflib.Configure import conf
+
+@conf
+def find_ar(conf):
+ """Configuration helper used by C/C++ tools to enable the support for static libraries"""
+ conf.load('ar')
+
+def configure(conf):
+ """Finds the ar program and sets the default flags in ``conf.env.ARFLAGS``"""
+ conf.find_program('ar', var='AR')
+ conf.add_os_flags('ARFLAGS')
+ if not conf.env.ARFLAGS:
+ conf.env.ARFLAGS = ['rcs']
+
diff --git a/waflib/Tools/asm.py b/waflib/Tools/asm.py
new file mode 100644
index 0000000..b6f26fb
--- /dev/null
+++ b/waflib/Tools/asm.py
@@ -0,0 +1,73 @@
+#!/usr/bin/env python
+# encoding: utf-8
+# Thomas Nagy, 2008-2018 (ita)
+
+"""
+Assembly support, used by tools such as gas and nasm
+
+To declare targets using assembly::
+
+ def configure(conf):
+ conf.load('gcc gas')
+
+ def build(bld):
+ bld(
+ features='c cstlib asm',
+ source = 'test.S',
+ target = 'asmtest')
+
+ bld(
+ features='asm asmprogram',
+ source = 'test.S',
+ target = 'asmtest')
+
+Support for pure asm programs and libraries should also work::
+
+ def configure(conf):
+ conf.load('nasm')
+ conf.find_program('ld', 'ASLINK')
+
+ def build(bld):
+ bld(
+ features='asm asmprogram',
+ source = 'test.S',
+ target = 'asmtest')
+"""
+
+from waflib import Task
+from waflib.Tools.ccroot import link_task, stlink_task
+from waflib.TaskGen import extension
+
+class asm(Task.Task):
+ """
+ Compiles asm files by gas/nasm/yasm/...
+ """
+ color = 'BLUE'
+ run_str = '${AS} ${ASFLAGS} ${ASMPATH_ST:INCPATHS} ${DEFINES_ST:DEFINES} ${AS_SRC_F}${SRC} ${AS_TGT_F}${TGT}'
+
+@extension('.s', '.S', '.asm', '.ASM', '.spp', '.SPP')
+def asm_hook(self, node):
+ """
+ Binds the asm extension to the asm task
+
+ :param node: input file
+ :type node: :py:class:`waflib.Node.Node`
+ """
+ return self.create_compiled_task('asm', node)
+
+class asmprogram(link_task):
+ "Links object files into a c program"
+ run_str = '${ASLINK} ${ASLINKFLAGS} ${ASLNK_TGT_F}${TGT} ${ASLNK_SRC_F}${SRC}'
+ ext_out = ['.bin']
+ inst_to = '${BINDIR}'
+
+class asmshlib(asmprogram):
+ "Links object files into a c shared library"
+ inst_to = '${LIBDIR}'
+
+class asmstlib(stlink_task):
+ "Links object files into a c static library"
+ pass # do not remove
+
+def configure(conf):
+ conf.env.ASMPATH_ST = '-I%s'
diff --git a/waflib/Tools/bison.py b/waflib/Tools/bison.py
new file mode 100644
index 0000000..eef56dc
--- /dev/null
+++ b/waflib/Tools/bison.py
@@ -0,0 +1,49 @@
+#!/usr/bin/env python
+# encoding: utf-8
+# John O'Meara, 2006
+# Thomas Nagy 2009-2018 (ita)
+
+"""
+The **bison** program is a code generator which creates C or C++ files.
+The generated files are compiled into object files.
+"""
+
+from waflib import Task
+from waflib.TaskGen import extension
+
+class bison(Task.Task):
+ """Compiles bison files"""
+ color = 'BLUE'
+ run_str = '${BISON} ${BISONFLAGS} ${SRC[0].abspath()} -o ${TGT[0].name}'
+ ext_out = ['.h'] # just to make sure
+
+@extension('.y', '.yc', '.yy')
+def big_bison(self, node):
+ """
+ Creates a bison task, which must be executed from the directory of the output file.
+ """
+ has_h = '-d' in self.env.BISONFLAGS
+
+ outs = []
+ if node.name.endswith('.yc'):
+ outs.append(node.change_ext('.tab.cc'))
+ if has_h:
+ outs.append(node.change_ext('.tab.hh'))
+ else:
+ outs.append(node.change_ext('.tab.c'))
+ if has_h:
+ outs.append(node.change_ext('.tab.h'))
+
+ tsk = self.create_task('bison', node, outs)
+ tsk.cwd = node.parent.get_bld()
+
+ # and the c/cxx file must be compiled too
+ self.source.append(outs[0])
+
+def configure(conf):
+ """
+ Detects the *bison* program
+ """
+ conf.find_program('bison', var='BISON')
+ conf.env.BISONFLAGS = ['-d']
+
diff --git a/waflib/Tools/c.py b/waflib/Tools/c.py
new file mode 100644
index 0000000..effd6b6
--- /dev/null
+++ b/waflib/Tools/c.py
@@ -0,0 +1,39 @@
+#!/usr/bin/env python
+# encoding: utf-8
+# Thomas Nagy, 2006-2018 (ita)
+
+"Base for c programs/libraries"
+
+from waflib import TaskGen, Task
+from waflib.Tools import c_preproc
+from waflib.Tools.ccroot import link_task, stlink_task
+
+@TaskGen.extension('.c')
+def c_hook(self, node):
+ "Binds the c file extensions create :py:class:`waflib.Tools.c.c` instances"
+ if not self.env.CC and self.env.CXX:
+ return self.create_compiled_task('cxx', node)
+ return self.create_compiled_task('c', node)
+
+class c(Task.Task):
+ "Compiles C files into object files"
+ run_str = '${CC} ${ARCH_ST:ARCH} ${CFLAGS} ${FRAMEWORKPATH_ST:FRAMEWORKPATH} ${CPPPATH_ST:INCPATHS} ${DEFINES_ST:DEFINES} ${CC_SRC_F}${SRC} ${CC_TGT_F}${TGT[0].abspath()} ${CPPFLAGS}'
+ vars = ['CCDEPS'] # unused variable to depend on, just in case
+ ext_in = ['.h'] # set the build order easily by using ext_out=['.h']
+ scan = c_preproc.scan
+
+class cprogram(link_task):
+ "Links object files into c programs"
+ run_str = '${LINK_CC} ${LINKFLAGS} ${CCLNK_SRC_F}${SRC} ${CCLNK_TGT_F}${TGT[0].abspath()} ${RPATH_ST:RPATH} ${FRAMEWORKPATH_ST:FRAMEWORKPATH} ${FRAMEWORK_ST:FRAMEWORK} ${ARCH_ST:ARCH} ${STLIB_MARKER} ${STLIBPATH_ST:STLIBPATH} ${STLIB_ST:STLIB} ${SHLIB_MARKER} ${LIBPATH_ST:LIBPATH} ${LIB_ST:LIB} ${LDFLAGS}'
+ ext_out = ['.bin']
+ vars = ['LINKDEPS']
+ inst_to = '${BINDIR}'
+
+class cshlib(cprogram):
+ "Links object files into c shared libraries"
+ inst_to = '${LIBDIR}'
+
+class cstlib(stlink_task):
+ "Links object files into a c static libraries"
+ pass # do not remove
+
diff --git a/waflib/Tools/c_aliases.py b/waflib/Tools/c_aliases.py
new file mode 100644
index 0000000..c9d5369
--- /dev/null
+++ b/waflib/Tools/c_aliases.py
@@ -0,0 +1,144 @@
+#!/usr/bin/env python
+# encoding: utf-8
+# Thomas Nagy, 2005-2015 (ita)
+
+"base for all c/c++ programs and libraries"
+
+from waflib import Utils, Errors
+from waflib.Configure import conf
+
+def get_extensions(lst):
+ """
+ Returns the file extensions for the list of files given as input
+
+ :param lst: files to process
+ :list lst: list of string or :py:class:`waflib.Node.Node`
+ :return: list of file extensions
+ :rtype: list of string
+ """
+ ret = []
+ for x in Utils.to_list(lst):
+ if not isinstance(x, str):
+ x = x.name
+ ret.append(x[x.rfind('.') + 1:])
+ return ret
+
+def sniff_features(**kw):
+ """
+ Computes and returns the features required for a task generator by
+ looking at the file extensions. This aimed for C/C++ mainly::
+
+ snif_features(source=['foo.c', 'foo.cxx'], type='shlib')
+ # returns ['cxx', 'c', 'cxxshlib', 'cshlib']
+
+ :param source: source files to process
+ :type source: list of string or :py:class:`waflib.Node.Node`
+ :param type: object type in *program*, *shlib* or *stlib*
+ :type type: string
+ :return: the list of features for a task generator processing the source files
+ :rtype: list of string
+ """
+ exts = get_extensions(kw['source'])
+ typ = kw['typ']
+ feats = []
+
+ # watch the order, cxx will have the precedence
+ for x in 'cxx cpp c++ cc C'.split():
+ if x in exts:
+ feats.append('cxx')
+ break
+
+ if 'c' in exts or 'vala' in exts or 'gs' in exts:
+ feats.append('c')
+
+ for x in 'f f90 F F90 for FOR'.split():
+ if x in exts:
+ feats.append('fc')
+ break
+
+ if 'd' in exts:
+ feats.append('d')
+
+ if 'java' in exts:
+ feats.append('java')
+ return 'java'
+
+ if typ in ('program', 'shlib', 'stlib'):
+ will_link = False
+ for x in feats:
+ if x in ('cxx', 'd', 'fc', 'c'):
+ feats.append(x + typ)
+ will_link = True
+ if not will_link and not kw.get('features', []):
+ raise Errors.WafError('Cannot link from %r, try passing eg: features="c cprogram"?' % kw)
+ return feats
+
+def set_features(kw, typ):
+ """
+ Inserts data in the input dict *kw* based on existing data and on the type of target
+ required (typ).
+
+ :param kw: task generator parameters
+ :type kw: dict
+ :param typ: type of target
+ :type typ: string
+ """
+ kw['typ'] = typ
+ kw['features'] = Utils.to_list(kw.get('features', [])) + Utils.to_list(sniff_features(**kw))
+
+@conf
+def program(bld, *k, **kw):
+ """
+ Alias for creating programs by looking at the file extensions::
+
+ def build(bld):
+ bld.program(source='foo.c', target='app')
+ # equivalent to:
+ # bld(features='c cprogram', source='foo.c', target='app')
+
+ """
+ set_features(kw, 'program')
+ return bld(*k, **kw)
+
+@conf
+def shlib(bld, *k, **kw):
+ """
+ Alias for creating shared libraries by looking at the file extensions::
+
+ def build(bld):
+ bld.shlib(source='foo.c', target='app')
+ # equivalent to:
+ # bld(features='c cshlib', source='foo.c', target='app')
+
+ """
+ set_features(kw, 'shlib')
+ return bld(*k, **kw)
+
+@conf
+def stlib(bld, *k, **kw):
+ """
+ Alias for creating static libraries by looking at the file extensions::
+
+ def build(bld):
+ bld.stlib(source='foo.cpp', target='app')
+ # equivalent to:
+ # bld(features='cxx cxxstlib', source='foo.cpp', target='app')
+
+ """
+ set_features(kw, 'stlib')
+ return bld(*k, **kw)
+
+@conf
+def objects(bld, *k, **kw):
+ """
+ Alias for creating object files by looking at the file extensions::
+
+ def build(bld):
+ bld.objects(source='foo.c', target='app')
+ # equivalent to:
+ # bld(features='c', source='foo.c', target='app')
+
+ """
+ set_features(kw, 'objects')
+ return bld(*k, **kw)
+
diff --git a/waflib/Tools/c_config.py b/waflib/Tools/c_config.py
new file mode 100644
index 0000000..d546be9
--- /dev/null
+++ b/waflib/Tools/c_config.py
@@ -0,0 +1,1351 @@
+#!/usr/bin/env python
+# encoding: utf-8
+# Thomas Nagy, 2005-2018 (ita)
+
+"""
+C/C++/D configuration helpers
+"""
+
+from __future__ import with_statement
+
+import os, re, shlex
+from waflib import Build, Utils, Task, Options, Logs, Errors, Runner
+from waflib.TaskGen import after_method, feature
+from waflib.Configure import conf
+
+WAF_CONFIG_H = 'config.h'
+"""default name for the config.h file"""
+
+DEFKEYS = 'define_key'
+INCKEYS = 'include_key'
+
+SNIP_EMPTY_PROGRAM = '''
+int main(int argc, char **argv) {
+ (void)argc; (void)argv;
+ return 0;
+}
+'''
+
+MACRO_TO_DESTOS = {
+'__linux__' : 'linux',
+'__GNU__' : 'gnu', # hurd
+'__FreeBSD__' : 'freebsd',
+'__NetBSD__' : 'netbsd',
+'__OpenBSD__' : 'openbsd',
+'__sun' : 'sunos',
+'__hpux' : 'hpux',
+'__sgi' : 'irix',
+'_AIX' : 'aix',
+'__CYGWIN__' : 'cygwin',
+'__MSYS__' : 'cygwin',
+'_UWIN' : 'uwin',
+'_WIN64' : 'win32',
+'_WIN32' : 'win32',
+# Note about darwin: this is also tested with 'defined __APPLE__ && defined __MACH__' somewhere below in this file.
+'__ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__' : 'darwin',
+'__ENVIRONMENT_IPHONE_OS_VERSION_MIN_REQUIRED__' : 'darwin', # iphone
+'__QNX__' : 'qnx',
+'__native_client__' : 'nacl' # google native client platform
+}
+
+MACRO_TO_DEST_CPU = {
+'__x86_64__' : 'x86_64',
+'__amd64__' : 'x86_64',
+'__i386__' : 'x86',
+'__ia64__' : 'ia',
+'__mips__' : 'mips',
+'__sparc__' : 'sparc',
+'__alpha__' : 'alpha',
+'__aarch64__' : 'aarch64',
+'__thumb__' : 'thumb',
+'__arm__' : 'arm',
+'__hppa__' : 'hppa',
+'__powerpc__' : 'powerpc',
+'__ppc__' : 'powerpc',
+'__convex__' : 'convex',
+'__m68k__' : 'm68k',
+'__s390x__' : 's390x',
+'__s390__' : 's390',
+'__sh__' : 'sh',
+'__xtensa__' : 'xtensa',
+}
+
+@conf
+def parse_flags(self, line, uselib_store, env=None, force_static=False, posix=None):
+ """
+ Parses flags from the input lines, and adds them to the relevant use variables::
+
+ def configure(conf):
+ conf.parse_flags('-O3', 'FOO')
+ # conf.env.CXXFLAGS_FOO = ['-O3']
+ # conf.env.CFLAGS_FOO = ['-O3']
+
+ :param line: flags
+ :type line: string
+ :param uselib_store: where to add the flags
+ :type uselib_store: string
+ :param env: config set or conf.env by default
+ :type env: :py:class:`waflib.ConfigSet.ConfigSet`
+ """
+
+ assert(isinstance(line, str))
+
+ env = env or self.env
+
+ # Issue 811 and 1371
+ if posix is None:
+ posix = True
+ if '\\' in line:
+ posix = ('\\ ' in line) or ('\\\\' in line)
+
+ lex = shlex.shlex(line, posix=posix)
+ lex.whitespace_split = True
+ lex.commenters = ''
+ lst = list(lex)
+
+ # append_unique is not always possible
+ # for example, apple flags may require both -arch i386 and -arch ppc
+ uselib = uselib_store
+ def app(var, val):
+ env.append_value('%s_%s' % (var, uselib), val)
+ def appu(var, val):
+ env.append_unique('%s_%s' % (var, uselib), val)
+ static = False
+ while lst:
+ x = lst.pop(0)
+ st = x[:2]
+ ot = x[2:]
+
+ if st == '-I' or st == '/I':
+ if not ot:
+ ot = lst.pop(0)
+ appu('INCLUDES', ot)
+ elif st == '-i':
+ tmp = [x, lst.pop(0)]
+ app('CFLAGS', tmp)
+ app('CXXFLAGS', tmp)
+ elif st == '-D' or (env.CXX_NAME == 'msvc' and st == '/D'): # not perfect but..
+ if not ot:
+ ot = lst.pop(0)
+ app('DEFINES', ot)
+ elif st == '-l':
+ if not ot:
+ ot = lst.pop(0)
+ prefix = 'STLIB' if (force_static or static) else 'LIB'
+ app(prefix, ot)
+ elif st == '-L':
+ if not ot:
+ ot = lst.pop(0)
+ prefix = 'STLIBPATH' if (force_static or static) else 'LIBPATH'
+ appu(prefix, ot)
+ elif x.startswith('/LIBPATH:'):
+ prefix = 'STLIBPATH' if (force_static or static) else 'LIBPATH'
+ appu(prefix, x.replace('/LIBPATH:', ''))
+ elif x.startswith('-std='):
+ prefix = 'CXXFLAGS' if '++' in x else 'CFLAGS'
+ app(prefix, x)
+ elif x.startswith('+') or x in ('-pthread', '-fPIC', '-fpic', '-fPIE', '-fpie'):
+ app('CFLAGS', x)
+ app('CXXFLAGS', x)
+ app('LINKFLAGS', x)
+ elif x == '-framework':
+ appu('FRAMEWORK', lst.pop(0))
+ elif x.startswith('-F'):
+ appu('FRAMEWORKPATH', x[2:])
+ elif x == '-Wl,-rpath' or x == '-Wl,-R':
+ app('RPATH', lst.pop(0).lstrip('-Wl,'))
+ elif x.startswith('-Wl,-R,'):
+ app('RPATH', x[7:])
+ elif x.startswith('-Wl,-R'):
+ app('RPATH', x[6:])
+ elif x.startswith('-Wl,-rpath,'):
+ app('RPATH', x[11:])
+ elif x == '-Wl,-Bstatic' or x == '-Bstatic':
+ static = True
+ elif x == '-Wl,-Bdynamic' or x == '-Bdynamic':
+ static = False
+ elif x.startswith('-Wl') or x in ('-rdynamic', '-pie'):
+ app('LINKFLAGS', x)
+ elif x.startswith(('-m', '-f', '-dynamic', '-O', '-g')):
+ # Adding the -W option breaks python builds on Openindiana
+ app('CFLAGS', x)
+ app('CXXFLAGS', x)
+ elif x.startswith('-bundle'):
+ app('LINKFLAGS', x)
+ elif x.startswith(('-undefined', '-Xlinker')):
+ arg = lst.pop(0)
+ app('LINKFLAGS', [x, arg])
+ elif x.startswith(('-arch', '-isysroot')):
+ tmp = [x, lst.pop(0)]
+ app('CFLAGS', tmp)
+ app('CXXFLAGS', tmp)
+ app('LINKFLAGS', tmp)
+ elif x.endswith(('.a', '.so', '.dylib', '.lib')):
+ appu('LINKFLAGS', x) # not cool, #762
+ else:
+ self.to_log('Unhandled flag %r' % x)
+
+@conf
+def validate_cfg(self, kw):
+ """
+ Searches for the program *pkg-config* if missing, and validates the
+ parameters to pass to :py:func:`waflib.Tools.c_config.exec_cfg`.
+
+ :param path: the **-config program to use** (default is *pkg-config*)
+ :type path: list of string
+ :param msg: message to display to describe the test executed
+ :type msg: string
+ :param okmsg: message to display when the test is successful
+ :type okmsg: string
+ :param errmsg: message to display in case of error
+ :type errmsg: string
+ """
+ if not 'path' in kw:
+ if not self.env.PKGCONFIG:
+ self.find_program('pkg-config', var='PKGCONFIG')
+ kw['path'] = self.env.PKGCONFIG
+
+ # verify that exactly one action is requested
+ s = ('atleast_pkgconfig_version' in kw) + ('modversion' in kw) + ('package' in kw)
+ if s != 1:
+ raise ValueError('exactly one of atleast_pkgconfig_version, modversion and package must be set')
+ if not 'msg' in kw:
+ if 'atleast_pkgconfig_version' in kw:
+ kw['msg'] = 'Checking for pkg-config version >= %r' % kw['atleast_pkgconfig_version']
+ elif 'modversion' in kw:
+ kw['msg'] = 'Checking for %r version' % kw['modversion']
+ else:
+ kw['msg'] = 'Checking for %r' %(kw['package'])
+
+ # let the modversion check set the okmsg to the detected version
+ if not 'okmsg' in kw and not 'modversion' in kw:
+ kw['okmsg'] = 'yes'
+ if not 'errmsg' in kw:
+ kw['errmsg'] = 'not found'
+
+ # pkg-config version
+ if 'atleast_pkgconfig_version' in kw:
+ pass
+ elif 'modversion' in kw:
+ if not 'uselib_store' in kw:
+ kw['uselib_store'] = kw['modversion']
+ if not 'define_name' in kw:
+ kw['define_name'] = '%s_VERSION' % Utils.quote_define_name(kw['uselib_store'])
+ else:
+ if not 'uselib_store' in kw:
+ kw['uselib_store'] = Utils.to_list(kw['package'])[0].upper()
+ if not 'define_name' in kw:
+ kw['define_name'] = self.have_define(kw['uselib_store'])
+
+@conf
+def exec_cfg(self, kw):
+ """
+ Executes ``pkg-config`` or other ``-config`` applications to collect configuration flags:
+
+ * if atleast_pkgconfig_version is given, check that pkg-config has the version n and return
+ * if modversion is given, then return the module version
+ * else, execute the *-config* program with the *args* and *variables* given, and set the flags on the *conf.env.FLAGS_name* variable
+
+ :param atleast_pkgconfig_version: minimum pkg-config version to use (disable other tests)
+ :type atleast_pkgconfig_version: string
+ :param package: package name, for example *gtk+-2.0*
+ :type package: string
+ :param uselib_store: if the test is successful, define HAVE\\_*name*. It is also used to define *conf.env.FLAGS_name* variables.
+ :type uselib_store: string
+ :param modversion: if provided, return the version of the given module and define *name*\\_VERSION
+ :type modversion: string
+ :param args: arguments to give to *package* when retrieving flags
+ :type args: list of string
+ :param variables: return the values of particular variables
+ :type variables: list of string
+ :param define_variable: additional variables to define (also in conf.env.PKG_CONFIG_DEFINES)
+ :type define_variable: dict(string: string)
+ """
+
+ path = Utils.to_list(kw['path'])
+ env = self.env.env or None
+ if kw.get('pkg_config_path'):
+ if not env:
+ env = dict(self.environ)
+ env['PKG_CONFIG_PATH'] = kw['pkg_config_path']
+
+ def define_it():
+ define_name = kw['define_name']
+ # by default, add HAVE_X to the config.h, else provide DEFINES_X for use=X
+ if kw.get('global_define', 1):
+ self.define(define_name, 1, False)
+ else:
+ self.env.append_unique('DEFINES_%s' % kw['uselib_store'], "%s=1" % define_name)
+
+ if kw.get('add_have_to_env', 1):
+ self.env[define_name] = 1
+
+ # pkg-config version
+ if 'atleast_pkgconfig_version' in kw:
+ cmd = path + ['--atleast-pkgconfig-version=%s' % kw['atleast_pkgconfig_version']]
+ self.cmd_and_log(cmd, env=env)
+ return
+
+ # single version for a module
+ if 'modversion' in kw:
+ version = self.cmd_and_log(path + ['--modversion', kw['modversion']], env=env).strip()
+ if not 'okmsg' in kw:
+ kw['okmsg'] = version
+ self.define(kw['define_name'], version)
+ return version
+
+ lst = [] + path
+
+ defi = kw.get('define_variable')
+ if not defi:
+ defi = self.env.PKG_CONFIG_DEFINES or {}
+ for key, val in defi.items():
+ lst.append('--define-variable=%s=%s' % (key, val))
+
+ static = kw.get('force_static', False)
+ if 'args' in kw: