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
commit0fbf71650d271164b73e266c301fee4db50bd28a (patch)
tree4d5d22ad43852784bb598854305e0d6ae6cde76e
parentdbfdd29ce534dcacf069223612d8bb7febab40e0 (diff)
parente8c89d26cc9e90ab8fa8673bf4f72271549f4946 (diff)
downloadd2tk-0fbf71650d271164b73e266c301fee4db50bd28a.zip
d2tk-0fbf71650d271164b73e266c301fee4db50bd28a.tar.gz
d2tk-0fbf71650d271164b73e266c301fee4db50bd28a.tar.bz2
d2tk-0fbf71650d271164b73e266c301fee4db50bd28a.tar.xz
Merge commit 'e8c89d26cc9e90ab8fa8673bf4f72271549f4946' into pugl_upstream
-rw-r--r--pugl/.gitignore2
-rw-r--r--pugl/.gitlab-ci.yml104
-rw-r--r--pugl/AUTHORS17
-rw-r--r--pugl/COPYING2
-rw-r--r--pugl/Doxyfile.in23
-rw-r--r--pugl/README.md76
-rw-r--r--pugl/pugl.pc.in7
-rw-r--r--pugl/pugl/cairo_gl.h106
-rw-r--r--pugl/pugl/detail/implementation.c (renamed from pugl/pugl/pugl_internal.h)195
-rw-r--r--pugl/pugl/detail/implementation.h46
-rw-r--r--pugl/pugl/detail/mac.h59
-rw-r--r--pugl/pugl/detail/mac.m860
-rw-r--r--pugl/pugl/detail/mac_cairo.m166
-rw-r--r--pugl/pugl/detail/mac_gl.m184
-rw-r--r--pugl/pugl/detail/types.h107
-rw-r--r--pugl/pugl/detail/win.c697
-rw-r--r--pugl/pugl/detail/win.h100
-rw-r--r--pugl/pugl/detail/win_cairo.c199
-rw-r--r--pugl/pugl/detail/win_gl.c306
-rw-r--r--pugl/pugl/detail/x11.c588
-rw-r--r--pugl/pugl/detail/x11.h43
-rw-r--r--pugl/pugl/detail/x11_cairo.c176
-rw-r--r--pugl/pugl/detail/x11_gl.c218
-rw-r--r--pugl/pugl/gl.h1
-rw-r--r--pugl/pugl/glew.h3
-rw-r--r--pugl/pugl/glu.h3
-rw-r--r--pugl/pugl/pugl.h279
-rw-r--r--pugl/pugl/pugl.hpp11
-rw-r--r--pugl/pugl/pugl_cairo_backend.h42
-rw-r--r--pugl/pugl/pugl_gl_backend.h42
-rw-r--r--pugl/pugl/pugl_osx.m746
-rw-r--r--pugl/pugl/pugl_win.cpp704
-rw-r--r--pugl/pugl/pugl_x11.c810
-rw-r--r--pugl/pugl_test.c261
-rw-r--r--pugl/resources/Info.plist.in20
-rw-r--r--pugl/resources/pugl.ipe293
-rw-r--r--pugl/resources/pugl.pngbin0 -> 2641 bytes
-rw-r--r--pugl/resources/pugl.svg83
-rw-r--r--pugl/test/pugl_cairo_test.c (renamed from pugl/pugl_cairo_test.c)113
-rw-r--r--pugl/test/pugl_test.c229
-rw-r--r--pugl/test/test_utils.h167
-rwxr-xr-xpugl/wafbin97489 -> 404 bytes
-rw-r--r--pugl/waflib/.gitignore2
-rw-r--r--pugl/waflib/Build.py1496
-rw-r--r--pugl/waflib/COPYING25
-rw-r--r--pugl/waflib/ConfigSet.py361
-rw-r--r--pugl/waflib/Configure.py639
-rw-r--r--pugl/waflib/Context.py737
-rw-r--r--pugl/waflib/Errors.py68
-rw-r--r--pugl/waflib/Logs.py379
-rw-r--r--pugl/waflib/Node.py970
-rw-r--r--pugl/waflib/Options.py342
-rw-r--r--pugl/waflib/README.md24
-rw-r--r--pugl/waflib/Runner.py617
-rw-r--r--pugl/waflib/Scripting.py620
-rw-r--r--pugl/waflib/Task.py1406
-rw-r--r--pugl/waflib/TaskGen.py917
-rw-r--r--pugl/waflib/Tools/__init__.py3
-rw-r--r--pugl/waflib/Tools/ar.py24
-rw-r--r--pugl/waflib/Tools/asm.py73
-rw-r--r--pugl/waflib/Tools/bison.py49
-rw-r--r--pugl/waflib/Tools/c.py39
-rw-r--r--pugl/waflib/Tools/c_aliases.py144
-rw-r--r--pugl/waflib/Tools/c_config.py1351
-rw-r--r--pugl/waflib/Tools/c_osx.py193
-rw-r--r--pugl/waflib/Tools/c_preproc.py1091
-rw-r--r--pugl/waflib/Tools/c_tests.py229
-rw-r--r--pugl/waflib/Tools/ccroot.py791
-rw-r--r--pugl/waflib/Tools/clang.py29
-rw-r--r--pugl/waflib/Tools/clangxx.py30
-rw-r--r--pugl/waflib/Tools/compiler_c.py110
-rw-r--r--pugl/waflib/Tools/compiler_cxx.py111
-rw-r--r--pugl/waflib/Tools/compiler_d.py85
-rw-r--r--pugl/waflib/Tools/compiler_fc.py73
-rw-r--r--pugl/waflib/Tools/cs.py211
-rw-r--r--pugl/waflib/Tools/cxx.py40
-rw-r--r--pugl/waflib/Tools/d.py97
-rw-r--r--pugl/waflib/Tools/d_config.py64
-rw-r--r--pugl/waflib/Tools/d_scan.py211
-rw-r--r--pugl/waflib/Tools/dbus.py70
-rw-r--r--pugl/waflib/Tools/dmd.py80
-rw-r--r--pugl/waflib/Tools/errcheck.py237
-rw-r--r--pugl/waflib/Tools/fc.py203
-rw-r--r--pugl/waflib/Tools/fc_config.py488
-rw-r--r--pugl/waflib/Tools/fc_scan.py120
-rw-r--r--pugl/waflib/Tools/flex.py62
-rw-r--r--pugl/waflib/Tools/g95.py66
-rw-r--r--pugl/waflib/Tools/gas.py18
-rw-r--r--pugl/waflib/Tools/gcc.py156
-rw-r--r--pugl/waflib/Tools/gdc.py55
-rw-r--r--pugl/waflib/Tools/gfortran.py93
-rw-r--r--pugl/waflib/Tools/glib2.py489
-rw-r--r--pugl/waflib/Tools/gnu_dirs.py131
-rw-r--r--pugl/waflib/Tools/gxx.py157
-rw-r--r--pugl/waflib/Tools/icc.py30
-rw-r--r--pugl/waflib/Tools/icpc.py30
-rw-r--r--pugl/waflib/Tools/ifort.py413
-rw-r--r--pugl/waflib/Tools/intltool.py231
-rw-r--r--pugl/waflib/Tools/irixcc.py66
-rw-r--r--pugl/waflib/Tools/javaw.py579
-rw-r--r--pugl/waflib/Tools/ldc2.py56
-rw-r--r--pugl/waflib/Tools/lua.py38
-rw-r--r--pugl/waflib/Tools/md5_tstamp.py38
-rw-r--r--pugl/waflib/Tools/msvc.py1020
-rw-r--r--pugl/waflib/Tools/nasm.py26
-rw-r--r--pugl/waflib/Tools/nobuild.py24
-rw-r--r--pugl/waflib/Tools/perl.py156
-rw-r--r--pugl/waflib/Tools/python.py631
-rw-r--r--pugl/waflib/Tools/qt5.py796
-rw-r--r--pugl/waflib/Tools/ruby.py186
-rw-r--r--pugl/waflib/Tools/suncc.py67
-rw-r--r--pugl/waflib/Tools/suncxx.py67
-rw-r--r--pugl/waflib/Tools/tex.py543
-rw-r--r--pugl/waflib/Tools/vala.py355
-rw-r--r--pugl/waflib/Tools/waf_unit_test.py296
-rw-r--r--pugl/waflib/Tools/winres.py78
-rw-r--r--pugl/waflib/Tools/xlc.py65
-rw-r--r--pugl/waflib/Tools/xlcxx.py65
-rw-r--r--pugl/waflib/Utils.py1029
-rw-r--r--pugl/waflib/__init__.py3
-rw-r--r--pugl/waflib/ansiterm.py342
-rw-r--r--pugl/waflib/extras/__init__.py3
-rw-r--r--pugl/waflib/extras/autowaf.py1452
-rw-r--r--pugl/waflib/extras/batched_cc.py173
-rw-r--r--pugl/waflib/extras/biber.py58
-rw-r--r--pugl/waflib/extras/bjam.py128
-rw-r--r--pugl/waflib/extras/blender.py108
-rw-r--r--pugl/waflib/extras/boo.py81
-rw-r--r--pugl/waflib/extras/boost.py525
-rw-r--r--pugl/waflib/extras/build_file_tracker.py28
-rw-r--r--pugl/waflib/extras/build_logs.py110
-rw-r--r--pugl/waflib/extras/buildcopy.py85
-rw-r--r--pugl/waflib/extras/c_bgxlc.py32
-rw-r--r--pugl/waflib/extras/c_dumbpreproc.py72
-rw-r--r--pugl/waflib/extras/c_emscripten.py87
-rw-r--r--pugl/waflib/extras/c_nec.py74
-rw-r--r--pugl/waflib/extras/cabal.py152
-rw-r--r--pugl/waflib/extras/cfg_altoptions.py110
-rw-r--r--pugl/waflib/extras/clang_compilation_database.py85
-rw-r--r--pugl/waflib/extras/codelite.py875
-rw-r--r--pugl/waflib/extras/color_gcc.py39
-rw-r--r--pugl/waflib/extras/color_rvct.py51
-rw-r--r--pugl/waflib/extras/compat15.py406
-rw-r--r--pugl/waflib/extras/cppcheck.py591
-rw-r--r--pugl/waflib/extras/cpplint.py209
-rw-r--r--pugl/waflib/extras/cross_gnu.py227
-rw-r--r--pugl/waflib/extras/cython.py147
-rw-r--r--pugl/waflib/extras/dcc.py72
-rw-r--r--pugl/waflib/extras/distnet.py430
-rw-r--r--pugl/waflib/extras/doxygen.py227
-rw-r--r--pugl/waflib/extras/dpapi.py87
-rw-r--r--pugl/waflib/extras/eclipse.py431
-rw-r--r--pugl/waflib/extras/erlang.py110
-rw-r--r--pugl/waflib/extras/fast_partial.py518
-rw-r--r--pugl/waflib/extras/fc_bgxlf.py32
-rw-r--r--pugl/waflib/extras/fc_cray.py51
-rw-r--r--pugl/waflib/extras/fc_nag.py61
-rw-r--r--pugl/waflib/extras/fc_nec.py60
-rw-r--r--pugl/waflib/extras/fc_nfort.py52
-rw-r--r--pugl/waflib/extras/fc_open64.py58
-rw-r--r--pugl/waflib/extras/fc_pgfortran.py68
-rw-r--r--pugl/waflib/extras/fc_solstudio.py62
-rw-r--r--pugl/waflib/extras/fc_xlf.py63
-rw-r--r--pugl/waflib/extras/file_to_object.py137
-rw-r--r--pugl/waflib/extras/fluid.py30
-rw-r--r--pugl/waflib/extras/freeimage.py74
-rw-r--r--pugl/waflib/extras/fsb.py31
-rw-r--r--pugl/waflib/extras/fsc.py64
-rw-r--r--pugl/waflib/extras/gccdeps.py214
-rw-r--r--pugl/waflib/extras/gdbus.py87
-rw-r--r--pugl/waflib/extras/gob2.py17
-rw-r--r--pugl/waflib/extras/halide.py151
-rwxr-xr-xpugl/waflib/extras/javatest.py118
-rw-r--r--pugl/waflib/extras/kde4.py93
-rw-r--r--pugl/waflib/extras/local_rpath.py19
-rw-r--r--pugl/waflib/extras/lv2.py75
-rw-r--r--pugl/waflib/extras/make.py142
-rw-r--r--pugl/waflib/extras/midl.py69
-rw-r--r--pugl/waflib/extras/msvcdeps.py256
-rw-r--r--pugl/waflib/extras/msvs.py1048
-rw-r--r--pugl/waflib/extras/netcache_client.py390
-rw-r--r--pugl/waflib/extras/objcopy.py50
-rw-r--r--pugl/waflib/extras/ocaml.py348
-rw-r--r--pugl/waflib/extras/package.py76
-rw-r--r--pugl/waflib/extras/parallel_debug.py462
-rw-r--r--pugl/waflib/extras/pch.py148
-rw-r--r--pugl/waflib/extras/pep8.py106
-rw-r--r--pugl/waflib/extras/pgicc.py75
-rw-r--r--pugl/waflib/extras/pgicxx.py20
-rw-r--r--pugl/waflib/extras/proc.py54
-rw-r--r--pugl/waflib/extras/protoc.py223
-rw-r--r--pugl/waflib/extras/pyqt5.py241
-rw-r--r--pugl/waflib/extras/pytest.py225
-rw-r--r--pugl/waflib/extras/qnxnto.py72
-rw-r--r--pugl/waflib/extras/qt4.py695
-rw-r--r--pugl/waflib/extras/relocation.py85
-rw-r--r--pugl/waflib/extras/remote.py327
-rw-r--r--pugl/waflib/extras/resx.py35
-rw-r--r--pugl/waflib/extras/review.py325
-rw-r--r--pugl/waflib/extras/rst.py260
-rw-r--r--pugl/waflib/extras/run_do_script.py139
-rw-r--r--pugl/waflib/extras/run_m_script.py88
-rw-r--r--pugl/waflib/extras/run_py_script.py104
-rw-r--r--pugl/waflib/extras/run_r_script.py86
-rw-r--r--pugl/waflib/extras/sas.py71
-rw-r--r--pugl/waflib/extras/satellite_assembly.py57
-rw-r--r--pugl/waflib/extras/scala.py128
-rw-r--r--pugl/waflib/extras/slow_qt4.py96
-rw-r--r--pugl/waflib/extras/softlink_libs.py76
-rw-r--r--pugl/waflib/extras/stale.py98
-rw-r--r--pugl/waflib/extras/stracedeps.py174
-rw-r--r--pugl/waflib/extras/swig.py237
-rw-r--r--pugl/waflib/extras/syms.py84
-rw-r--r--pugl/waflib/extras/ticgt.py300
-rw-r--r--pugl/waflib/extras/unity.py108
-rw-r--r--pugl/waflib/extras/use_config.py185
-rw-r--r--pugl/waflib/extras/valadoc.py140
-rw-r--r--pugl/waflib/extras/waf_xattr.py150
-rw-r--r--pugl/waflib/extras/why.py78
-rw-r--r--pugl/waflib/extras/win32_opts.py170
-rw-r--r--pugl/waflib/extras/wix.py87
-rw-r--r--pugl/waflib/extras/xcode6.py727
-rw-r--r--pugl/waflib/fixpy2.py64
-rwxr-xr-xpugl/waflib/processor.py64
-rwxr-xr-xpugl/waflib/waf16
-rw-r--r--pugl/wscript385
226 files changed, 47398 insertions, 3040 deletions
diff --git a/pugl/.gitignore b/pugl/.gitignore
index e511642..dad71c3 100644
--- a/pugl/.gitignore
+++ b/pugl/.gitignore
@@ -1,3 +1,5 @@
.waf*/
build/
.lock-waf*
+__pycache__
+*.pyc
diff --git a/pugl/.gitlab-ci.yml b/pugl/.gitlab-ci.yml
new file mode 100644
index 0000000..4fe8cf9
--- /dev/null
+++ b/pugl/.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/pugl/AUTHORS b/pugl/AUTHORS
index 5625baa..6c09a5b 100644
--- a/pugl/AUTHORS
+++ b/pugl/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/pugl/COPYING b/pugl/COPYING
index e1e203d..b16a139 100644
--- a/pugl/COPYING
+++ b/pugl/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/pugl/Doxyfile.in b/pugl/Doxyfile.in
index 59e3331..b332116 100644
--- a/pugl/Doxyfile.in
+++ b/pugl/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/pugl/README.md b/pugl/README.md
index 77809d8..8169f79 100644
--- a/pugl/README.md
+++ b/pugl/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/pugl.pc.in b/pugl/pugl.pc.in
index d3606cd..531cd54 100644
--- a/pugl/pugl.pc.in
+++ b/pugl/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/pugl/cairo_gl.h b/pugl/pugl/cairo_gl.h
deleted file mode 100644
index fb4cb2a..0000000
--- a/pugl/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/pugl_internal.h b/pugl/pugl/detail/implementation.c
index 5976cd7..7eeba01 100644
--- a/pugl/pugl/pugl_internal.h
+++ b/pugl/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/pugl/detail/implementation.h b/pugl/pugl/detail/implementation.h
new file mode 100644
index 0000000..50f54f5
--- /dev/null
+++ b/pugl/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/pugl/detail/mac.h b/pugl/pugl/detail/mac.h
new file mode 100644
index 0000000..9a5997d
--- /dev/null
+++ b/pugl/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/pugl/detail/mac.m b/pugl/pugl/detail/mac.m
new file mode 100644
index 0000000..b1bf4fd
--- /dev/null
+++ b/pugl/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/pugl/detail/mac_cairo.m b/pugl/pugl/detail/mac_cairo.m
new file mode 100644
index 0000000..b0a8db5
--- /dev/null
+++ b/pugl/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/pugl/detail/mac_gl.m b/pugl/pugl/detail/mac_gl.m
new file mode 100644
index 0000000..4a6e39a
--- /dev/null
+++ b/pugl/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/pugl/detail/types.h b/pugl/pugl/detail/types.h
new file mode 100644
index 0000000..60f7682
--- /dev/null
+++ b/pugl/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/pugl/detail/win.c b/pugl/pugl/detail/win.c
new file mode 100644
index 0000000..93dce9d
--- /dev/null
+++ b/pugl/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/pugl/detail/win.h b/pugl/pugl/detail/win.h
new file mode 100644
index 0000000..9af5cbb
--- /dev/null
+++ b/pugl/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/pugl/detail/win_cairo.c b/pugl/pugl/detail/win_cairo.c
new file mode 100644
index 0000000..49175c1
--- /dev/null
+++ b/pugl/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/pugl/detail/win_gl.c b/pugl/pugl/detail/win_gl.c
new file mode 100644
index 0000000..17ee68d
--- /dev/null
+++ b/pugl/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/pugl/detail/x11.c b/pugl/pugl/detail/x11.c
new file mode 100644
index 0000000..d6461a2
--- /dev/null
+++ b/pugl/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/pugl/detail/x11.h b/pugl/pugl/detail/x11.h
new file mode 100644
index 0000000..1ead119
--- /dev/null
+++ b/pugl/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/pugl/detail/x11_cairo.c b/pugl/pugl/detail/x11_cairo.c
new file mode 100644
index 0000000..d5e2ed7
--- /dev/null
+++ b/pugl/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/pugl/detail/x11_gl.c b/pugl/pugl/detail/x11_gl.c
new file mode 100644
index 0000000..85c18b4
--- /dev/null
+++ b/pugl/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/pugl/gl.h b/pugl/pugl/gl.h
index 9a6aeef..55a55c4 100644
--- a/pugl/pugl/gl.h
+++ b/pugl/pugl/gl.h
@@ -29,4 +29,3 @@
# endif
# include "GL/gl.h"
#endif
-
diff --git a/pugl/pugl/glew.h b/pugl/pugl/glew.h
index 5374406..f26ff93 100644
--- a/pugl/pugl/glew.h
+++ b/pugl/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/pugl/glu.h b/pugl/pugl/glu.h
index 0d3e8e1..0ade70c 100644
--- a/pugl/pugl/glu.h
+++ b/pugl/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/pugl.h b/pugl/pugl/pugl.h
index dbbad90..d04f1aa 100644
--- a/pugl/pugl/pugl.h
+++ b/pugl/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/pugl.hpp b/pugl/pugl/pugl.hpp
index 7d3ea9e..c2602dd 100644
--- a/pugl/pugl/pugl.hpp
+++ b/pugl/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/pugl_cairo_backend.h b/pugl/pugl/pugl_cairo_backend.h
new file mode 100644
index 0000000..3330c08
--- /dev/null
+++ b/pugl/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/pugl_gl_backend.h b/pugl/pugl/pugl_gl_backend.h
new file mode 100644
index 0000000..5913b95
--- /dev/null
+++ b/pugl/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/pugl_osx.m b/pugl/pugl/pugl_osx.m
deleted file mode 100644
index 014d553..0000000
--- a/pugl/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/pugl_win.cpp b/pugl/pugl/pugl_win.cpp
deleted file mode 100644
index 6468963..0000000
--- a/pugl/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/pugl_x11.c b/pugl/pugl/pugl_x11.c
deleted file mode 100644
index 5afc880..0000000
--- a/pugl/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/pugl_test.c b/pugl/pugl_test.c
deleted file mode 100644
index 367e7a4..0000000
--- a/pugl/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/pugl/resources/Info.plist.in b/pugl/resources/Info.plist.in
new file mode 100644
index 0000000..a08dbd0
--- /dev/null
+++ b/pugl/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/pugl/resources/pugl.ipe b/pugl/resources/pugl.ipe
new file mode 100644
index 0000000..238c09c
--- /dev/null
+++ b/pugl/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/pugl/resources/pugl.png b/pugl/resources/pugl.png
new file mode 100644
index 0000000..4641660
--- /dev/null
+++ b/pugl/resources/pugl.png
Binary files differ
diff --git a/pugl/resources/pugl.svg b/pugl/resources/pugl.svg
new file mode 100644
index 0000000..5bb5335
--- /dev/null
+++ b/pugl/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/pugl_cairo_test.c b/pugl/test/pugl_cairo_test.c
index c04c785..a16c821 100644
--- a/pugl/pugl_cairo_test.c
+++ b/pugl/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/pugl/test/pugl_test.c b/pugl/test/pugl_test.c
new file mode 100644
index 0000000..b8a0a42
--- /dev/null
+++ b/pugl/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/pugl/test/test_utils.h b/pugl/test/test_utils.h
new file mode 100644
index 0000000..9738d96
--- /dev/null
+++ b/pugl/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/pugl/waf b/pugl/waf
index 2dbb06b..e22930a 100755
--- a/pugl/waf
+++ b/pugl/waf
Binary files differ
diff --git a/pugl/waflib/.gitignore b/pugl/waflib/.gitignore
new file mode 100644
index 0000000..8d35cb3
--- /dev/null
+++ b/pugl/waflib/.gitignore
@@ -0,0 +1,2 @@
+__pycache__
+*.pyc
diff --git a/pugl/waflib/Build.py b/pugl/waflib/Build.py
new file mode 100644
index 0000000..8143dbc
--- /dev/null
+++ b/pugl/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/pugl/waflib/COPYING b/pugl/waflib/COPYING
new file mode 100644
index 0000000..a4147d2
--- /dev/null
+++ b/pugl/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/pugl/waflib/ConfigSet.py b/pugl/waflib/ConfigSet.py
new file mode 100644
index 0000000..901fba6
--- /dev/null
+++ b/pugl/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/pugl/waflib/Configure.py b/pugl/waflib/Configure.py
new file mode 100644
index 0000000..db09c0e
--- /dev/null
+++ b/pugl/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/pugl/waflib/Context.py b/pugl/waflib/Context.py
new file mode 100644
index 0000000..876ea46
--- /dev/null
+++ b/pugl/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/pugl/waflib/Errors.py b/pugl/waflib/Errors.py
new file mode 100644
index 0000000..bf75c1b
--- /dev/null
+++ b/pugl/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/pugl/waflib/Logs.py b/pugl/waflib/Logs.py
new file mode 100644
index 0000000..11dc34f
--- /dev/null
+++ b/pugl/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/pugl/waflib/Node.py b/pugl/waflib/Node.py
new file mode 100644
index 0000000..4ac1ea8
--- /dev/null
+++ b/pugl/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/pugl/waflib/Options.py b/pugl/waflib/Options.py
new file mode 100644
index 0000000..ad802d4
--- /dev/null
+++ b/pugl/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/pugl/waflib/README.md b/pugl/waflib/README.md
new file mode 100644
index 0000000..c5361b9
--- /dev/null
+++ b/pugl/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/pugl/waflib/Runner.py b/pugl/waflib/Runner.py
new file mode 100644
index 0000000..5d27669
--- /dev/null
+++ b/pugl/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/pugl/waflib/Scripting.py b/pugl/waflib/Scripting.py
new file mode 100644
index 0000000..ae17a8b
--- /dev/null
+++ b/pugl/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/pugl/waflib/Task.py b/pugl/waflib/Task.py
new file mode 100644
index 0000000..cb49a73
--- /dev/null
+++ b/pugl/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/pugl/waflib/TaskGen.py b/pugl/waflib/TaskGen.py
new file mode 100644
index 0000000..532b7d5
--- /dev/null
+++ b/pugl/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/pugl/waflib/Tools/__init__.py b/pugl/waflib/Tools/__init__.py
new file mode 100644
index 0000000..079df35
--- /dev/null
+++ b/pugl/waflib/Tools/__init__.py
@@ -0,0 +1,3 @@
+#!/usr/bin/env python
+# encoding: utf-8
+# Thomas Nagy, 2005-2018 (ita)
diff --git a/pugl/waflib/Tools/ar.py b/pugl/waflib/Tools/ar.py
new file mode 100644
index 0000000..b39b645
--- /dev/null
+++ b/pugl/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/pugl/waflib/Tools/asm.py b/pugl/waflib/Tools/asm.py
new file mode 100644
index 0000000..b6f26fb
--- /dev/null
+++ b/pugl/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/pugl/waflib/Tools/bison.py b/pugl/waflib/Tools/bison.py
new file mode 100644
index 0000000..eef56dc
--- /dev/null
+++ b/pugl/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/pugl/waflib/Tools/c.py b/pugl/waflib/Tools/c.py
new file mode 100644
index 0000000..effd6b6
--- /dev/null
+++ b/pugl/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/pugl/waflib/Tools/c_aliases.py b/pugl/waflib/Tools/c_aliases.py
new file mode 100644
index 0000000..c9d5369
--- /dev/null
+++ b/pugl/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/pugl/waflib/Tools/c_config.py b/pugl/waflib/Tools/c_config.py
new file mode 100644
index 0000000..d546be9
--- /dev/null
+++ b/pugl/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:
+ args = Utils.to_list(kw['args'])
+ if '--static' in args or '--static-libs' in args:
+ static = True
+ lst += args
+
+ # tools like pkgconf expect the package argument after the -- ones -_-
+ lst.extend(Utils.to_list(kw['package']))
+
+ # retrieving variables of a module
+ if 'variables' in kw:
+ v_env = kw.get('env', self.env)
+ vars = Utils.to_list(kw['variables'])
+ for v in vars:
+ val = self.cmd_and_log(lst + ['--variable=' + v], env=env).strip()
+ var = '%s_%s' % (kw['uselib_store'], v)
+ v_env[var] = val
+ return
+
+ # so we assume the command-line will output flags to be parsed afterwards
+ ret = self.cmd_and_log(lst, env=env)
+
+ define_it()
+ self.parse_flags(ret, kw['uselib_store'], kw.get('env', self.env), force_static=static, posix=kw.get('posix'))
+ return ret
+
+@conf
+def check_cfg(self, *k, **kw):
+ """
+ Checks for configuration flags using a **-config**-like program (pkg-config, sdl-config, etc).
+ This wraps internal calls to :py:func:`waflib.Tools.c_config.validate_cfg` and :py:func:`waflib.Tools.c_config.exec_cfg`
+
+ A few examples::
+
+ def configure(conf):
+ conf.load('compiler_c')
+ conf.check_cfg(package='glib-2.0', args='--libs --cflags')
+ conf.check_cfg(package='pango')
+ conf.check_cfg(package='pango', uselib_store='MYPANGO', args=['--cflags', '--libs'])
+ conf.check_cfg(package='pango',
+ args=['pango >= 0.1.0', 'pango < 9.9.9', '--cflags', '--libs'],
+ msg="Checking for 'pango 0.1.0'")
+ conf.check_cfg(path='sdl-config', args='--cflags --libs', package='', uselib_store='SDL')
+ conf.check_cfg(path='mpicc', args='--showme:compile --showme:link',
+ package='', uselib_store='OPEN_MPI', mandatory=False)
+ # variables
+ conf.check_cfg(package='gtk+-2.0', variables=['includedir', 'prefix'], uselib_store='FOO')
+ print(conf.env.FOO_includedir)
+ """
+ self.validate_cfg(kw)
+ if 'msg' in kw:
+ self.start_msg(kw['msg'], **kw)
+ ret = None
+ try:
+ ret = self.exec_cfg(kw)
+ except self.errors.WafError as e:
+ if 'errmsg' in kw:
+ self.end_msg(kw['errmsg'], 'YELLOW', **kw)
+ if Logs.verbose > 1:
+ self.to_log('Command failure: %s' % e)
+ self.fatal('The configuration failed')
+ else:
+ if not ret:
+ ret = True
+ kw['success'] = ret
+ if 'okmsg' in kw:
+ self.end_msg(self.ret_msg(kw['okmsg'], kw), **kw)
+
+ return ret
+
+def build_fun(bld):
+ """
+ Build function that is used for running configuration tests with ``conf.check()``
+ """
+ if bld.kw['compile_filename']:
+ node = bld.srcnode.make_node(bld.kw['compile_filename'])
+ node.write(bld.kw['code'])
+
+ o = bld(features=bld.kw['features'], source=bld.kw['compile_filename'], target='testprog')
+
+ for k, v in bld.kw.items():
+ setattr(o, k, v)
+
+ if not bld.kw.get('quiet'):
+ bld.conf.to_log("==>\n%s\n<==" % bld.kw['code'])
+
+@conf
+def validate_c(self, kw):
+ """
+ Pre-checks the parameters that will be given to :py:func:`waflib.Configure.run_build`
+
+ :param compiler: c or cxx (tries to guess what is best)
+ :type compiler: string
+ :param type: cprogram, cshlib, cstlib - not required if *features are given directly*
+ :type type: binary to create
+ :param feature: desired features for the task generator that will execute the test, for example ``cxx cxxstlib``
+ :type feature: list of string
+ :param fragment: provide a piece of code for the test (default is to let the system create one)
+ :type fragment: string
+ :param uselib_store: define variables after the test is executed (IMPORTANT!)
+ :type uselib_store: string
+ :param use: parameters to use for building (just like the normal *use* keyword)
+ :type use: list of string
+ :param define_name: define to set when the check is over
+ :type define_name: string
+ :param execute: execute the resulting binary
+ :type execute: bool
+ :param define_ret: if execute is set to True, use the execution output in