aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorHanspeter Portner <dev@open-music-kontrollers.ch>2020-04-09 00:10:50 +0200
committerHanspeter Portner <dev@open-music-kontrollers.ch>2020-04-09 00:10:50 +0200
commit96248e979953d93bea7e07c54c5dae570533acf8 (patch)
treed1a24bd466bd7e63b2170f005785488bfda88d6c
parentde04cad026d0979f5f4a00fe6b359d7698776d65 (diff)
downloadd2tk.lv2-96248e979953d93bea7e07c54c5dae570533acf8.tar.xz
Squashed 'subprojects/d2tk/' changes from 3e533231..dfd8b473
dfd8b473 meson: put -DGLEW_STATIC into glew dependency. 8fc55de6 pugl: fixes for updated pugl. c2d6c830 Merge commit '8f01dade6c23c2cc0264c3b985e3574d74a9fa19' 8f01dade Squashed 'pugl/' changes from bdb46d41..8f28d8c9 08cd518b pugl: disable X11 threads. 0b9eb914 base: always draw a minimal 1px bar. 50912baf pugl: do not trigger redisplay in PUGL_CONFIGURE. 37cbc3af base: beautify spinner widget. 6a3493e4 test: add .end driver method to test mock. 6184c718 Squashed 'pugl/' changes from ba4e39fd..bdb46d41 47152e67 Merge commit '6184c718b9dfff026a20c2c7fae305fcd7db19c8' e5d92f2b pugl: handle return/tab as non-special keys. 6840357a cairo: always copy tmp surf to pugl's front surf. 84d9d269 fix some memleaks. 43a25c8c test: add context callback to mock driver. 0fbbf9a6 cairo: fixes for upd. pugl, dedicated cairo surf. 6a9c35a6 initial fixes for updated pugl. a4c26ce5 Squashed 'pugl/' changes from 630aed14..ba4e39fd 1c921bb1 Merge commit 'a4c26ce5fc2b2188f32b4fab24727510834d3e68' into feature-pugl-upstream e6bc8b10 base: add float spinner. cbf5da34 frontend: merge pugl/fbdev headers where possible. git-subtree-dir: subprojects/d2tk git-subtree-split: dfd8b47337ef183cd3f47f1a1a0e46051f9790f2
-rw-r--r--VERSION2
-rw-r--r--d2tk/base.h18
-rw-r--r--d2tk/core.h4
-rw-r--r--d2tk/frontend.h63
-rw-r--r--d2tk/frontend_fbdev.h20
-rw-r--r--d2tk/frontend_pugl.h35
-rw-r--r--example/d2tk_fbdev.c10
-rw-r--r--example/d2tk_pugl.c12
-rw-r--r--example/example.c36
-rw-r--r--meson.build2
-rw-r--r--pugl/.clang-format127
-rw-r--r--pugl/.clang-tidy16
-rw-r--r--pugl/.gitattributes1
-rw-r--r--pugl/.gitlab-ci.yml15
-rw-r--r--pugl/.gitmodules3
-rw-r--r--pugl/AUTHORS3
-rw-r--r--pugl/COPYING2
-rw-r--r--pugl/README.md35
-rw-r--r--pugl/doc/header.html2
-rw-r--r--pugl/doc/layout.xml2
-rw-r--r--pugl/doc/mainpage.md76
-rw-r--r--pugl/doc/reference.doxygen.in8
-rw-r--r--pugl/doc/style.css822
-rw-r--r--pugl/examples/cube_view.h82
-rw-r--r--pugl/examples/demo_utils.h175
-rw-r--r--pugl/examples/glad/glad.c (renamed from pugl/test/glad/glad.c)132
-rw-r--r--pugl/examples/glad/glad.h (renamed from pugl/test/glad/glad.h)200
-rw-r--r--pugl/examples/glad/khrplatform.h (renamed from pugl/test/glad/khrplatform.h)0
-rw-r--r--pugl/examples/pugl_cairo_demo.c (renamed from pugl/test/pugl_cairo_test.c)154
-rw-r--r--pugl/examples/pugl_embed_demo.c (renamed from pugl/test/pugl_test.c)196
-rw-r--r--pugl/examples/pugl_gl3_demo.c432
-rw-r--r--pugl/examples/pugl_print_events.c79
-rw-r--r--pugl/examples/pugl_window_demo.c252
-rw-r--r--pugl/examples/shader_utils.h (renamed from pugl/test/shader_utils.h)0
-rw-r--r--pugl/pugl/detail/implementation.c219
-rw-r--r--pugl/pugl/detail/implementation.h23
-rw-r--r--pugl/pugl/detail/mac.h9
-rw-r--r--pugl/pugl/detail/mac.m468
-rw-r--r--pugl/pugl/detail/mac_cairo.m41
-rw-r--r--pugl/pugl/detail/mac_gl.m69
-rw-r--r--pugl/pugl/detail/mac_stub.m95
-rw-r--r--pugl/pugl/detail/types.h24
-rw-r--r--pugl/pugl/detail/win.c326
-rw-r--r--pugl/pugl/detail/win.h46
-rw-r--r--pugl/pugl/detail/win_cairo.c132
-rw-r--r--pugl/pugl/detail/win_gl.c80
-rw-r--r--pugl/pugl/detail/x11.c615
-rw-r--r--pugl/pugl/detail/x11.h34
-rw-r--r--pugl/pugl/detail/x11_cairo.c157
-rw-r--r--pugl/pugl/detail/x11_gl.c102
-rw-r--r--pugl/pugl/pugl.h1205
-rw-r--r--pugl/pugl/pugl.hpp23
-rw-r--r--pugl/pugl/pugl_cairo.h49
-rw-r--r--pugl/pugl/pugl_cairo_backend.h29
-rw-r--r--pugl/pugl/pugl_gl.h60
-rw-r--r--pugl/pugl/pugl_gl_backend.h29
-rw-r--r--pugl/pugl/pugl_stub.h103
-rw-r--r--pugl/pugl/pugl_stub_backend.h (renamed from pugl/pugl/glew.h)20
-rw-r--r--pugl/shaders/rect.frag35
-rw-r--r--pugl/shaders/rect.vert34
-rw-r--r--pugl/test/pugl_gl3_test.c395
-rw-r--r--pugl/test/test_redisplay.c132
-rw-r--r--pugl/test/test_show_hide.c147
-rw-r--r--pugl/test/test_timer.c160
-rw-r--r--pugl/test/test_update.c124
-rw-r--r--pugl/test/test_utils.h322
-rwxr-xr-xpugl/waf17
-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/wscript245
-rw-r--r--src/backend_cairo.c93
-rw-r--r--src/backend_nanovg.c22
-rw-r--r--src/base.c6
-rw-r--r--src/base_bar.c100
-rw-r--r--src/base_pty.c17
-rw-r--r--src/base_spinner.c288
-rw-r--r--src/core.c8
-rw-r--r--src/core_internal.h7
-rw-r--r--src/frontend_fbdev.c105
-rw-r--r--src/frontend_pugl.c224
-rw-r--r--test/base.c28
-rw-r--r--test/core.c52
-rw-r--r--test/mock.c28
264 files changed, 6770 insertions, 44758 deletions
diff --git a/VERSION b/VERSION
index a688425..4eac5b4 100644
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-0.1.1017
+0.1.1059
diff --git a/d2tk/base.h b/d2tk/base.h
index 98c2f48..4e4238c 100644
--- a/d2tk/base.h
+++ b/d2tk/base.h
@@ -580,6 +580,20 @@ d2tk_base_bar_int32(d2tk_base_t *base, d2tk_id_t id, const d2tk_rect_t *rect,
#define d2tk_base_bar_int32_is_changed(...) \
d2tk_state_is_changed(d2tk_base_bar_int32(__VA_ARGS__))
+D2TK_API d2tk_state_t
+d2tk_base_spinner_float(d2tk_base_t *base, d2tk_id_t id, const d2tk_rect_t *rect,
+ float min, float *value, float max);
+
+#define d2tk_base_spinner_float_is_changed(...) \
+ d2tk_state_is_changed(d2tk_base_spinner_float(__VA_ARGS__))
+
+D2TK_API d2tk_state_t
+d2tk_base_bar_float(d2tk_base_t *base, d2tk_id_t id, const d2tk_rect_t *rect,
+ float min, float *value, float max);
+
+#define d2tk_base_bar_float_is_changed(...) \
+ d2tk_state_is_changed(d2tk_base_bar_float(__VA_ARGS__))
+
D2TK_API d2tk_flowmatrix_t *
d2tk_flowmatrix_begin(d2tk_base_t *base, const d2tk_rect_t *rect, d2tk_id_t id,
d2tk_flowmatrix_t *flowmatrix);
@@ -669,8 +683,8 @@ d2tk_base_set_ttls(d2tk_base_t *base, uint32_t sprites, uint32_t memcaches);
D2TK_API void
d2tk_base_free(d2tk_base_t *base);
-D2TK_API void
-d2tk_base_pre(d2tk_base_t *base);
+D2TK_API int
+d2tk_base_pre(d2tk_base_t *base, void *pctx);
D2TK_API void
d2tk_base_post(d2tk_base_t *base);
diff --git a/d2tk/core.h b/d2tk/core.h
index 8de5cc5..0b8049b 100644
--- a/d2tk/core.h
+++ b/d2tk/core.h
@@ -186,8 +186,8 @@ d2tk_core_custom(d2tk_core_t *core, const d2tk_rect_t *rect, uint32_t size,
D2TK_API void
d2tk_core_stroke_width(d2tk_core_t *core, d2tk_coord_t width);
-D2TK_API void
-d2tk_core_pre(d2tk_core_t *core);
+D2TK_API int
+d2tk_core_pre(d2tk_core_t *core, void *pctx);
D2TK_API void
d2tk_core_post(d2tk_core_t *core);
diff --git a/d2tk/frontend.h b/d2tk/frontend.h
new file mode 100644
index 0000000..eaa2b4a
--- /dev/null
+++ b/d2tk/frontend.h
@@ -0,0 +1,63 @@
+/*
+ * Copyright (c) 2018-2019 Hanspeter Portner (dev@open-music-kontrollers.ch)
+ *
+ * This is free software: you can redistribute it and/or modify
+ * it under the terms of the Artistic License 2.0 as published by
+ * The Perl Foundation.
+ *
+ * This source is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * Artistic License 2.0 for more details.
+ *
+ * You should have received a copy of the Artistic License 2.0
+ * along the source as a COPYING file. If not, obtain it from
+ * http://www.perlfoundation.org/artistic_license_2_0.
+ */
+
+#ifndef _D2TK_FRONTEND_H
+#define _D2TK_FRONTEND_H
+
+#include <signal.h>
+
+#include <d2tk/base.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef int (*d2tk_frontend_expose_t)(void *data, d2tk_coord_t w, d2tk_coord_t h);
+typedef struct _d2tk_frontend_t d2tk_frontend_t;
+
+D2TK_API void
+d2tk_frontend_free(d2tk_frontend_t *dpugl);
+
+D2TK_API int
+d2tk_frontend_step(d2tk_frontend_t *dpugl);
+
+D2TK_API int
+d2tk_frontend_poll(d2tk_frontend_t *dpugl, double timeout);
+
+D2TK_API void
+d2tk_frontend_run(d2tk_frontend_t *dpugl, const sig_atomic_t *done);
+
+D2TK_API void
+d2tk_frontend_redisplay(d2tk_frontend_t *dpugl);
+
+D2TK_API int
+d2tk_frontend_set_size(d2tk_frontend_t *dpugl, d2tk_coord_t w, d2tk_coord_t h);
+
+D2TK_API int
+d2tk_frontend_get_size(d2tk_frontend_t *dpugl, d2tk_coord_t *w, d2tk_coord_t *h);
+
+D2TK_API d2tk_base_t *
+d2tk_frontend_get_base(d2tk_frontend_t *dpugl);
+
+D2TK_API float
+d2tk_frontend_get_scale();
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif // _D2TK_FRONTEND_H
diff --git a/d2tk/frontend_fbdev.h b/d2tk/frontend_fbdev.h
index 06a2907..969c644 100644
--- a/d2tk/frontend_fbdev.h
+++ b/d2tk/frontend_fbdev.h
@@ -21,38 +21,24 @@
#include <signal.h>
#include <d2tk/base.h>
+#include <d2tk/frontend.h>
#ifdef __cplusplus
extern "C" {
#endif
-typedef int (*d2tk_fbdev_expose_t)(void *data, d2tk_coord_t w, d2tk_coord_t h);
-
-typedef struct _d2tk_fbdev_t d2tk_fbdev_t;
typedef struct _d2tk_fbdev_config_t d2tk_fbdev_config_t;
struct _d2tk_fbdev_config_t {
const char *fb_device;
const char *bundle_path;
- d2tk_fbdev_expose_t expose;
+ d2tk_frontend_expose_t expose;
void *data;
};
-D2TK_API d2tk_fbdev_t *
+D2TK_API d2tk_frontend_t *
d2tk_fbdev_new(const d2tk_fbdev_config_t *config);
-D2TK_API void
-d2tk_fbdev_free(d2tk_fbdev_t *fbdev);
-
-D2TK_API int
-d2tk_fbdev_step(d2tk_fbdev_t *fbdev);
-
-D2TK_API void
-d2tk_fbdev_run(d2tk_fbdev_t *fbdev, const sig_atomic_t *done);
-
-D2TK_API d2tk_base_t *
-d2tk_fbdev_get_base(d2tk_fbdev_t *fbdev);
-
#ifdef __cplusplus
}
#endif
diff --git a/d2tk/frontend_pugl.h b/d2tk/frontend_pugl.h
index abec33d..4be0b86 100644
--- a/d2tk/frontend_pugl.h
+++ b/d2tk/frontend_pugl.h
@@ -21,14 +21,12 @@
#include <signal.h>
#include <d2tk/base.h>
+#include <d2tk/frontend.h>
#ifdef __cplusplus
extern "C" {
#endif
-typedef int (*d2tk_pugl_expose_t)(void *data, d2tk_coord_t w, d2tk_coord_t h);
-
-typedef struct _d2tk_pugl_t d2tk_pugl_t;
typedef struct _d2tk_pugl_config_t d2tk_pugl_config_t;
struct _d2tk_pugl_config_t {
@@ -40,40 +38,13 @@ struct _d2tk_pugl_config_t {
d2tk_coord_t h;
bool fixed_size;
bool fixed_aspect;
- d2tk_pugl_expose_t expose;
+ d2tk_frontend_expose_t expose;
void *data;
};
-D2TK_API d2tk_pugl_t *
+D2TK_API d2tk_frontend_t *
d2tk_pugl_new(const d2tk_pugl_config_t *config, uintptr_t *widget);
-D2TK_API void
-d2tk_pugl_free(d2tk_pugl_t *dpugl);
-
-D2TK_API int
-d2tk_pugl_step(d2tk_pugl_t *dpugl);
-
-D2TK_API int
-d2tk_pugl_poll(d2tk_pugl_t *dpugl, double timeout);
-
-D2TK_API void
-d2tk_pugl_run(d2tk_pugl_t *dpugl, const sig_atomic_t *done);
-
-D2TK_API void
-d2tk_pugl_redisplay(d2tk_pugl_t *dpugl);
-
-D2TK_API int
-d2tk_pugl_set_size(d2tk_pugl_t *dpugl, d2tk_coord_t w, d2tk_coord_t h);
-
-D2TK_API int
-d2tk_pugl_get_size(d2tk_pugl_t *dpugl, d2tk_coord_t *w, d2tk_coord_t *h);
-
-D2TK_API d2tk_base_t *
-d2tk_pugl_get_base(d2tk_pugl_t *dpugl);
-
-D2TK_API float
-d2tk_pugl_get_scale();
-
#ifdef __cplusplus
}
#endif
diff --git a/example/d2tk_fbdev.c b/example/d2tk_fbdev.c
index d298190..f44e47d 100644
--- a/example/d2tk_fbdev.c
+++ b/example/d2tk_fbdev.c
@@ -32,7 +32,7 @@ typedef struct _app_t app_t;
typedef void (*foreach_t)(const char *path, const char *d_name);
struct _app_t {
- d2tk_fbdev_t *fbdev;
+ d2tk_frontend_t *fbdev;
bool show_cursor;
};
@@ -48,8 +48,8 @@ static inline int
_expose(void *data, d2tk_coord_t w, d2tk_coord_t h)
{
app_t *app = data;
- d2tk_fbdev_t *fbdev = app->fbdev;
- d2tk_base_t *base = d2tk_fbdev_get_base(fbdev);
+ d2tk_frontend_t *fbdev = app->fbdev;
+ d2tk_base_t *base = d2tk_frontend_get_base(fbdev);
d2tk_example_run(base, w, h);
@@ -218,9 +218,9 @@ main(int argc, char **argv)
d2tk_example_init();
- d2tk_fbdev_run(app.fbdev, &done);
+ d2tk_frontend_run(app.fbdev, &done);
- d2tk_fbdev_free(app.fbdev);
+ d2tk_frontend_free(app.fbdev);
d2tk_example_deinit();
diff --git a/example/d2tk_pugl.c b/example/d2tk_pugl.c
index 4c9bc2e..70ab967 100644
--- a/example/d2tk_pugl.c
+++ b/example/d2tk_pugl.c
@@ -27,7 +27,7 @@
typedef struct _app_t app_t;
struct _app_t {
- d2tk_pugl_t *dpugl;
+ d2tk_frontend_t *dpugl;
};
static sig_atomic_t done = false;
@@ -42,8 +42,8 @@ static int
_expose(void *data, d2tk_coord_t w, d2tk_coord_t h)
{
app_t *app = data;
- d2tk_pugl_t *dpugl = app->dpugl;
- d2tk_base_t *base = d2tk_pugl_get_base(dpugl);
+ d2tk_frontend_t *dpugl = app->dpugl;
+ d2tk_base_t *base = d2tk_frontend_get_base(dpugl);
d2tk_example_run(base, w, h);
@@ -55,7 +55,7 @@ main(int argc __attribute__((unused)), char **argv __attribute__((unused)))
{
static app_t app;
- const float scale = d2tk_pugl_get_scale();
+ const float scale = d2tk_frontend_get_scale();
d2tk_coord_t w = scale * 1280;
d2tk_coord_t h = scale * 720;
@@ -108,9 +108,9 @@ main(int argc __attribute__((unused)), char **argv __attribute__((unused)))
{
d2tk_example_init();
- d2tk_pugl_run(app.dpugl, &done);
+ d2tk_frontend_run(app.dpugl, &done);
- d2tk_pugl_free(app.dpugl);
+ d2tk_frontend_free(app.dpugl);
d2tk_example_deinit();
diff --git a/example/example.c b/example/example.c
index 2d8b94e..92f3199 100644
--- a/example/example.c
+++ b/example/example.c
@@ -254,7 +254,10 @@ _render_c_spinner(d2tk_base_t *base, const d2tk_rect_t *rect)
{
#define N 8
#define M 24
- static int32_t val [N*M];
+ static union {
+ int32_t i32;
+ float f32;
+ } val [N*M];
D2TK_BASE_TABLE(rect, N, M, D2TK_FLAG_TABLE_REL, tab)
{
@@ -262,9 +265,36 @@ _render_c_spinner(d2tk_base_t *base, const d2tk_rect_t *rect)
const unsigned k = d2tk_table_get_index(tab);
const d2tk_id_t id = D2TK_ID_IDX(k);
- if(d2tk_base_spinner_int32_is_changed(base, id, trect, -99, &val[k], 99))
+ switch(k % 4)
{
- fprintf(stdout, "spinner %016"PRIx64" %"PRIi32"\n", id, val[k]);
+ case 0:
+ {
+ if(d2tk_base_spinner_int32_is_changed(base, id, trect, 0, &val[k].i32, 99))
+ {
+ fprintf(stdout, "spinner %016"PRIx64" %"PRIi32"\n", id, val[k].i32);
+ }
+ } break;
+ case 1:
+ {
+ if(d2tk_base_spinner_float_is_changed(base, id, trect, -1.f, &val[k].f32, 0.f))
+ {
+ fprintf(stdout, "spinner %016"PRIx64" %f\n", id, val[k].f32);
+ }
+ } break;
+ case 2:
+ {
+ if(d2tk_base_spinner_int32_is_changed(base, id, trect, -99, &val[k].i32, 33))
+ {
+ fprintf(stdout, "spinner %016"PRIx64" %"PRIi32"\n", id, val[k].i32);
+ }
+ } break;
+ case 3:
+ {
+ if(d2tk_base_spinner_float_is_changed(base, id, trect, -0.5f, &val[k].f32, 1.f))
+ {
+ fprintf(stdout, "spinner %016"PRIx64" %f\n", id, val[k].f32);
+ }
+ } break;
}
}
#undef N
diff --git a/meson.build b/meson.build
index f4467e2..4fc39c6 100644
--- a/meson.build
+++ b/meson.build
@@ -85,6 +85,7 @@ glew_dep = dependency('glew',
if use_backend_nanovg.enabled() and not glew_dep.found()
# use embedded glew
glew_dep = declare_dependency(
+ compile_args : '-DGLEW_STATIC',
include_directories : include_directories('glew-2.1.0'),
sources : join_paths('glew-2.1.0', 'glew.c'))
endif
@@ -228,7 +229,6 @@ c_args = ['-fvisibility=hidden',
'-ffast-math']
if host_machine.system() == 'windows'
- add_project_arguments('-DGLEW_STATIC', language : 'c')
deps += cc.find_library('opengl32', required : use_frontend_pugl)
deps += cc.find_library('gdi32', required : use_frontend_pugl)
deps += cc.find_library('ws2_32', required : true)
diff --git a/pugl/.clang-format b/pugl/.clang-format
new file mode 100644
index 0000000..b788676
--- /dev/null
+++ b/pugl/.clang-format
@@ -0,0 +1,127 @@
+---
+# Language: Cpp
+# BasedOnStyle: Mozilla
+AccessModifierOffset: -4
+AlignAfterOpenBracket: Align
+AlignConsecutiveAssignments: true
+AlignConsecutiveDeclarations: true
+AlignEscapedNewlines: Left
+AlignOperands: true
+AlignTrailingComments: true
+AllowAllArgumentsOnNextLine: true
+AllowAllConstructorInitializersOnNextLine: false
+AllowAllParametersOfDeclarationOnNextLine: false
+AllowShortBlocksOnASingleLine: false
+AllowShortCaseLabelsOnASingleLine: false
+AllowShortFunctionsOnASingleLine: Inline
+AllowShortLambdasOnASingleLine: All
+AllowShortIfStatementsOnASingleLine: Never
+AllowShortLoopsOnASingleLine: false
+AlwaysBreakAfterDefinitionReturnType: TopLevel
+AlwaysBreakAfterReturnType: TopLevel
+AlwaysBreakBeforeMultilineStrings: false
+AlwaysBreakTemplateDeclarations: Yes
+BinPackArguments: false
+BinPackParameters: false
+BraceWrapping:
+ AfterCaseLabel: false
+ AfterClass: true
+ AfterControlStatement: false
+ AfterEnum: false
+ AfterFunction: true
+ AfterNamespace: false
+ AfterObjCDeclaration: true
+ AfterStruct: false
+ AfterUnion: false
+ AfterExternBlock: true
+ BeforeCatch: false
+ BeforeElse: false
+ IndentBraces: false
+ SplitEmptyFunction: true
+ SplitEmptyRecord: false
+ SplitEmptyNamespace: true
+BreakBeforeBinaryOperators: None
+BreakBeforeBraces: Custom
+BreakBeforeInheritanceComma: false
+BreakInheritanceList: BeforeComma
+BreakBeforeTernaryOperators: true
+BreakConstructorInitializersBeforeComma: false
+BreakConstructorInitializers: BeforeComma
+BreakAfterJavaFieldAnnotations: false
+BreakStringLiterals: true
+ColumnLimit: 80
+CommentPragmas: '^ IWYU pragma:'
+CompactNamespaces: false
+ConstructorInitializerAllOnOneLineOrOnePerLine: false
+ConstructorInitializerIndentWidth: 4
+ContinuationIndentWidth: 4
+Cpp11BracedListStyle: true
+DerivePointerAlignment: false
+DisableFormat: false
+ExperimentalAutoDetectBinPacking: false
+FixNamespaceComments: false
+ForEachMacros:
+ - foreach
+ - Q_FOREACH
+ - BOOST_FOREACH
+IncludeBlocks: Preserve
+IncludeCategories:
+ - Regex: '^"(llvm|llvm-c|clang|clang-c)/'
+ Priority: 2
+ - Regex: '^(<|"(gtest|gmock|isl|json)/)'
+ Priority: 3
+ - Regex: '.*'
+ Priority: 1
+IncludeIsMainRegex: '(Test)?$'
+IndentCaseLabels: false
+IndentPPDirectives: AfterHash
+IndentWidth: 4
+IndentWrappedFunctionNames: false
+JavaScriptQuotes: Leave
+JavaScriptWrapImports: true
+KeepEmptyLinesAtTheStartOfBlocks: true
+MacroBlockBegin: ''
+MacroBlockEnd: ''
+MaxEmptyLinesToKeep: 1
+NamespaceIndentation: None
+ObjCBinPackProtocolList: Auto
+ObjCBlockIndentWidth: 4
+ObjCSpaceAfterProperty: true
+ObjCSpaceBeforeProtocolList: false
+PenaltyBreakAssignment: 100
+PenaltyBreakBeforeFirstCallParameter: 100
+PenaltyBreakComment: 300
+PenaltyBreakFirstLessLess: 120
+PenaltyBreakString: 1000
+PenaltyBreakTemplateDeclaration: 10
+PenaltyExcessCharacter: 1000000
+PenaltyReturnTypeOnItsOwnLine: 0
+PointerAlignment: Left
+ReflowComments: true
+SortIncludes: true
+SortUsingDeclarations: true
+SpaceAfterCStyleCast: false
+SpaceAfterLogicalNot: false
+SpaceAfterTemplateKeyword: false
+SpaceBeforeAssignmentOperators: true
+SpaceBeforeCpp11BracedList: true
+SpaceBeforeCtorInitializerColon: true
+SpaceBeforeInheritanceColon: true
+SpaceBeforeParens: ControlStatements
+SpaceBeforeRangeBasedForLoopColon: true
+SpaceInEmptyParentheses: false
+SpacesBeforeTrailingComments: 1
+SpacesInAngles: false
+SpacesInContainerLiterals: true
+SpacesInCStyleCastParentheses: false
+SpacesInParentheses: false
+SpacesInSquareBrackets: false
+Standard: Cpp11
+StatementMacros:
+ - PUGL_API
+ - PUGL_DEPRECATED_BY
+ - PUGL_UNUSED
+TabWidth: 4
+UseTab: ForIndentation
+...
+
diff --git a/pugl/.clang-tidy b/pugl/.clang-tidy
new file mode 100644
index 0000000..055d63b
--- /dev/null
+++ b/pugl/.clang-tidy
@@ -0,0 +1,16 @@
+Checks: >
+ *,
+ -*magic-numbers,
+ -*uppercase-literal-suffix,
+ -android-cloexec-fopen,
+ -bugprone-suspicious-string-compare,
+ -cert-flp30-c,
+ -clang-analyzer-alpha.*,
+ -clang-analyzer-security.FloatLoopCounter,
+ -hicpp-multiway-paths-covered,
+ -hicpp-signed-bitwise,
+ -llvm-header-guard,
+ -readability-else-after-return
+WarningsAsErrors: ''
+HeaderFilterRegex: 'pugl/.*|test/.*'
+FormatStyle: file
diff --git a/pugl/.gitattributes b/pugl/.gitattributes
new file mode 100644
index 0000000..32967c1
--- /dev/null
+++ b/pugl/.gitattributes
@@ -0,0 +1 @@
+waflib/* linguist-vendored
diff --git a/pugl/.gitlab-ci.yml b/pugl/.gitlab-ci.yml
index 0a2215c..29dc8be 100644
--- a/pugl/.gitlab-ci.yml
+++ b/pugl/.gitlab-ci.yml
@@ -2,6 +2,9 @@ stages:
- build
- deploy
+variables:
+ GIT_SUBMODULE_STRATEGY: normal
+
.build_template: &build_definition
stage: build
@@ -85,27 +88,23 @@ mingw64_rel:
mac_dbg:
<<: *build_definition
script: python ./waf configure build -dsT --no-coverage
- tags:
- - macos
+ tags: [macos]
mac_rel:
<<: *build_definition
script: python ./waf configure build -sT --no-coverage
- tags:
- - macos
+ tags: [macos]
win_dbg:
<<: *build_definition
script:
- python ./waf configure build -dT --no-coverage
- tags:
- - windows
+ tags: [windows,msvc,python]
win_rel:
<<: *build_definition
script: python ./waf configure build -T --no-coverage
- tags:
- - windows
+ tags: [windows,msvc,python]
pages:
stage: deploy
diff --git a/pugl/.gitmodules b/pugl/.gitmodules
new file mode 100644
index 0000000..cc8b569
--- /dev/null
+++ b/pugl/.gitmodules
@@ -0,0 +1,3 @@
+[submodule "waflib"]
+ path = waflib
+ url = ../../drobilla/autowaf.git
diff --git a/pugl/AUTHORS b/pugl/AUTHORS
index 6c09a5b..1470491 100644
--- a/pugl/AUTHORS
+++ b/pugl/AUTHORS
@@ -6,3 +6,6 @@ Robin Gareus <robin@gareus.org>
Erik Åldstedt Sund <erikalds@gmail.com>
Hanspeter Portner <dev@open-music-kontrollers.ch>
Stefan Westerfeld <stefan@space.twc.de>
+Jordan Halase <jordan@halase.me>
+Oliver Schmidt <oliver@luced.de>
+Zoë Sparks <zoe@milky.flowers> \ No newline at end of file
diff --git a/pugl/COPYING b/pugl/COPYING
index b16a139..4a287b9 100644
--- a/pugl/COPYING
+++ b/pugl/COPYING
@@ -1,4 +1,4 @@
-Copyright 2011-2019 David Robillard <http://drobilla.net>
+Copyright 2011-2020 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/README.md b/pugl/README.md
index 718e690..ae9c420 100644
--- a/pugl/README.md
+++ b/pugl/README.md
@@ -67,9 +67,42 @@ 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.
+Testing
+-------
+
+There are a few unit tests included which can be run with `python waf test
+--gui-tests`, but unfortunately manual testing is still required.
+
+Several example programs are included that serve as both manual tests and
+demonstrations:
+
+ * `pugl_embed_demo` shows a view embedded in another, and also tests
+ requesting attention (which happens after 5 seconds), keyboard focus
+ (switched by pressing tab), view moving (with the arrow keys), and view
+ resizing (with the arrow keys while shift is held). This program uses only
+ very old OpenGL and should work on any system.
+
+ * `pugl_window_demo` demonstrates multiple top-level windows.
+
+ * `pugl_gl3_demo` demonstrates using more modern OpenGL where dynamic loading
+ and shaders are required. It can also be used to test performance by
+ passing the number of rectangles to draw on the command line.
+
+ * `pugl_cairo_demo` demonstrates using Cairo on top of the native windowing
+ system (without OpenGL), and partial redrawing.
+
+ * `pugl_print_events` is a utility that prints all received events to the
+ console in a human readable format.
+
+All example programs support several command line options to control various
+behaviours, see the output of `--help` for details. Please file an issue if
+any of these programs do not work as expected on your system.
+
Documentation
-------------
- * [API reference](https://lv2.gitlab.io/pugl/)
+The [API reference](https://lv2.gitlab.io/pugl/) for the latest master is
+available online, and can also be built from the source code by configuring
+with `--docs`.
-- David Robillard <d@drobilla.net>
diff --git a/pugl/doc/header.html b/pugl/doc/header.html
index 07d4076..54c25b0 100644
--- a/pugl/doc/header.html
+++ b/pugl/doc/header.html
@@ -41,7 +41,9 @@
<ul class="tablist">
<li><a href="index.html"><span>Main&#160;Page</span></a></li>
<li><a href="modules.html"><span>Modules</span></a></li>
+ <li><a href="namespaces.html"><span>Namespaces</span></a></li>
<li><a href="annotated.html"><span>Data&#160;Structures</span></a></li>
+ <li><a href="functions.html"><span>Symbols</span></a></li>
<li><a href="files.html"><span>Files</span></a></li>
</ul>
</div>
diff --git a/pugl/doc/layout.xml b/pugl/doc/layout.xml
index f752de1..1889302 100644
--- a/pugl/doc/layout.xml
+++ b/pugl/doc/layout.xml
@@ -83,6 +83,7 @@
<!-- Layout definition for a namespace page -->
<namespace>
<briefdescription visible="yes"/>
+ <detaileddescription title=""/>
<memberdecl>
<nestednamespaces visible="yes" title=""/>
<constantgroups visible="yes" title=""/>
@@ -93,7 +94,6 @@
<variables title=""/>
<membergroups visible="yes"/>
</memberdecl>
- <detaileddescription title=""/>
<memberdef>
<inlineclasses title=""/>
<typedefs title=""/>
diff --git a/pugl/doc/mainpage.md b/pugl/doc/mainpage.md
index b16551a..aa6f925 100644
--- a/pugl/doc/mainpage.md
+++ b/pugl/doc/mainpage.md
@@ -1,43 +1,73 @@
This is the API documentation for Pugl.
-The complete C API is documented in the [Pugl](@ref pugl) group,
-and the C++ wrapper in the [Puglmm](@ref puglmm) group.
+This documentation is based around the [C API](@ref pugl_api),
+there is also a [C++ API](@ref pugl) in the `pugl` namespace.
The Pugl API revolves around two main objects:
the [World](@ref world) and the [View](@ref view).
-An application creates a single world to manage system-level state,
+An application creates a single world to manage top-level state,
then creates one or more views to display.
-## View creation
+## World
+
+The [World](@ref world) contains all top-level state,
+and manages views and the event loop.
+
+A world must be [created](@ref puglNewWorld) before any views,
+and it must outlive all views.
+
+## View
+
+A [View](@ref view) is a drawable region that receives events.
Creating a visible view is a multi-step process.
-A new view allocated with #puglNewView does not yet represent a "real" system window.
-To display, it must first have a [backend set](@ref puglSetBackend),
+When a new view is [created](@ref puglNewView),
+it does not yet represent a real system view or window.
+To display, it must first have a [backend](@ref puglSetBackend)
+and [event handler](@ref puglSetEventFunc) set,
and be configured by [setting hints](@ref puglSetViewHint)
-and [configuring the frame](@ref frame).
+and optionally [adjusting the frame](@ref frame).
+
+The [Backend](@ref PuglBackend) controls drawing for a view.
+Pugl includes [Cairo](@ref cairo) and [OpenGL](@ref gl) backends,
+as well as a [stub](@ref stub) backend that creates a native window with no drawing context.
-Once the view is configured,
-the corresponding window can be [created](@ref puglCreateWindow)
-and [shown](@ref puglShowWindow).
-Note that a view does not necessary correspond to a top-level system window.
+Once the view is configured,
+it can be [realized](@ref puglRealize) and [shown](@ref puglShowWindow).
+By default a view will correspond to a top-level system window.
To create a view within another window,
-call #puglSetParentWindow before #puglCreateWindow.
+it must have a [parent window set](@ref puglSetParentWindow) before being created.
+
+
+## Events
-## Interaction
+[Events](@ref events) are sent to a view when it has received user input or must be drawn.
-Interaction with the user and system happens via [events](@ref interaction).
-Before creating a window,
-a view must have an [event handler](@ref PuglEventFunc) set with #puglSetEventFunc.
-This handler is called whenever something happens that the view must respond to.
+Events are handled by the [event handler](@ref PuglEventFunc) set during initialisation.
+This function is called whenever something happens that the view must respond to.
This includes user interaction like mouse and keyboard input,
-and system events like window resizing and exposure (drawing).
+and system events like window resizing and exposure.
## Event Loop
-Two functions are used to drive the event loop:
+The event loop is driven by repeatedly calling #puglUpdate which processes events from the window system,
+and dispatches them to views when necessary.
+
+Typically, a plugin calls #puglUpdate with timeout 0 in some callback driven by the host.
+A program can use whatever timeout is appropriate:
+event-driven applications may wait forever,
+or for continuous animation,
+use a timeout that is a significant fraction of the frame period
+(with enough time left over to render).
+
+Redrawing can be requested by calling #puglPostRedisplay or #puglPostRedisplayRect,
+which post expose events to the queue.
+Note, however, that this will not wake up a blocked #puglUpdate call on MacOS
+(which does not handle drawing via events).
+For continuous redrawing, call #puglPostRedisplay when a #PUGL_UPDATE event is received.
+This event is sent before views are redrawn,
+so can be used as a hook to expand the update region right before the view is exposed.
- * #puglPollEvents waits for events to become available.
- * #puglDispatchEvents processes all pending events.
+## Error Handling
-Redrawing is accomplished by calling #puglPostRedisplay,
-which posts an expose event to the queue.
+Most functions return a [Status](@ref status) which should be checked to detect failure.
diff --git a/pugl/doc/reference.doxygen.in b/pugl/doc/reference.doxygen.in
index eb64d58..1357fe4 100644
--- a/pugl/doc/reference.doxygen.in
+++ b/pugl/doc/reference.doxygen.in
@@ -379,7 +379,7 @@ DISTRIBUTE_GROUP_DOC = NO
# is disabled and one has to add nested compounds explicitly via \ingroup.
# The default value is: NO.
-GROUP_NESTED_COMPOUNDS = YES
+GROUP_NESTED_COMPOUNDS = NO
# Set the SUBGROUPING tag to YES to allow class member groups of the same type
# (for instance a group of public functions) to be put as a subgroup of that
@@ -692,7 +692,7 @@ SHOW_FILES = YES
# Folder Tree View (if specified).
# The default value is: YES.
-SHOW_NAMESPACES = NO
+SHOW_NAMESPACES = YES
# The FILE_VERSION_FILTER tag can be used to specify a program or script that
# doxygen should invoke to get the current version for each file (typically from
@@ -1451,7 +1451,7 @@ GENERATE_TREEVIEW = NO
# Minimum value: 0, maximum value: 20, default value: 4.
# This tag requires that the tag GENERATE_HTML is set to YES.
-ENUM_VALUES_PER_LINE = 1
+ENUM_VALUES_PER_LINE = 0
# If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be used
# to set the initial width (in pixels) of the frame in which the tree is shown.
@@ -2090,7 +2090,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 = PUGL_API PUGL_DEPRECATED_BY
+PREDEFINED = PUGL_API PUGL_DISABLE_DEPRECATED
# 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
diff --git a/pugl/doc/style.css b/pugl/doc/style.css
index fa366af..28f0519 100644
--- a/pugl/doc/style.css
+++ b/pugl/doc/style.css
@@ -1,744 +1,808 @@
body {
- background: #FFF;
- color: #222;
- font-style: normal;
- line-height: 1.6em;
- margin-left: auto;
- margin-right: auto;
- padding: 1em;
- max-width: 60em;
- font-family: "DejaVu Serif",Palatino,serif;
- text-rendering: optimizeLegibility;
+ background: #FFF;
+ color: #222;
+ font-style: normal;
+ line-height: 1.6em;
+ margin-left: auto;
+ margin-right: auto;
+ padding: 1em;
+ max-width: 60em;
+ font-family: "SF Pro Text", Verdana, "DejaVu Sans", sans-serif;
+ text-rendering: optimizeLegibility;
+}
+
+h1 {
+ font-size: 1.68em;
+ font-weight: 500;
+ font-family: Helvetica, Arial, "DejaVu Sans Condensed", Verdana, sans-serif;
+ line-height: 2em;
+ margin: 0 0 0.25em 0;
}
-h1, .title, #projectname, h2, h3, h4, h5, h6 {
- line-height: 1.0125em;
- color: #444;
- font-family: "DejaVu Sans",Helvetica,Arial,sans-serif;
- margin: 1em 0 0.5em 0;
+h2 {
+ line-height: 1.68em;
+ font-size: 1.41em;
+ font-weight: 600;
+ font-family: Helvetica, Arial, "DejaVu Sans Condensed", Verdana, sans-serif;
+ margin: 1.25em 0 0.5em 0;
}
-h1, .titlearea .header .titlebox, #projectname {
- font-size: 300%;
- font-weight: 400;
- margin-bottom: 0.25em;
- margin-top: 0;
+h3 {
+ line-height: 1.41em;
+ font-size: 1.18em;
+ font-weight: 600;
+ font-family: Helvetica, Arial, "DejaVu Sans Condensed", Verdana, sans-serif;
+ margin: 1.25em 0 0.5em 0;
}
-.header .headertitle .title {
- font-size: 180%;
- font-weight: 400;
- margin: 0.75em 0.25em 0.5em 0;
+h4 {
+ line-height: 1.18em;
+ font-size: 1em;
+ font-weight: 600;
+ font-family: Helvetica, Arial, "DejaVu Sans Condensed", Verdana, sans-serif;
+ margin: 1.25em 0 0.5em 0;
}
-.ingroups {
- display: inline;
+h5, h6 {
+ font-size: 0.7em;
+ font-weight: 600;
+ font-family: Helvetica, Arial, "DejaVu Sans Condensed", Verdana, sans-serif;
+ margin: 1.25em 0 0.5em 0;
}
-.title .ingroups a {
- font-size: small;
- margin-left: 1em;
+
+a {
+ color: #546E00;
+ text-decoration: none;
}
-#titlebox, #metabox {
- display: inline-block;
+h1 a, h2 a, h3 a, h4 a, h5 a, h6 a {
+ color: #444;
}
-#titlebox{
- display: inline-block;
- width: 75%;
- left: 0;
- top: 0;
+
+a:hover {
+ text-decoration: underline;
}
-#title {
- margin-bottom: 0.25em;
+h1 a:link, h2 a:link, h3 a:link, h4 a:link, h5 a:link, h6 a:link {
+ color: #444;
}
-#shortdesc {
- margin: 0;
- color: #666;
- display: inline-block;
- font-style: italic;
- padding: 0;
+h1 a:visited, h2 a:visited, h3 a:visited, h4 a:visited, h5 a:visited, h6 a:visited {
+ color: #444;
}
-#titlearea {
- margin: 0.25em auto 0.25em auto;
- padding: 0;
- position: relative;
- clear: both;
- line-height: 1.0em;
+p {
+ margin: 0.5em 0 0.5em 0;
}
-h2 {
- font-size: 160%;
- font-weight: 400;
+dt {
+ font-weight: 600;
}
-h3 {
- font-size: 140%;
- font-weight: 400;
+dd {
+ margin-left: 2em;
}
-h4 {
- font-size: 120%;
- font-weight: 500;
+caption {
+ font-weight: 700;
}
-h5, h6 {
- font-size: 110%;
- font-weight: 600;
+.title, #projectname {
+ line-height: 1.0125em;
+ margin: 0.75em 0 0 0;
}
-h1 a, h1 a:link, h1 a:visited ,
-h2 a, h2 a:link, h2 a:visited ,
-h3 a, h3 a:link, h3 a:visited ,
-h4 a, h4 a:link, h4 a:visited ,
-h5 a, h5 a:link, h5 a:visited ,
-h6 a, h6 a:link, h6 a:visited {
- color: #444;
+.titlearea .header .titlebox, #projectname {
+ font-size: 1.68em;
+ font-weight: 400;
+ margin-bottom: 0.25em;
+ margin-top: 0;
}
-p {
- margin: 0.5em 0 0.5em 0;
+#header {
+ padding: 0 0 0.5em 0;
+ border-bottom: 1px solid #EEE;
}
-dt {
- font-weight: 700;
+.header .headertitle .title {
+ line-height: 1.68em;
+ font-size: 1.68em;
+ font-weight: 600;
+ font-family: Helvetica, Arial, "DejaVu Sans Condensed", Verdana, sans-serif;
}
-dd {
- margin-left: 2em;
+.ingroups {
+ display: none;
}
-caption {
- font-weight: 700;
+.title .ingroups a {
+ font-size: small;
+ margin-left: 1em;
}
-span.legend {
- font-size: small;
- text-align: center;
+#titlebox, #metabox {
+ display: inline-block;
}
-h3.version {
- font-size: small;
- text-align: center;
+#titlebox {
+ display: inline-block;
+ width: 75%;
+ left: 0;
+ top: 0;
}
-div.qindex,div.navtab {
- background-color: #EBEFF6;
- border: 1px solid #A3B4D7;
- text-align: center;
- margin: 2px;
- padding: 2px;
+#title {
+ margin-bottom: 0.25em;
+ line-height: 1.25em;
+ font-size: 2.5em;
+ color: #333;
+ font-weight: 600;
}
-div.navtab {
- margin-right: 15px;
+.PageDoc {
+ margin-top: 1.5em;
}
-/* @group Link Styling */
-a {
- color: #546E00;
- text-decoration: none;
+.PageDoc .header .headertitle .title {
+ display: none;
}
-.contents a:visited {
- color: #344E00;
+#shortdesc {
+ margin: 0;
+ color: #666;
+ display: inline-block;
+ font-style: italic;
+ font-family: Helvetica, Arial, "DejaVu Sans Condensed", Verdana, sans-serif;
+ padding: 0;
}
-a:hover {
- text-decoration: underline;
+#titlearea {
+ margin: 0.25em auto 0 auto;
+ padding: 0;
+ position: relative;
+ clear: both;
+ line-height: 1em;
+}
+
+.legend {
+ font-size: small;
+ text-align: center;
+}
+
+.version {
+ font-size: small;
+ text-align: center;
+}
+
+div.qindex,div.navtab {
+ background-color: #EBEFF6;
+ border: 1px solid #A3B4D7;
+ text-align: center;
+ margin: 2px;
+ padding: 2px;
+}
+
+div.navtab {
+ margin-right: 15px;
+}
+
+.contents a:visited {
+ color: #344E00;
}
a.qindexHL {
- background-color: #9CAFD4;
- color: #FFF;
- border: 1px double #869DCA;
+ background-color: #9CAFD4;
+ color: #FFF;
+ border: 1px double #869DCA;
}
code {
- color: #444;
+ color: #444;
+ font-family: "SF Mono", Menlo, Consolas, "DejaVu Sans Mono", monospace, fixed;
}
-/* @end */
dl.el {
- margin-left: -1cm;
+ margin-left: -1cm;
}
.fragment {
- font-family: "DejaVu Sans Mono",monospace,fixed;
+ font-family: "SF Mono", Menlo, Consolas, "DejaVu Sans Mono", monospace, fixed;
}
pre.fragment {
- border: 1px solid #C4C4C4;
- background-color: #F9F9F9;
- padding: 0.5em;
- overflow: auto;
+ border: 1px solid #C4C4C4;
+ background-color: #F9F9F9;
+ padding: 0.5em;
+ overflow: auto;
}
div.ah {
- background-color: #000;
- font-weight: 700;
- color: #FFF;
- margin-bottom: 3px;
- margin-top: 3px;
- padding: .2em;
- border: thin solid #333;
+ background-color: #000;
+ font-weight: 700;
+ color: #FFF;
+ margin-bottom: 3px;
+ margin-top: 3px;
+ padding: 0.2em;
+ border: thin solid #333;
}
div.groupHeader {
- margin-left: 16px;
- margin-top: 12px;
- margin-bottom: 6px;
- font-weight: 700;
+ margin-left: 16px;
+ margin-top: 12px;
+ margin-bottom: 6px;
+ font-weight: 700;
}
a + h2.groupheader {
- display: none;
+ display: none;
}
div.groupText {
- margin-left: 16px;
- font-style: italic;
+ margin-left: 16px;
+ font-style: italic;
}
div.contents, #content {
- padding: 0 0.25em 0 0.25em;
- max-width: 60em;
- margin-left: auto;
- margin-right: auto;
+ max-width: 60em;
+ margin-left: auto;
+ margin-right: auto;
+}
+
+.groupheader + p {
+ font-style: italic;
+ color: #666;
+ margin: 0 0 1em 0;
}
td.indexkey {
- background-color: #EBEFF6;
- font-weight: 700;
- border: 1px solid #C4CFE5;
- margin: 2px 0;
- padding: 2px 10px;
+ background-color: #EBEFF6;
+ font-weight: 700;
+ border: 1px solid #C4CFE5;
+ margin: 2px 0;
+ padding: 2px 10px;
}
td.indexvalue {
- background-color: #EBEFF6;
- border: 1px solid #C4CFE5;
- padding: 2px 10px;
- margin: 2px 0;
+ background-color: #EBEFF6;
+ border: 1px solid #C4CFE5;
+ padding: 2px 10px;
+ margin: 2px 0;
}
table.memname {
- font-family: "DejaVu Sans Mono",monospace,fixed;
+ font-family: "SF Mono", Menlo, Consolas, "DejaVu Sans Mono", monospace, fixed;
+ border-spacing: 0;
+}
+
+table.memname tbody tr:last-child {
+ display: none;
+}
+
+table.memname tbody tr:only-child {
+ display: table-cell;
+}
+
+table.memname tbody tr:nth-last-child(2)::after {
+ content: ")";
}
tr.memlist {
- background-color: #EEF1F7;
+ background-color: #EEF1F7;
}
p.formulaDsp {
- text-align: center;
+ text-align: center;
}
img.formulaInl {
- vertical-align: middle;
+ vertical-align: middle;
}
div.center {
- text-align: center;
- margin-top: 0;
- margin-bottom: 0;
- padding: 0;
+ text-align: center;
+ margin-top: 0;
+ margin-bottom: 0;
+ padding: 0;
}
div.center img {
- border: 0;
+ border: 0;
}
address.footer {
- text-align: right;
+ text-align: right;
}
img.footer {
- border: 0;
- vertical-align: middle;
+ border: 0;
+ vertical-align: middle;
}
-/* @group Code Colorization */
span.keyword {
- color: #586E75;
+ color: #586E75;
}
span.keywordtype {
- color: #546E00;
+ color: #546E00;
}
span.keywordflow {
- color: #586E75;
+ color: #586E75;
}
span.comment {
- color: #6C71C4;
+ color: #6C71C4;
}
span.preprocessor {
- color: #D33682;
+ color: #D33682;
}
span.stringliteral {
- color: #CB4B16;
+ color: #CB4B16;
}
span.charliteral {
- color: #CB4B16;
+ color: #CB4B16;
}
-/* @end */
td.tiny {
- font-size: x-small;
+ font-size: x-small;
}
.dirtab {
- padding: 4px;
- border-collapse: collapse;
- border: 1px solid #A3B4D7;
+ padding: 4px;
+ border-collapse: collapse;
+ border: 1px solid #A3B4D7;
}
th.dirtab {
- background: #EBEFF6;
- font-weight: 700;
+ background: #EBEFF6;
+ font-weight: 700;
}
hr {
- height: 0;
- border: none;
- border-top: 1px solid #DDD;
- margin: 2em 0 1em;
+ height: 0;
+ border: none;
+ border-top: 1px solid #DDD;
+ margin: 2em 0;
}
#footer {
- bottom: 0;
- clear: both;
- font-size: x-small;
- margin: 2em 0 0;
- padding: 0 1em 1em 1em;
- vertical-align: top;
- color: #888;
+ bottom: 0;
+ clear: both;
+ font-size: x-small;
+ margin: 2em 0 0;
+ padding: 0 1em 1em 1em;
+ vertical-align: top;
+ color: #888;
+}
+
+td.ititle {
+ padding-bottom: 0.75em;
}
-/* @group Member Descriptions */
table.memberdecls {
- border-spacing: 0.125em;
- line-height: 1.3em;
+ border-spacing: 0.125em;
+ line-height: 1.3em;
}
.mdescLeft,.mdescRight,.memItemLeft,.memItemRight,.memTemplItemLeft,.memTemplItemRight,.memTemplParams {
- margin: 0;
- padding: 0;
+ margin: 0;
+ padding: 0;
}
.mdescLeft,.mdescRight {
- color: #555;
+ color: #555;
}
.memItemLeft,.memItemRight,.memTemplParams {
- border: 0;
- font-family: "DejaVu Sans Mono",monospace,fixed;
+ border: 0;
+ font-family: "SF Mono", Menlo, Consolas, "DejaVu Sans Mono", monospace, fixed;
}
.memItemLeft,.memTemplItemLeft {
- white-space: nowrap;
- padding-left: 2em;
- padding-right: 1em;
+ white-space: nowrap;
+ padding-left: 2em;
}
.memItemLeft a.el {
- font-weight: bold;
+ font-weight: bold;
}
.memTemplParams {
- color: #464646;
- white-space: nowrap;
+ color: #464646;
+ white-space: nowrap;
}
td.memSeparator {
- display: none;
+ display: none;
}
td.mlabels-right {
- vertical-align: top;
- padding-top: 4px;
- color: #B4C342;
+ vertical-align: top;
+ padding-top: 4px;
+ color: #B4C342;
}
.memtitle {
- display: none;
+ display: none;
}
-/* @end */
-/* @group Member Details */
-/* Styles for detailed member documentation */
.memtemplate {
- color: #888;
- font-style: italic;
- font-family: "DejaVu Sans Mono",monospace,fixed;
- font-size: small;
+ color: #888;
+ font-style: italic;
+ font-family: "SF Mono", Menlo, Consolas, "DejaVu Sans Mono", monospace, fixed;
+ font-size: small;
}
.memnav {
- background-color: #EEE;
- border: 1px solid #B4C342;
- text-align: center;
- margin: 2px;
- margin-right: 15px;
- padding: 2px;
+ background-color: #EEE;
+ border: 1px solid #B4C342;
+ text-align: center;
+ margin: 2px;
+ margin-right: 15px;
+ padding: 2px;
}
.memitem {
- padding: 0.25em 0.5em 0.25em 0.5em;
- margin: 0 0 1em 0;
- border-radius: 6px;
- border: 1px solid #DDD;
+ padding: 0.5em 0.5em 0.25em 0.5em;
+ margin: 1em 0 2em 0;
}
.memproto {
- font-size: 110%;
- font-weight: 400;
- line-height: 1em;
- color: #000;
+ border-bottom: 1px solid #EEE;
+ font-family: "SF Mono", Menlo, Consolas, "DejaVu Sans Mono", monospace, fixed;
+ font-size: 1.09em;
+ font-weight: 600;
+ line-height: 1.41em;
+ margin-bottom: 0.25em;
+ padding-bottom: 0.125em;
}
.memproto .paramname {
- font-style: normal;
+ font-style: normal;
+ padding-right: 0.25em;
}
.memdoc {
- padding: 0 0.25em 0 0.25em;
+ padding: 0;
+}
+
+.memdoc > p:first-child, .memdoc .textblock > p:first-child {
+ font-style: italic;
+ color: #444;
+ margin-bottom: 0.75em;
}
.paramkey {
- text-align: right;
+ text-align: right;
}
.paramtype {
- color: #666;
- padding-right: 0.5em;
- white-space: nowrap;
+ color: #666;
+ padding: 0 0.25em 0 0.25em;
+ white-space: nowrap;
}
-.paramname {
- color: #111;
- white-space: nowrap;
- font-family: "DejaVu Sans Mono",monospace,fixed;
- font-style: italic;
- padding-right: 0.5em;
+.params .paramname {
+ color: #111;
+ white-space: nowrap;
+ font-family: "SF Mono", Menlo, Consolas, "DejaVu Sans Mono", monospace, fixed;
+ font-style: italic;
+ padding-right: 0.5em;
+ vertical-align: top;
}
.fieldname {
- color: #000;
+ color: #000;
}
.fieldtable {
- padding-top: 0.25em;
- border-top: 1px dashed #DDD;
+ margin-top: 1em;
+ border-collapse: collapse;
}
.fieldtable tbody tr:first-child {
- display: none;
+ display: none;
}
td.fieldname {
- padding: 0 0.5em 0 0.25em;
- vertical-align: top;
- font-family: "DejaVu Sans Mono",monospace,fixed;
+ vertical-align: top;
+ font-family: "SF Mono", Menlo, Consolas, "DejaVu Sans Mono", monospace, fixed;
+}
+
+td.fielddoc {
+ padding: 0.125em 0.5em 0 0.25em;
+ vertical-align: top;
+}
+
+.fieldtable tbody tr td {
+ border-top: 1px dashed #DDD;
+ border-bottom: 1px dashed #DDD;
}
td.fieldtype {
- color: #666;
- padding: 0 0.5em 0 0;
- vertical-align: top;
- font-family: "DejaVu Sans Mono",monospace,fixed;
+ color: #666;
+ padding: 0 0.5em 0 0;
+ vertical-align: top;
+ font-family: "SF Mono", Menlo, Consolas, "DejaVu Sans Mono", monospace, fixed;
}
td.fielddoc p {
- margin: 0;
- vertical-align: top;
- padding: 0 0.5em 0 0;
+ margin: 0;
+ padding: 0 0.5em 0 0;
}
p.reference {
- font-size: x-small;
- font-style: italic;
+ font-size: x-small;
+ font-style: italic;
}
-/* @end */
-/* @group Directory (tree) */
-/* for the tree view */
.ftvtree {
- font-family: sans-serif;
- margin: 0;
+ font-family: "DejaVu Sans", Verdana, Helvetica, Arial, sans-serif;
+ margin: 0;
}
-/* these are for tree view when used as main index */
.directory {
- font-size: small;
- margin: 0.5em;
+ margin: 0.5em;
}
.directory h3 {
- margin: 0;
- margin-top: 1em;
- font-size: 11pt;
+ margin: 0;
+ margin-top: 1em;
+ font-size: 11pt;
}
.directory > h3 {
- margin-top: 0;
+ margin-top: 0;
}
.directory p {
- margin: 0;
- white-space: nowrap;
+ margin: 0;
+ white-space: nowrap;
}
.directory div {
- display: none;
- margin: 0;
+ display: none;
+ margin: 0;
}
.directory img {
- vertical-align: -30%;
+ vertical-align: -30%;
}
td.entry {
- font-family: "DejaVu Sans",Helvetica,Arial,sans-serif;
- font-weight: 400;
- padding-right: 1em;
+ font-family: "DejaVu Sans", Verdana, Helvetica, Arial, sans-serif;
+ font-weight: 400;
+ padding-right: 1em;
}
-td.entry .arrow {
- display: none;
+.arrow {
+ color: #CCC;
+ user-select: none;
+ font-size: 80%;
+ display: inline-block;
+ width: 16px;
+ height: 22px;
+ vertical-align: top;
}
td.entry b {
- font-family: "DejaVu Sans",Helvetica,Arial,sans-serif;
- font-weight: 400;
- font-size: 130%;
+ font-family: "DejaVu Sans", Verdana, Helvetica, Arial, sans-serif;
+ font-weight: 400;
+ font-size: 130%;
}
-/* these are for tree view when not used as main index */
.directory-alt {
- font-size: 100%;
- font-weight: bold;
+ font-size: 100%;
+ font-weight: bold;
}
.directory-alt h3 {
- margin: 0;
- margin-top: 1em;
- font-size: 11pt;
+ margin: 0;
+ margin-top: 1em;
+ font-size: 11pt;
}
.directory-alt > h3 {
- margin-top: 0;
+ margin-top: 0;
}
.directory-alt p {
- margin: 0;
- white-space: nowrap;
+ margin: 0;
+ white-space: nowrap;
}
.directory-alt div {
- display: none;
- margin: 0;
+ display: none;
+ margin: 0;
}
.directory-alt img {
- vertical-align: -30%;
+ vertical-align: -30%;
}
-/* @end */
div.dynheader {
- margin-top: 8px;
+ margin-top: 8px;
}
address {
- font-style: normal;
- color: #444;
+ font-style: normal;
+ color: #444;
}
table.doxtable {
- border-collapse: collapse;
- margin: 0.5em;
+ border-collapse: collapse;
+ margin: 0.5em;
}
table.doxtable td,table.doxtable th {
- border: 1px solid #DDD;
- padding: 3px 7px 2px;
+ border: 1px solid #DDD;
+ padding: 3px 7px 2px;
}
table.doxtable th {
- background-color: #F3F3F3;
- color: #000;
- padding-bottom: 4px;
- padding-top: 5px;
- text-align: left;
- font-weight: bold;
+ background-color: #F3F3F3;
+ color: #000;
+ padding-bottom: 4px;
+ padding-top: 5px;
+ text-align: left;
+ font-weight: bold;
}
.tabsearch {
- top: 0;
- left: 10px;
- height: 36px;
- z-index: 101;
- overflow: hidden;
- font-size: 13px;
+ top: 0;
+ left: 10px;
+ height: 36px;
+ z-index: 101;
+ overflow: hidden;
+ font-size: 13px;
}
div.navpath {
- color: #DDD;
+ color: #DDD;
}
.navpath ul {
- overflow: hidden;
- margin: 0;
- padding: 0;
+ overflow: hidden;
+ margin: 0;
+ padding: 0;
}
.navpath li {
- float: left;
- padding-left: 0;
- margin-left: 0.5em;
- padding-right: 1em;
+ float: left;
+ padding-left: 0;
+ margin-left: 0.5em;
+ padding-right: 1em;
}
.navpath a {
- display: block;
- text-decoration: none;
- outline: none;
+ display: block;
+ text-decoration: none;
+ outline: none;
}
div.summary {
- font-size: small;
- font-family: "DejaVu Sans",Helvetica,Arial,sans-serif;
- margin: 0;
- color: #FFF; /* Hide separator bars */
- border-bottom: 1px solid #DDD;
- padding: 0.25em 0;
+ font-size: small;
+ font-family: "DejaVu Sans", Verdana, Helvetica, Arial, sans-serif;
+ margin: 0;
+ padding: 0.25em 0;
+ display: none;
}
div.summary a {
- white-space: nowrap;
+ white-space: nowrap;
}
-/* Metadata box (right aligned next to title) */
-
#metabox {
- display: inline-block;
- font-size: x-small;
- margin: 0 0 0.25em 0;
- position: absolute;
- right: 0;
- top: 0;
- color: #666;
- font-style: italic;
- padding: 0 1em;
+ display: inline-block;
+ font-size: x-small;
+ font-family: Helvetica, Arial, "DejaVu Sans Condensed", Verdana, sans-serif;
+ position: absolute;
+ right: 0;
+ bottom: 0.25em;
+ color: #666;
+ font-style: italic;
}
#meta {
- border-style: hidden;
- margin-right: 0.25em;
+ border-style: hidden;
+ margin-right: 0.25em;
}
#meta tr, #meta th, #meta td {
- background-color: transparent;
- border: 0;
- margin: 0;
- font-weight: normal;
+ background-color: transparent;
+ border: 0;
+ margin: 0;
+ font-weight: normal;
}
#meta th {
- text-align: right;
+ text-align: right;
}
-#meta th:after {
- content: ":";
+#meta th::after {
+ content: ":";
}
div.line {
- font-family: "DejaVu Sans Mono",monospace,fixed;
- line-height: 1.4em;
- white-space: pre-wrap;
+ font-family: "SF Mono", Menlo, Consolas, "DejaVu Sans Mono", monospace, fixed;
+ line-height: 1.4em;
+ white-space: pre-wrap;
}
.glow {
- background-color: #2AA198;
- box-shadow: 0 0 10px #2AA198;
+ background-color: #2AA198;
+ box-shadow: 0 0 10px #2AA198;
}
span.lineno {
- padding-right: 4px;
- text-align: right;
- border-right: 2px solid #546E00;
- background-color: #E8E8E8;
- white-space: pre;
+ padding-right: 4px;
+ text-align: right;
+ border-right: 2px solid #546E00;
+ background-color: #E8E8E8;
+ white-space: pre;
}
+
span.lineno a {
- background-color: #D8D8D8;
+ background-color: #D8D8D8;
}
span.lineno a:hover {
- background-color: #C8C8C8;
+ background-color: #C8C8C8;
}
.tabs, .tabs2, .navpath {
- padding: 0.25em 0;
- border-bottom: 1px solid #DDD;
- font-size: small;
- font-family: "DejaVu Sans",Helvetica,Arial,sans-serif;
- margin: 0;
+ padding: 0.25em 0;
+ font-size: small;
+ font-family: Helvetica, Arial, "DejaVu Sans Condensed", Verdana, sans-serif;
+ margin: 0;
}
th {
- text-align: left;
- font-size: 110%;
- font-weight: 500;
+ text-align: left;
+ font-size: 110%;
+ font-weight: 500;
}
.mlabel {
- padding: 0.125em;
+ padding: 0.125em;
}
-#navrow1 {
- /* Disable menu from Doxygen 1.8.15, it is faked in the template */
- display: none;
+#navrow1, #navrow2 {
+ /* Disable menu from Doxygen 1.8.15, it is faked in the template */
+ display: none;
}
-/* tabs*/
-
.tablist {
- margin: 0;
- padding: 0;
- display: table;
+ margin: 0;
+ padding: 0;
+ display: table;
}
.tablist li {
- display: table-cell;
- line-height: 2em;
- list-style: none;
- border-bottom: 0;
+ display: table-cell;
+ line-height: 2em;
+ list-style: none;
+ border-bottom: 0;
}
.tablist a {
- display: block;
- padding: 0 1em 0 0;
- font-family: "DejaVu Sans",Helvetica,Arial,sans-serif;
- text-decoration: none;
- outline: none;
+ display: block;
+ padding: 0 1em 0 0;
+ text-decoration: none;
+ outline: none;
}
.tabs3 .tablist a {
- padding: 0 10px;
+ padding: 0 10px;
}
.tablist li.current a {
- color: #222;
+ color: #222;
}
span.icon {
- display: none;
+ display: none;
}
diff --git a/pugl/examples/cube_view.h b/pugl/examples/cube_view.h
new file mode 100644
index 0000000..9fd2349
--- /dev/null
+++ b/pugl/examples/cube_view.h
@@ -0,0 +1,82 @@
+/*
+ Copyright 2012-2020 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.
+*/
+
+#define GL_SILENCE_DEPRECATION 1
+
+#include "demo_utils.h"
+
+#include "pugl/gl.h"
+
+static inline void
+reshapeCube(const float width, const float height)
+{
+ const float aspect = width / height;
+
+ glEnable(GL_DEPTH_TEST);
+ glDepthFunc(GL_LESS);
+ glClearColor(0.2f, 0.2f, 0.2f, 1.0f);
+
+ glMatrixMode(GL_PROJECTION);
+ glLoadIdentity();
+ glViewport(0, 0, (int)width, (int)height);
+
+ float projection[16];
+ perspective(projection, 1.8f, aspect, 1.0f, 100.0f);
+ glLoadMatrixf(projection);
+}
+
+static inline void
+displayCube(PuglView* const view,
+ const float distance,
+ const float xAngle,
+ const float yAngle,
+ const bool entered)
+{
+ glMatrixMode(GL_MODELVIEW);
+ glLoadIdentity();
+ glTranslatef(0.0f, 0.0f, distance * -1.0f);
+ glRotatef(xAngle, 0.0f, 1.0f, 0.0f);
+ glRotatef(yAngle, 1.0f, 0.0f, 0.0f);
+
+ const float bg = entered ? 0.2f : 0.0f;
+ glClearColor(bg, bg, bg, 1.0f);
+ glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
+
+ if (puglHasFocus(view)) {
+ // Draw cube surfaces
+ glEnableClientState(GL_VERTEX_ARRAY);
+ glEnableClientState(GL_COLOR_ARRAY);
+ glVertexPointer(3, GL_FLOAT, 0, cubeStripVertices);
+ glColorPointer(3, GL_FLOAT, 0, cubeStripVertices);
+ glDrawArrays(GL_TRIANGLE_STRIP, 0, 14);
+ glDisableClientState(GL_COLOR_ARRAY);
+ glDisableClientState(GL_VERTEX_ARRAY);
+
+ glColor3f(0.0f, 0.0f, 0.0f);
+ } else {
+ glColor3f(1.0f, 1.0f, 1.0f);
+ }
+
+ // Draw cube wireframe
+ glEnableClientState(GL_VERTEX_ARRAY);
+ glVertexPointer(3, GL_FLOAT, 0, cubeFrontLineLoop);
+ glDrawArrays(GL_LINE_LOOP, 0, 4);
+ glVertexPointer(3, GL_FLOAT, 0, cubeBackLineLoop);
+ glDrawArrays(GL_LINE_LOOP, 0, 4);
+ glVertexPointer(3, GL_FLOAT, 0, cubeSideLines);
+ glDrawArrays(GL_LINES, 0, 8);
+ glDisableClientState(GL_VERTEX_ARRAY);
+}
diff --git a/pugl/examples/demo_utils.h b/pugl/examples/demo_utils.h
new file mode 100644
index 0000000..9a1cb7a
--- /dev/null
+++ b/pugl/examples/demo_utils.h
@@ -0,0 +1,175 @@
+/*
+ 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.
+*/
+
+#ifndef PUGL_DEMO_UTILS_H
+#define PUGL_DEMO_UTILS_H
+
+#include "pugl/pugl.h"
+
+#include <math.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <string.h>
+
+typedef struct {
+ double lastReportTime;
+} PuglFpsPrinter;
+
+typedef float vec4[4];
+typedef vec4 mat4[4];
+
+// clang-format off
+
+static const float cubeStripVertices[] = {
+ -1.0f, 1.0f, 1.0f, // Front top left
+ 1.0f, 1.0f, 1.0f, // Front top right
+ -1.0f, -1.0f, 1.0f, // Front bottom left
+ 1.0f, -1.0f, 1.0f, // Front bottom right
+ 1.0f, -1.0f, -1.0f, // Back bottom right
+ 1.0f, 1.0f, 1.0f, // Front top right
+ 1.0f, 1.0f, -1.0f, // Back top right
+ -1.0f, 1.0f, 1.0f, // Front top left
+ -1.0f, 1.0f, -1.0f, // Back top left
+ -1.0f, -1.0f, 1.0f, // Front bottom left
+ -1.0f, -1.0f, -1.0f, // Back bottom left
+ 1.0f, -1.0f, -1.0f, // Back bottom right
+ -1.0f, 1.0f, -1.0f, // Back top left
+ 1.0f, 1.0f, -1.0f // Back top right
+};
+
+static const float cubeFrontLineLoop[] = {
+ -1.0f, 1.0f, 1.0f, // Front top left
+ 1.0f, 1.0f, 1.0f, // Front top right
+ 1.0f, -1.0f, 1.0f, // Front bottom right
+ -1.0f, -1.0f, 1.0f, // Front bottom left
+};
+
+static const float cubeBackLineLoop[] = {
+ -1.0f, 1.0f, -1.0f, // Back top left
+ 1.0f, 1.0f, -1.0f, // Back top right
+ 1.0f, -1.0f, -1.0f, // Back bottom right
+ -1.0f, -1.0f, -1.0f, // Back bottom left
+};
+
+static const float cubeSideLines[] = {
+ -1.0f, 1.0f, 1.0f, // Front top left
+ -1.0f, 1.0f, -1.0f, // Back top left
+
+ -1.0f, -1.0f, 1.0f, // Front bottom left
+ -1.0f, -1.0f, -1.0f, // Back bottom left
+
+ 1.0f, 1.0f, 1.0f, // Front top right
+ 1.0f, 1.0f, -1.0f, // Back top right
+
+ 1.0f, -1.0f, 1.0f, // Front bottom right
+ 1.0f, -1.0f, -1.0f, // Back bottom right
+};
+
+// clang-format on
+
+static inline void
+mat4Identity(mat4 m)
+{
+ for (int c = 0; c < 4; ++c) {
+ for (int r = 0; r < 4; ++r) {
+ m[c][r] = c == r ? 1.0f : 0.0f;
+ }
+ }
+}
+
+static inline void
+mat4Translate(mat4 m, const float x, const float y, const float z)
+{
+ m[3][0] = x;
+ m[3][1] = y;
+ m[3][2] = z;
+}
+
+static inline void
+mat4Mul(mat4 m, mat4 a, mat4 b)
+{
+ for (int c = 0; c < 4; ++c) {
+ for (int r = 0; r < 4; ++r) {
+ m[c][r] = 0.0f;
+ for (int k = 0; k < 4; ++k) {
+ m[c][r] += a[k][r] * b[c][k];
+ }
+ }
+ }
+}
+
+static inline void
+mat4Ortho(mat4 m,
+ const float l,
+ const float r,
+ const float b,
+ const float t,
+ const float n,
+ const float f)
+{
+ m[0][0] = 2.0f / (r - l);
+ m[0][1] = m[0][2] = m[0][3] = 0.0f;
+
+ m[1][1] = 2.0f / (t - b);
+ m[1][0] = m[1][2] = m[1][3] = 0.0f;
+
+ m[2][2] = -2.0f / (f - n);
+ m[2][0] = m[2][1] = m[2][3] = 0.0f;
+
+ m[3][0] = -(r + l) / (r - l);
+ m[3][1] = -(t + b) / (t - b);
+ m[3][2] = -(f + n) / (f - n);
+ m[3][3] = 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;
+
+ // clang-format off
+ 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;
+ // clang-format on
+}
+
+static inline void
+puglPrintFps(const PuglWorld* world,
+ PuglFpsPrinter* printer,
+ unsigned* const framesDrawn)
+{
+ const double thisTime = puglGetTime(world);
+ 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;
+ }
+}
+
+#endif // PUGL_DEMO_UTILS_H
diff --git a/pugl/test/glad/glad.c b/pugl/examples/glad/glad.c
index 2a0567c..38f442c 100644
--- a/pugl/test/glad/glad.c
+++ b/pugl/examples/glad/glad.c
@@ -4,7 +4,7 @@
Language/Generator: C/C++
Specification: gl
- APIs: gl=3.2
+ APIs: gl=3.3
Profile: core
Extensions:
@@ -14,9 +14,9 @@
Reproducible: True
Commandline:
- --profile="core" --api="gl=3.2" --generator="c" --spec="gl" --local-files --extensions=""
+ --profile="core" --api="gl=3.3" --generator="c" --spec="gl" --local-files --extensions=""
Online:
- https://glad.dav1d.de/#profile=core&language=c&specification=gl&loader=on&api=gl%3D3.2
+ https://glad.dav1d.de/#profile=core&language=c&specification=gl&loader=on&api=gl%3D3.3
*/
#include <stdio.h>
@@ -264,6 +264,7 @@ int GLAD_GL_VERSION_2_1 = 0;
int GLAD_GL_VERSION_3_0 = 0;
int GLAD_GL_VERSION_3_1 = 0;
int GLAD_GL_VERSION_3_2 = 0;
+int GLAD_GL_VERSION_3_3 = 0;
PFNGLACTIVETEXTUREPROC glad_glActiveTexture = NULL;
PFNGLATTACHSHADERPROC glad_glAttachShader = NULL;
PFNGLBEGINCONDITIONALRENDERPROC glad_glBeginConditionalRender = NULL;
@@ -274,8 +275,10 @@ PFNGLBINDBUFFERPROC glad_glBindBuffer = NULL;
PFNGLBINDBUFFERBASEPROC glad_glBindBufferBase = NULL;
PFNGLBINDBUFFERRANGEPROC glad_glBindBufferRange = NULL;
PFNGLBINDFRAGDATALOCATIONPROC glad_glBindFragDataLocation = NULL;
+PFNGLBINDFRAGDATALOCATIONINDEXEDPROC glad_glBindFragDataLocationIndexed = NULL;
PFNGLBINDFRAMEBUFFERPROC glad_glBindFramebuffer = NULL;
PFNGLBINDRENDERBUFFERPROC glad_glBindRenderbuffer = NULL;
+PFNGLBINDSAMPLERPROC glad_glBindSampler = NULL;
PFNGLBINDTEXTUREPROC glad_glBindTexture = NULL;
PFNGLBINDVERTEXARRAYPROC glad_glBindVertexArray = NULL;
PFNGLBLENDCOLORPROC glad_glBlendColor = NULL;
@@ -299,6 +302,10 @@ PFNGLCLEARSTENCILPROC glad_glClearStencil = NULL;
PFNGLCLIENTWAITSYNCPROC glad_glClientWaitSync = NULL;
PFNGLCOLORMASKPROC glad_glColorMask = NULL;
PFNGLCOLORMASKIPROC glad_glColorMaski = NULL;
+PFNGLCOLORP3UIPROC glad_glColorP3ui = NULL;
+PFNGLCOLORP3UIVPROC glad_glColorP3uiv = NULL;
+PFNGLCOLORP4UIPROC glad_glColorP4ui = NULL;
+PFNGLCOLORP4UIVPROC glad_glColorP4uiv = NULL;
PFNGLCOMPILESHADERPROC glad_glCompileShader = NULL;
PFNGLCOMPRESSEDTEXIMAGE1DPROC glad_glCompressedTexImage1D = NULL;
PFNGLCOMPRESSEDTEXIMAGE2DPROC glad_glCompressedTexImage2D = NULL;
@@ -320,6 +327,7 @@ PFNGLDELETEFRAMEBUFFERSPROC glad_glDeleteFramebuffers = NULL;
PFNGLDELETEPROGRAMPROC glad_glDeleteProgram = NULL;
PFNGLDELETEQUERIESPROC glad_glDeleteQueries = NULL;
PFNGLDELETERENDERBUFFERSPROC glad_glDeleteRenderbuffers = NULL;
+PFNGLDELETESAMPLERSPROC glad_glDeleteSamplers = NULL;
PFNGLDELETESHADERPROC glad_glDeleteShader = NULL;
PFNGLDELETESYNCPROC glad_glDeleteSync = NULL;
PFNGLDELETETEXTURESPROC glad_glDeleteTextures = NULL;
@@ -362,6 +370,7 @@ PFNGLGENBUFFERSPROC glad_glGenBuffers = NULL;
PFNGLGENFRAMEBUFFERSPROC glad_glGenFramebuffers = NULL;
PFNGLGENQUERIESPROC glad_glGenQueries = NULL;
PFNGLGENRENDERBUFFERSPROC glad_glGenRenderbuffers = NULL;
+PFNGLGENSAMPLERSPROC glad_glGenSamplers = NULL;
PFNGLGENTEXTURESPROC glad_glGenTextures = NULL;
PFNGLGENVERTEXARRAYSPROC glad_glGenVertexArrays = NULL;
PFNGLGENERATEMIPMAPPROC glad_glGenerateMipmap = NULL;
@@ -383,6 +392,7 @@ PFNGLGETCOMPRESSEDTEXIMAGEPROC glad_glGetCompressedTexImage = NULL;
PFNGLGETDOUBLEVPROC glad_glGetDoublev = NULL;
PFNGLGETERRORPROC glad_glGetError = NULL;
PFNGLGETFLOATVPROC glad_glGetFloatv = NULL;
+PFNGLGETFRAGDATAINDEXPROC glad_glGetFragDataIndex = NULL;
PFNGLGETFRAGDATALOCATIONPROC glad_glGetFragDataLocation = NULL;
PFNGLGETFRAMEBUFFERATTACHMENTPARAMETERIVPROC glad_glGetFramebufferAttachmentParameteriv = NULL;
PFNGLGETINTEGER64I_VPROC glad_glGetInteger64i_v = NULL;
@@ -392,10 +402,16 @@ PFNGLGETINTEGERVPROC glad_glGetIntegerv = NULL;
PFNGLGETMULTISAMPLEFVPROC glad_glGetMultisamplefv = NULL;
PFNGLGETPROGRAMINFOLOGPROC glad_glGetProgramInfoLog = NULL;
PFNGLGETPROGRAMIVPROC glad_glGetProgramiv = NULL;
+PFNGLGETQUERYOBJECTI64VPROC glad_glGetQueryObjecti64v = NULL;
PFNGLGETQUERYOBJECTIVPROC glad_glGetQueryObjectiv = NULL;
+PFNGLGETQUERYOBJECTUI64VPROC glad_glGetQueryObjectui64v = NULL;
PFNGLGETQUERYOBJECTUIVPROC glad_glGetQueryObjectuiv = NULL;
PFNGLGETQUERYIVPROC glad_glGetQueryiv = NULL;
PFNGLGETRENDERBUFFERPARAMETERIVPROC glad_glGetRenderbufferParameteriv = NULL;
+PFNGLGETSAMPLERPARAMETERIIVPROC glad_glGetSamplerParameterIiv = NULL;
+PFNGLGETSAMPLERPARAMETERIUIVPROC glad_glGetSamplerParameterIuiv = NULL;
+PFNGLGETSAMPLERPARAMETERFVPROC glad_glGetSamplerParameterfv = NULL;
+PFNGLGETSAMPLERPARAMETERIVPROC glad_glGetSamplerParameteriv = NULL;
PFNGLGETSHADERINFOLOGPROC glad_glGetShaderInfoLog = NULL;
PFNGLGETSHADERSOURCEPROC glad_glGetShaderSource = NULL;
PFNGLGETSHADERIVPROC glad_glGetShaderiv = NULL;
@@ -430,6 +446,7 @@ PFNGLISFRAMEBUFFERPROC glad_glIsFramebuffer = NULL;
PFNGLISPROGRAMPROC glad_glIsProgram = NULL;
PFNGLISQUERYPROC glad_glIsQuery = NULL;
PFNGLISRENDERBUFFERPROC glad_glIsRenderbuffer = NULL;
+PFNGLISSAMPLERPROC glad_glIsSampler = NULL;
PFNGLISSHADERPROC glad_glIsShader = NULL;
PFNGLISSYNCPROC glad_glIsSync = NULL;
PFNGLISTEXTUREPROC glad_glIsTexture = NULL;
@@ -442,6 +459,16 @@ PFNGLMAPBUFFERRANGEPROC glad_glMapBufferRange = NULL;
PFNGLMULTIDRAWARRAYSPROC glad_glMultiDrawArrays = NULL;
PFNGLMULTIDRAWELEMENTSPROC glad_glMultiDrawElements = NULL;
PFNGLMULTIDRAWELEMENTSBASEVERTEXPROC glad_glMultiDrawElementsBaseVertex = NULL;
+PFNGLMULTITEXCOORDP1UIPROC glad_glMultiTexCoordP1ui = NULL;
+PFNGLMULTITEXCOORDP1UIVPROC glad_glMultiTexCoordP1uiv = NULL;
+PFNGLMULTITEXCOORDP2UIPROC glad_glMultiTexCoordP2ui = NULL;
+PFNGLMULTITEXCOORDP2UIVPROC glad_glMultiTexCoordP2uiv = NULL;
+PFNGLMULTITEXCOORDP3UIPROC glad_glMultiTexCoordP3ui = NULL;
+PFNGLMULTITEXCOORDP3UIVPROC glad_glMultiTexCoordP3uiv = NULL;
+PFNGLMULTITEXCOORDP4UIPROC glad_glMultiTexCoordP4ui = NULL;
+PFNGLMULTITEXCOORDP4UIVPROC glad_glMultiTexCoordP4uiv = NULL;
+PFNGLNORMALP3UIPROC glad_glNormalP3ui = NULL;
+PFNGLNORMALP3UIVPROC glad_glNormalP3uiv = NULL;
PFNGLPIXELSTOREFPROC glad_glPixelStoref = NULL;
PFNGLPIXELSTOREIPROC glad_glPixelStorei = NULL;
PFNGLPOINTPARAMETERFPROC glad_glPointParameterf = NULL;
@@ -453,13 +480,22 @@ PFNGLPOLYGONMODEPROC glad_glPolygonMode = NULL;
PFNGLPOLYGONOFFSETPROC glad_glPolygonOffset = NULL;
PFNGLPRIMITIVERESTARTINDEXPROC glad_glPrimitiveRestartIndex = NULL;
PFNGLPROVOKINGVERTEXPROC glad_glProvokingVertex = NULL;
+PFNGLQUERYCOUNTERPROC glad_glQueryCounter = NULL;
PFNGLREADBUFFERPROC glad_glReadBuffer = NULL;
PFNGLREADPIXELSPROC glad_glReadPixels = NULL;
PFNGLRENDERBUFFERSTORAGEPROC glad_glRenderbufferStorage = NULL;
PFNGLRENDERBUFFERSTORAGEMULTISAMPLEPROC glad_glRenderbufferStorageMultisample = NULL;
PFNGLSAMPLECOVERAGEPROC glad_glSampleCoverage = NULL;
PFNGLSAMPLEMASKIPROC glad_glSampleMaski = NULL;
+PFNGLSAMPLERPARAMETERIIVPROC glad_glSamplerParameterIiv = NULL;
+PFNGLSAMPLERPARAMETERIUIVPROC glad_glSamplerParameterIuiv = NULL;
+PFNGLSAMPLERPARAMETERFPROC glad_glSamplerParameterf = NULL;
+PFNGLSAMPLERPARAMETERFVPROC glad_glSamplerParameterfv = NULL;
+PFNGLSAMPLERPARAMETERIPROC glad_glSamplerParameteri = NULL;
+PFNGLSAMPLERPARAMETERIVPROC glad_glSamplerParameteriv = NULL;
PFNGLSCISSORPROC glad_glScissor = NULL;
+PFNGLSECONDARYCOLORP3UIPROC glad_glSecondaryColorP3ui = NULL;
+PFNGLSECONDARYCOLORP3UIVPROC glad_glSecondaryColorP3uiv = NULL;
PFNGLSHADERSOURCEPROC glad_glShaderSource = NULL;
PFNGLSTENCILFUNCPROC glad_glStencilFunc = NULL;
PFNGLSTENCILFUNCSEPARATEPROC glad_glStencilFuncSeparate = NULL;
@@ -468,6 +504,14 @@ PFNGLSTENCILMASKSEPARATEPROC glad_glStencilMaskSeparate = NULL;
PFNGLSTENCILOPPROC glad_glStencilOp = NULL;
PFNGLSTENCILOPSEPARATEPROC glad_glStencilOpSeparate = NULL;
PFNGLTEXBUFFERPROC glad_glTexBuffer = NULL;
+PFNGLTEXCOORDP1UIPROC glad_glTexCoordP1ui = NULL;
+PFNGLTEXCOORDP1UIVPROC glad_glTexCoordP1uiv = NULL;
+PFNGLTEXCOORDP2UIPROC glad_glTexCoordP2ui = NULL;
+PFNGLTEXCOORDP2UIVPROC glad_glTexCoordP2uiv = NULL;
+PFNGLTEXCOORDP3UIPROC glad_glTexCoordP3ui = NULL;
+PFNGLTEXCOORDP3UIVPROC glad_glTexCoordP3uiv = NULL;
+PFNGLTEXCOORDP4UIPROC glad_glTexCoordP4ui = NULL;
+PFNGLTEXCOORDP4UIVPROC glad_glTexCoordP4uiv = NULL;
PFNGLTEXIMAGE1DPROC glad_glTexImage1D = NULL;
PFNGLTEXIMAGE2DPROC glad_glTexImage2D = NULL;
PFNGLTEXIMAGE2DMULTISAMPLEPROC glad_glTexImage2DMultisample = NULL;
@@ -556,6 +600,7 @@ PFNGLVERTEXATTRIB4SVPROC glad_glVertexAttrib4sv = NULL;
PFNGLVERTEXATTRIB4UBVPROC glad_glVertexAttrib4ubv = NULL;
PFNGLVERTEXATTRIB4UIVPROC glad_glVertexAttrib4uiv = NULL;
PFNGLVERTEXATTRIB4USVPROC glad_glVertexAttrib4usv = NULL;
+PFNGLVERTEXATTRIBDIVISORPROC glad_glVertexAttribDivisor = NULL;
PFNGLVERTEXATTRIBI1IPROC glad_glVertexAttribI1i = NULL;
PFNGLVERTEXATTRIBI1IVPROC glad_glVertexAttribI1iv = NULL;
PFNGLVERTEXATTRIBI1UIPROC glad_glVertexAttribI1ui = NULL;
@@ -577,7 +622,21 @@ PFNGLVERTEXATTRIBI4UIPROC glad_glVertexAttribI4ui = NULL;
PFNGLVERTEXATTRIBI4UIVPROC glad_glVertexAttribI4uiv = NULL;
PFNGLVERTEXATTRIBI4USVPROC glad_glVertexAttribI4usv = NULL;
PFNGLVERTEXATTRIBIPOINTERPROC glad_glVertexAttribIPointer = NULL;
+PFNGLVERTEXATTRIBP1UIPROC glad_glVertexAttribP1ui = NULL;
+PFNGLVERTEXATTRIBP1UIVPROC glad_glVertexAttribP1uiv = NULL;
+PFNGLVERTEXATTRIBP2UIPROC glad_glVertexAttribP2ui = NULL;
+PFNGLVERTEXATTRIBP2UIVPROC glad_glVertexAttribP2uiv = NULL;
+PFNGLVERTEXATTRIBP3UIPROC glad_glVertexAttribP3ui = NULL;
+PFNGLVERTEXATTRIBP3UIVPROC glad_glVertexAttribP3uiv = NULL;
+PFNGLVERTEXATTRIBP4UIPROC glad_glVertexAttribP4ui = NULL;
+PFNGLVERTEXATTRIBP4UIVPROC glad_glVertexAttribP4uiv = NULL;
PFNGLVERTEXATTRIBPOINTERPROC glad_glVertexAttribPointer = NULL;
+PFNGLVERTEXP2UIPROC glad_glVertexP2ui = NULL;
+PFNGLVERTEXP2UIVPROC glad_glVertexP2uiv = NULL;
+PFNGLVERTEXP3UIPROC glad_glVertexP3ui = NULL;
+PFNGLVERTEXP3UIVPROC glad_glVertexP3uiv = NULL;
+PFNGLVERTEXP4UIPROC glad_glVertexP4ui = NULL;
+PFNGLVERTEXP4UIVPROC glad_glVertexP4uiv = NULL;
PFNGLVIEWPORTPROC glad_glViewport = NULL;
PFNGLWAITSYNCPROC glad_glWaitSync = NULL;
static void load_GL_VERSION_1_0(GLADloadproc load) {
@@ -932,6 +991,67 @@ static void load_GL_VERSION_3_2(GLADloadproc load) {
glad_glGetMultisamplefv = (PFNGLGETMULTISAMPLEFVPROC)load("glGetMultisamplefv");
glad_glSampleMaski = (PFNGLSAMPLEMASKIPROC)load("glSampleMaski");
}
+static void load_GL_VERSION_3_3(GLADloadproc load) {
+ if(!GLAD_GL_VERSION_3_3) return;
+ glad_glBindFragDataLocationIndexed = (PFNGLBINDFRAGDATALOCATIONINDEXEDPROC)load("glBindFragDataLocationIndexed");
+ glad_glGetFragDataIndex = (PFNGLGETFRAGDATAINDEXPROC)load("glGetFragDataIndex");
+ glad_glGenSamplers = (PFNGLGENSAMPLERSPROC)load("glGenSamplers");
+ glad_glDeleteSamplers = (PFNGLDELETESAMPLERSPROC)load("glDeleteSamplers");
+ glad_glIsSampler = (PFNGLISSAMPLERPROC)load("glIsSampler");
+ glad_glBindSampler = (PFNGLBINDSAMPLERPROC)load("glBindSampler");
+ glad_glSamplerParameteri = (PFNGLSAMPLERPARAMETERIPROC)load("glSamplerParameteri");
+ glad_glSamplerParameteriv = (PFNGLSAMPLERPARAMETERIVPROC)load("glSamplerParameteriv");
+ glad_glSamplerParameterf = (PFNGLSAMPLERPARAMETERFPROC)load("glSamplerParameterf");
+ glad_glSamplerParameterfv = (PFNGLSAMPLERPARAMETERFVPROC)load("glSamplerParameterfv");
+ glad_glSamplerParameterIiv = (PFNGLSAMPLERPARAMETERIIVPROC)load("glSamplerParameterIiv");
+ glad_glSamplerParameterIuiv = (PFNGLSAMPLERPARAMETERIUIVPROC)load("glSamplerParameterIuiv");
+ glad_glGetSamplerParameteriv = (PFNGLGETSAMPLERPARAMETERIVPROC)load("glGetSamplerParameteriv");
+ glad_glGetSamplerParameterIiv = (PFNGLGETSAMPLERPARAMETERIIVPROC)load("glGetSamplerParameterIiv");
+ glad_glGetSamplerParameterfv = (PFNGLGETSAMPLERPARAMETERFVPROC)load("glGetSamplerParameterfv");
+ glad_glGetSamplerParameterIuiv = (PFNGLGETSAMPLERPARAMETERIUIVPROC)load("glGetSamplerParameterIuiv");
+ glad_glQueryCounter = (PFNGLQUERYCOUNTERPROC)load("glQueryCounter");
+ glad_glGetQueryObjecti64v = (PFNGLGETQUERYOBJECTI64VPROC)load("glGetQueryObjecti64v");
+ glad_glGetQueryObjectui64v = (PFNGLGETQUERYOBJECTUI64VPROC)load("glGetQueryObjectui64v");
+ glad_glVertexAttribDivisor = (PFNGLVERTEXATTRIBDIVISORPROC)load("glVertexAttribDivisor");
+ glad_glVertexAttribP1ui = (PFNGLVERTEXATTRIBP1UIPROC)load("glVertexAttribP1ui");
+ glad_glVertexAttribP1uiv = (PFNGLVERTEXATTRIBP1UIVPROC)load("glVertexAttribP1uiv");
+ glad_glVertexAttribP2ui = (PFNGLVERTEXATTRIBP2UIPROC)load("glVertexAttribP2ui");
+ glad_glVertexAttribP2uiv = (PFNGLVERTEXATTRIBP2UIVPROC)load("glVertexAttribP2uiv");
+ glad_glVertexAttribP3ui = (PFNGLVERTEXATTRIBP3UIPROC)load("glVertexAttribP3ui");
+ glad_glVertexAttribP3uiv = (PFNGLVERTEXATTRIBP3UIVPROC)load("glVertexAttribP3uiv");
+ glad_glVertexAttribP4ui = (PFNGLVERTEXATTRIBP4UIPROC)load("glVertexAttribP4ui");
+ glad_glVertexAttribP4uiv = (PFNGLVERTEXATTRIBP4UIVPROC)load("glVertexAttribP4uiv");
+ glad_glVertexP2ui = (PFNGLVERTEXP2UIPROC)load("glVertexP2ui");
+ glad_glVertexP2uiv = (PFNGLVERTEXP2UIVPROC)load("glVertexP2uiv");
+ glad_glVertexP3ui = (PFNGLVERTEXP3UIPROC)load("glVertexP3ui");
+ glad_glVertexP3uiv = (PFNGLVERTEXP3UIVPROC)load("glVertexP3uiv");
+ glad_glVertexP4ui = (PFNGLVERTEXP4UIPROC)load("glVertexP4ui");
+ glad_glVertexP4uiv = (PFNGLVERTEXP4UIVPROC)load("glVertexP4uiv");
+ glad_glTexCoordP1ui = (PFNGLTEXCOORDP1UIPROC)load("glTexCoordP1ui");
+ glad_glTexCoordP1uiv = (PFNGLTEXCOORDP1UIVPROC)load("glTexCoordP1uiv");
+ glad_glTexCoordP2ui = (PFNGLTEXCOORDP2UIPROC)load("glTexCoordP2ui");
+ glad_glTexCoordP2uiv = (PFNGLTEXCOORDP2UIVPROC)load("glTexCoordP2uiv");
+ glad_glTexCoordP3ui = (PFNGLTEXCOORDP3UIPROC)load("glTexCoordP3ui");
+ glad_glTexCoordP3uiv = (PFNGLTEXCOORDP3UIVPROC)load("glTexCoordP3uiv");
+ glad_glTexCoordP4ui = (PFNGLTEXCOORDP4UIPROC)load("glTexCoordP4ui");
+ glad_glTexCoordP4uiv = (PFNGLTEXCOORDP4UIVPROC)load("glTexCoordP4uiv");
+ glad_glMultiTexCoordP1ui = (PFNGLMULTITEXCOORDP1UIPROC)load("glMultiTexCoordP1ui");
+ glad_glMultiTexCoordP1uiv = (PFNGLMULTITEXCOORDP1UIVPROC)load("glMultiTexCoordP1uiv");
+ glad_glMultiTexCoordP2ui = (PFNGLMULTITEXCOORDP2UIPROC)load("glMultiTexCoordP2ui");
+ glad_glMultiTexCoordP2uiv = (PFNGLMULTITEXCOORDP2UIVPROC)load("glMultiTexCoordP2uiv");
+ glad_glMultiTexCoordP3ui = (PFNGLMULTITEXCOORDP3UIPROC)load("glMultiTexCoordP3ui");
+ glad_glMultiTexCoordP3uiv = (PFNGLMULTITEXCOORDP3UIVPROC)load("glMultiTexCoordP3uiv");
+ glad_glMultiTexCoordP4ui = (PFNGLMULTITEXCOORDP4UIPROC)load("glMultiTexCoordP4ui");
+ glad_glMultiTexCoordP4uiv = (PFNGLMULTITEXCOORDP4UIVPROC)load("glMultiTexCoordP4uiv");
+ glad_glNormalP3ui = (PFNGLNORMALP3UIPROC)load("glNormalP3ui");
+ glad_glNormalP3uiv = (PFNGLNORMALP3UIVPROC)load("glNormalP3uiv");
+ glad_glColorP3ui = (PFNGLCOLORP3UIPROC)load("glColorP3ui");
+ glad_glColorP3uiv = (PFNGLCOLORP3UIVPROC)load("glColorP3uiv");
+ glad_glColorP4ui = (PFNGLCOLORP4UIPROC)load("glColorP4ui");
+ glad_glColorP4uiv = (PFNGLCOLORP4UIVPROC)load("glColorP4uiv");
+ glad_glSecondaryColorP3ui = (PFNGLSECONDARYCOLORP3UIPROC)load("glSecondaryColorP3ui");
+ glad_glSecondaryColorP3uiv = (PFNGLSECONDARYCOLORP3UIVPROC)load("glSecondaryColorP3uiv");
+}
static int find_extensionsGL(void) {
if (!get_exts()) return 0;
(void)&has_ext;
@@ -986,9 +1106,10 @@ static void find_coreGL(void) {
GLAD_GL_VERSION_3_0 = (major == 3 && minor >= 0) || major > 3;
GLAD_GL_VERSION_3_1 = (major == 3 && minor >= 1) || major > 3;
GLAD_GL_VERSION_3_2 = (major == 3 && minor >= 2) || major > 3;
- if (GLVersion.major > 3 || (GLVersion.major >= 3 && GLVersion.minor >= 2)) {
+ GLAD_GL_VERSION_3_3 = (major == 3 && minor >= 3) || major > 3;
+ if (GLVersion.major > 3 || (GLVersion.major >= 3 && GLVersion.minor >= 3)) {
max_loaded_major = 3;
- max_loaded_minor = 2;
+ max_loaded_minor = 3;
}
}
@@ -1009,6 +1130,7 @@ int gladLoadGLLoader(GLADloadproc load) {
load_GL_VERSION_3_0(load);
load_GL_VERSION_3_1(load);
load_GL_VERSION_3_2(load);
+ load_GL_VERSION_3_3(load);
if (!find_extensionsGL()) return 0;
return GLVersion.major != 0 || GLVersion.minor != 0;
diff --git a/pugl/test/glad/glad.h b/pugl/examples/glad/glad.h
index b743679..d8068c6 100644
--- a/pugl/test/glad/glad.h
+++ b/pugl/examples/glad/glad.h
@@ -4,7 +4,7 @@
Language/Generator: C/C++
Specification: gl
- APIs: gl=3.2
+ APIs: gl=3.3
Profile: core
Extensions:
@@ -14,9 +14,9 @@
Reproducible: True
Commandline:
- --profile="core" --api="gl=3.2" --generator="c" --spec="gl" --local-files --extensions=""
+ --profile="core" --api="gl=3.3" --generator="c" --spec="gl" --local-files --extensions=""
Online:
- https://glad.dav1d.de/#profile=core&language=c&specification=gl&loader=on&api=gl%3D3.2
+ https://glad.dav1d.de/#profile=core&language=c&specification=gl&loader=on&api=gl%3D3.3
*/
@@ -935,6 +935,22 @@ typedef void (APIENTRY *GLVULKANPROCNV)(void);
#define GL_MAX_COLOR_TEXTURE_SAMPLES 0x910E
#define GL_MAX_DEPTH_TEXTURE_SAMPLES 0x910F
#define GL_MAX_INTEGER_SAMPLES 0x9110
+#define GL_VERTEX_ATTRIB_ARRAY_DIVISOR 0x88FE
+#define GL_SRC1_COLOR 0x88F9
+#define GL_ONE_MINUS_SRC1_COLOR 0x88FA
+#define GL_ONE_MINUS_SRC1_ALPHA 0x88FB
+#define GL_MAX_DUAL_SOURCE_DRAW_BUFFERS 0x88FC
+#define GL_ANY_SAMPLES_PASSED 0x8C2F
+#define GL_SAMPLER_BINDING 0x8919
+#define GL_RGB10_A2UI 0x906F
+#define GL_TEXTURE_SWIZZLE_R 0x8E42
+#define GL_TEXTURE_SWIZZLE_G 0x8E43
+#define GL_TEXTURE_SWIZZLE_B 0x8E44
+#define GL_TEXTURE_SWIZZLE_A 0x8E45
+#define GL_TEXTURE_SWIZZLE_RGBA 0x8E46
+#define GL_TIME_ELAPSED 0x88BF
+#define GL_TIMESTAMP 0x8E28
+#define GL_INT_2_10_10_10_REV 0x8D9F
#ifndef GL_VERSION_1_0
#define GL_VERSION_1_0 1
GLAPI int GLAD_GL_VERSION_1_0;
@@ -1927,6 +1943,184 @@ typedef void (APIENTRYP PFNGLSAMPLEMASKIPROC)(GLuint maskNumber, GLbitfield mask
GLAPI PFNGLSAMPLEMASKIPROC glad_glSampleMaski;
#define glSampleMaski glad_glSampleMaski
#endif
+#ifndef GL_VERSION_3_3
+#define GL_VERSION_3_3 1
+GLAPI int GLAD_GL_VERSION_3_3;
+typedef void (APIENTRYP PFNGLBINDFRAGDATALOCATIONINDEXEDPROC)(GLuint program, GLuint colorNumber, GLuint index, const GLchar *name);
+GLAPI PFNGLBINDFRAGDATALOCATIONINDEXEDPROC glad_glBindFragDataLocationIndexed;
+#define glBindFragDataLocationIndexed glad_glBindFragDataLocationIndexed
+typedef GLint (APIENTRYP PFNGLGETFRAGDATAINDEXPROC)(GLuint program, const GLchar *name);
+GLAPI PFNGLGETFRAGDATAINDEXPROC glad_glGetFragDataIndex;
+#define glGetFragDataIndex glad_glGetFragDataIndex
+typedef void (APIENTRYP PFNGLGENSAMPLERSPROC)(GLsizei count, GLuint *samplers);
+GLAPI PFNGLGENSAMPLERSPROC glad_glGenSamplers;
+#define glGenSamplers glad_glGenSamplers
+typedef void (APIENTRYP PFNGLDELETESAMPLERSPROC)(GLsizei count, const GLuint *samplers);
+GLAPI PFNGLDELETESAMPLERSPROC glad_glDeleteSamplers;
+#define glDeleteSamplers glad_glDeleteSamplers
+typedef GLboolean (APIENTRYP PFNGLISSAMPLERPROC)(GLuint sampler);
+GLAPI PFNGLISSAMPLERPROC glad_glIsSampler;
+#define glIsSampler glad_glIsSampler
+typedef void (APIENTRYP PFNGLBINDSAMPLERPROC)(GLuint unit, GLuint sampler);
+GLAPI PFNGLBINDSAMPLERPROC glad_glBindSampler;
+#define glBindSampler glad_glBindSampler
+typedef void (APIENTRYP PFNGLSAMPLERPARAMETERIPROC)(GLuint sampler, GLenum pname, GLint param);
+GLAPI PFNGLSAMPLERPARAMETERIPROC glad_glSamplerParameteri;
+#define glSamplerParameteri glad_glSamplerParameteri
+typedef void (APIENTRYP PFNGLSAMPLERPARAMETERIVPROC)(GLuint sampler, GLenum pname, const GLint *param);
+GLAPI PFNGLSAMPLERPARAMETERIVPROC glad_glSamplerParameteriv;
+#define glSamplerParameteriv glad_glSamplerParameteriv
+typedef void (APIENTRYP PFNGLSAMPLERPARAMETERFPROC)(GLuint sampler, GLenum pname, GLfloat param);
+GLAPI PFNGLSAMPLERPARAMETERFPROC glad_glSamplerParameterf;
+#define glSamplerParameterf glad_glSamplerParameterf
+typedef void (APIENTRYP PFNGLSAMPLERPARAMETERFVPROC)(GLuint sampler, GLenum pname, const GLfloat *param);
+GLAPI PFNGLSAMPLERPARAMETERFVPROC glad_glSamplerParameterfv;
+#define glSamplerParameterfv glad_glSamplerParameterfv
+typedef void (APIENTRYP PFNGLSAMPLERPARAMETERIIVPROC)(GLuint sampler, GLenum pname, const GLint *param);
+GLAPI PFNGLSAMPLERPARAMETERIIVPROC glad_glSamplerParameterIiv;
+#define glSamplerParameterIiv glad_glSamplerParameterIiv
+typedef void (APIENTRYP PFNGLSAMPLERPARAMETERIUIVPROC)(GLuint sampler, GLenum pname, const GLuint *param);
+GLAPI PFNGLSAMPLERPARAMETERIUIVPROC glad_glSamplerParameterIuiv;
+#define glSamplerParameterIuiv glad_glSamplerParameterIuiv
+typedef void (APIENTRYP PFNGLGETSAMPLERPARAMETERIVPROC)(GLuint sampler, GLenum pname, GLint *params);
+GLAPI PFNGLGETSAMPLERPARAMETERIVPROC glad_glGetSamplerParameteriv;
+#define glGetSamplerParameteriv glad_glGetSamplerParameteriv
+typedef void (APIENTRYP PFNGLGETSAMPLERPARAMETERIIVPROC)(GLuint sampler, GLenum pname, GLint *params);
+GLAPI PFNGLGETSAMPLERPARAMETERIIVPROC glad_glGetSamplerParameterIiv;
+#define glGetSamplerParameterIiv glad_glGetSamplerParameterIiv
+typedef void (APIENTRYP PFNGLGETSAMPLERPARAMETERFVPROC)(GLuint sampler, GLenum pname, GLfloat *params);
+GLAPI PFNGLGETSAMPLERPARAMETERFVPROC glad_glGetSamplerParameterfv;
+#define glGetSamplerParameterfv glad_glGetSamplerParameterfv
+typedef void (APIENTRYP PFNGLGETSAMPLERPARAMETERIUIVPROC)(GLuint sampler, GLenum pname, GLuint *params);
+GLAPI PFNGLGETSAMPLERPARAMETERIUIVPROC glad_glGetSamplerParameterIuiv;
+#define glGetSamplerParameterIuiv glad_glGetSamplerParameterIuiv
+typedef void (APIENTRYP PFNGLQUERYCOUNTERPROC)(GLuint id, GLenum target);
+GLAPI PFNGLQUERYCOUNTERPROC glad_glQueryCounter;
+#define glQueryCounter glad_glQueryCounter
+typedef void (APIENTRYP PFNGLGETQUERYOBJECTI64VPROC)(GLuint id, GLenum pname, GLint64 *params);
+GLAPI PFNGLGETQUERYOBJECTI64VPROC glad_glGetQueryObjecti64v;
+#define glGetQueryObjecti64v glad_glGetQueryObjecti64v
+typedef void (APIENTRYP PFNGLGETQUERYOBJECTUI64VPROC)(GLuint id, GLenum pname, GLuint64 *params);
+GLAPI PFNGLGETQUERYOBJECTUI64VPROC glad_glGetQueryObjectui64v;
+#define glGetQueryObjectui64v glad_glGetQueryObjectui64v
+typedef void (APIENTRYP PFNGLVERTEXATTRIBDIVISORPROC)(GLuint index, GLuint divisor);
+GLAPI PFNGLVERTEXATTRIBDIVISORPROC glad_glVertexAttribDivisor;
+#define glVertexAttribDivisor glad_glVertexAttribDivisor
+typedef void (APIENTRYP PFNGLVERTEXATTRIBP1UIPROC)(GLuint index, GLenum type, GLboolean normalized, GLuint value);
+GLAPI PFNGLVERTEXATTRIBP1UIPROC glad_glVertexAttribP1ui;
+#define glVertexAttribP1ui glad_glVertexAttribP1ui
+typedef void (APIENTRYP PFNGLVERTEXATTRIBP1UIVPROC)(GLuint index, GLenum type, GLboolean normalized, const GLuint *value);
+GLAPI PFNGLVERTEXATTRIBP1UIVPROC glad_glVertexAttribP1uiv;
+#define glVertexAttribP1uiv glad_glVertexAttribP1uiv
+typedef void (APIENTRYP PFNGLVERTEXATTRIBP2UIPROC)(GLuint index, GLenum type, GLboolean normalized, GLuint value);
+GLAPI PFNGLVERTEXATTRIBP2UIPROC glad_glVertexAttribP2ui;
+#define glVertexAttribP2ui glad_glVertexAttribP2ui
+typedef void (APIENTRYP PFNGLVERTEXATTRIBP2UIVPROC)(GLuint index, GLenum type, GLboolean normalized, const GLuint *value);
+GLAPI PFNGLVERTEXATTRIBP2UIVPROC glad_glVertexAttribP2uiv;
+#define glVertexAttribP2uiv glad_glVertexAttribP2uiv
+typedef void (APIENTRYP PFNGLVERTEXATTRIBP3UIPROC)(GLuint index, GLenum type, GLboolean normalized, GLuint value);
+GLAPI PFNGLVERTEXATTRIBP3UIPROC glad_glVertexAttribP3ui;
+#define glVertexAttribP3ui glad_glVertexAttribP3ui
+typedef void (APIENTRYP PFNGLVERTEXATTRIBP3UIVPROC)(GLuint index, GLenum type, GLboolean normalized, const GLuint *value);
+GLAPI PFNGLVERTEXATTRIBP3UIVPROC glad_glVertexAttribP3uiv;
+#define glVertexAttribP3uiv glad_glVertexAttribP3uiv
+typedef void (APIENTRYP PFNGLVERTEXATTRIBP4UIPROC)(GLuint index, GLenum type, GLboolean normalized, GLuint value);
+GLAPI PFNGLVERTEXATTRIBP4UIPROC glad_glVertexAttribP4ui;
+#define glVertexAttribP4ui glad_glVertexAttribP4ui
+typedef void (APIENTRYP PFNGLVERTEXATTRIBP4UIVPROC)(GLuint index, GLenum type, GLboolean normalized, const GLuint *value);
+GLAPI PFNGLVERTEXATTRIBP4UIVPROC glad_glVertexAttribP4uiv;
+#define glVertexAttribP4uiv glad_glVertexAttribP4uiv
+typedef void (APIENTRYP PFNGLVERTEXP2UIPROC)(GLenum type, GLuint value);
+GLAPI PFNGLVERTEXP2UIPROC glad_glVertexP2ui;
+#define glVertexP2ui glad_glVertexP2ui
+typedef void (APIENTRYP PFNGLVERTEXP2UIVPROC)(GLenum type, const GLuint *value);
+GLAPI PFNGLVERTEXP2UIVPROC glad_glVertexP2uiv;
+#define glVertexP2uiv glad_glVertexP2uiv
+typedef void (APIENTRYP PFNGLVERTEXP3UIPROC)(GLenum type, GLuint value);
+GLAPI PFNGLVERTEXP3UIPROC glad_glVertexP3ui;
+#define glVertexP3ui glad_glVertexP3ui
+typedef void (APIENTRYP PFNGLVERTEXP3UIVPROC)(GLenum type, const GLuint *value);
+GLAPI PFNGLVERTEXP3UIVPROC glad_glVertexP3uiv;
+#define glVertexP3uiv glad_glVertexP3uiv
+typedef void (APIENTRYP PFNGLVERTEXP4UIPROC)(GLenum type, GLuint value);
+GLAPI PFNGLVERTEXP4UIPROC glad_glVertexP4ui;
+#define glVertexP4ui glad_glVertexP4ui
+typedef void (APIENTRYP PFNGLVERTEXP4UIVPROC)(GLenum type, const GLuint *value);
+GLAPI PFNGLVERTEXP4UIVPROC glad_glVertexP4uiv;
+#define glVertexP4uiv glad_glVertexP4uiv
+typedef void (APIENTRYP PFNGLTEXCOORDP1UIPROC)(GLenum type, GLuint coords);
+GLAPI PFNGLTEXCOORDP1UIPROC glad_glTexCoordP1ui;
+#define glTexCoordP1ui glad_glTexCoordP1ui
+typedef void (APIENTRYP PFNGLTEXCOORDP1UIVPROC)(GLenum type, const GLuint *coords);
+GLAPI PFNGLTEXCOORDP1UIVPROC glad_glTexCoordP1uiv;
+#define glTexCoordP1uiv glad_glTexCoordP1uiv
+typedef void (APIENTRYP PFNGLTEXCOORDP2UIPROC)(GLenum type, GLuint coords);
+GLAPI PFNGLTEXCOORDP2UIPROC glad_glTexCoordP2ui;
+#define glTexCoordP2ui glad_glTexCoordP2ui
+typedef void (APIENTRYP PFNGLTEXCOORDP2UIVPROC)(GLenum type, const GLuint *coords);
+GLAPI PFNGLTEXCOORDP2UIVPROC glad_glTexCoordP2uiv;
+#define glTexCoordP2uiv glad_glTexCoordP2uiv
+typedef void (APIENTRYP PFNGLTEXCOORDP3UIPROC)(GLenum type, GLuint coords);
+GLAPI PFNGLTEXCOORDP3UIPROC glad_glTexCoordP3ui;
+#define glTexCoordP3ui glad_glTexCoordP3ui
+typedef void (APIENTRYP PFNGLTEXCOORDP3UIVPROC)(GLenum type, const GLuint *coords);
+GLAPI PFNGLTEXCOORDP3UIVPROC glad_glTexCoordP3uiv;
+#define glTexCoordP3uiv glad_glTexCoordP3uiv
+typedef void (APIENTRYP PFNGLTEXCOORDP4UIPROC)(GLenum type, GLuint coords);
+GLAPI PFNGLTEXCOORDP4UIPROC glad_glTexCoordP4ui;
+#define glTexCoordP4ui glad_glTexCoordP4ui
+typedef void (APIENTRYP PFNGLTEXCOORDP4UIVPROC)(GLenum type, const GLuint *coords);
+GLAPI PFNGLTEXCOORDP4UIVPROC glad_glTexCoordP4uiv;
+#define glTexCoordP4uiv glad_glTexCoordP4uiv
+typedef void (APIENTRYP PFNGLMULTITEXCOORDP1UIPROC)(GLenum texture, GLenum type, GLuint coords);
+GLAPI PFNGLMULTITEXCOORDP1UIPROC glad_glMultiTexCoordP1ui;
+#define glMultiTexCoordP1ui glad_glMultiTexCoordP1ui
+typedef void (APIENTRYP PFNGLMULTITEXCOORDP1UIVPROC)(GLenum texture, GLenum type, const GLuint *coords);
+GLAPI PFNGLMULTITEXCOORDP1UIVPROC glad_glMultiTexCoordP1uiv;
+#define glMultiTexCoordP1uiv glad_glMultiTexCoordP1uiv
+typedef void (APIENTRYP PFNGLMULTITEXCOORDP2UIPROC)(GLenum texture, GLenum type, GLuint coords);
+GLAPI PFNGLMULTITEXCOORDP2UIPROC glad_glMultiTexCoordP2ui;
+#define glMultiTexCoordP2ui glad_glMultiTexCoordP2ui
+typedef void (APIENTRYP PFNGLMULTITEXCOORDP2UIVPROC)(GLenum texture, GLenum type, const GLuint *coords);
+GLAPI PFNGLMULTITEXCOORDP2UIVPROC glad_glMultiTexCoordP2uiv;
+#define glMultiTexCoordP2uiv glad_glMultiTexCoordP2uiv
+typedef void (APIENTRYP PFNGLMULTITEXCOORDP3UIPROC)(GLenum texture, GLenum type, GLuint coords);
+GLAPI PFNGLMULTITEXCOORDP3UIPROC glad_glMultiTexCoordP3ui;
+#define glMultiTexCoordP3ui glad_glMultiTexCoordP3ui
+typedef void (APIENTRYP PFNGLMULTITEXCOORDP3UIVPROC)(GLenum texture, GLenum type, const GLuint *coords);
+GLAPI PFNGLMULTITEXCOORDP3UIVPROC glad_glMultiTexCoordP3uiv;
+#define glMultiTexCoordP3uiv glad_glMultiTexCoordP3uiv
+typedef void (APIENTRYP PFNGLMULTITEXCOORDP4UIPROC)(GLenum texture, GLenum type, GLuint coords);
+GLAPI PFNGLMULTITEXCOORDP4UIPROC glad_glMultiTexCoordP4ui;
+#define glMultiTexCoordP4ui glad_glMultiTexCoordP4ui
+typedef void (APIENTRYP PFNGLMULTITEXCOORDP4UIVPROC)(GLenum texture, GLenum type, const GLuint *coords);
+GLAPI PFNGLMULTITEXCOORDP4UIVPROC glad_glMultiTexCoordP4uiv;
+#define glMultiTexCoordP4uiv glad_glMultiTexCoordP4uiv
+typedef void (APIENTRYP PFNGLNORMALP3UIPROC)(GLenum type, GLuint coords);
+GLAPI PFNGLNORMALP3UIPROC glad_glNormalP3ui;
+#define glNormalP3ui glad_glNormalP3ui
+typedef void (APIENTRYP PFNGLNORMALP3UIVPROC)(GLenum type, const GLuint *coords);
+GLAPI PFNGLNORMALP3UIVPROC glad_glNormalP3uiv;
+#define glNormalP3uiv glad_glNormalP3uiv
+typedef void (APIENTRYP PFNGLCOLORP3UIPROC)(GLenum type, GLuint color);
+GLAPI PFNGLCOLORP3UIPROC glad_glColorP3ui;
+#define glColorP3ui glad_glColorP3ui
+typedef void (APIENTRYP PFNGLCOLORP3UIVPROC)(GLenum type, const GLuint *color);
+GLAPI PFNGLCOLORP3UIVPROC glad_glColorP3uiv;
+#define glColorP3uiv glad_glColorP3uiv
+typedef void (APIENTRYP PFNGLCOLORP4UIPROC)(GLenum type, GLuint color);
+GLAPI PFNGLCOLORP4UIPROC glad_glColorP4ui;
+#define glColorP4ui glad_glColorP4ui
+typedef void (APIENTRYP PFNGLCOLORP4UIVPROC)(GLenum type, const GLuint *color);
+GLAPI PFNGLCOLORP4UIVPROC glad_glColorP4uiv;
+#define glColorP4uiv glad_glColorP4uiv
+typedef void (APIENTRYP PFNGLSECONDARYCOLORP3UIPROC)(GLenum type, GLuint color);
+GLAPI PFNGLSECONDARYCOLORP3UIPROC glad_glSecondaryColorP3ui;
+#define glSecondaryColorP3ui glad_glSecondaryColorP3ui
+typedef void (APIENTRYP PFNGLSECONDARYCOLORP3UIVPROC)(GLenum type, const GLuint *color);
+GLAPI PFNGLSECONDARYCOLORP3UIVPROC glad_glSecondaryColorP3uiv;
+#define glSecondaryColorP3uiv glad_glSecondaryColorP3uiv
+#endif
#ifdef __cplusplus
}
diff --git a/pugl/test/glad/khrplatform.h b/pugl/examples/glad/khrplatform.h
index 5b55ea2..5b55ea2 100644
--- a/pugl/test/glad/khrplatform.h
+++ b/pugl/examples/glad/khrplatform.h
diff --git a/pugl/test/pugl_cairo_test.c b/pugl/examples/pugl_cairo_demo.c
index 23c2b4e..483446f 100644
--- a/pugl/test/pugl_cairo_test.c
+++ b/pugl/examples/pugl_cairo_demo.c
@@ -1,5 +1,5 @@
/*
- Copyright 2012-2019 David Robillard <http://drobilla.net>
+ Copyright 2012-2020 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,13 +15,14 @@
*/
/**
- @file pugl_cairo_test.c A simple Pugl test that creates a top-level window.
+ @file pugl_cairo_demo.c An example of drawing with Cairo.
*/
-#include "test_utils.h"
+#include "demo_utils.h"
+#include "test/test_utils.h"
#include "pugl/pugl.h"
-#include "pugl/pugl_cairo_backend.h"
+#include "pugl/pugl_cairo.h"
#include <cairo.h>
@@ -30,13 +31,14 @@
#include <stdio.h>
#include <string.h>
-static PuglWorld* world = NULL;
-PuglTestOptions opts = {0};
-
-static int quit = 0;
-static bool entered = false;
-static bool mouseDown = false;
-static unsigned framesDrawn = 0;
+typedef struct {
+ PuglTestOptions opts;
+ PuglWorld* world;
+ unsigned framesDrawn;
+ int quit;
+ bool entered;
+ bool mouseDown;
+} PuglTestApp;
typedef struct {
int x;
@@ -46,11 +48,11 @@ typedef struct {
const char* label;
} Button;
-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 const 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)
@@ -76,14 +78,14 @@ roundedBox(cairo_t* cr, double x, double y, double w, double h)
}
static void
-buttonDraw(cairo_t* cr, const Button* but, const double time)
+buttonDraw(PuglTestApp* app, 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 (mouseDown) {
+ if (app->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);
@@ -110,20 +112,42 @@ buttonDraw(cairo_t* cr, const Button* but, const double time)
}
static void
-onDisplay(PuglView* view)
+postButtonRedisplay(PuglView* view)
+{
+ const PuglRect frame = puglGetFrame(view);
+ const double width = frame.width;
+ const double height = frame.height;
+ const double scaleX = (width - (512 / width)) / 512.0;
+ const double scaleY = (height - (512 / height)) / 512.0;
+
+ for (const Button* b = buttons; b->label; ++b) {
+ const double span = sqrt(b->w * b->w + b->h * b->h);
+ const PuglRect rect = {(b->x - span) * scaleX,
+ (b->y - span) * scaleY,
+ span * 2.0 * scaleX,
+ span * 2.0 * scaleY};
+
+ puglPostRedisplayRect(view, rect);
+ }
+}
+
+static void
+onDisplay(PuglTestApp* app, PuglView* view, const PuglEventExpose* event)
{
cairo_t* cr = (cairo_t*)puglGetContext(view);
+ cairo_rectangle(cr, event->x, event->y, event->width, event->height);
+ cairo_clip_preserve(cr);
+
// Draw background
const PuglRect frame = puglGetFrame(view);
const double width = frame.width;
const double height = frame.height;
- if (entered) {
+ if (app->entered) {
cairo_set_source_rgb(cr, 0.1, 0.1, 0.1);
} else {
cairo_set_source_rgb(cr, 0, 0, 0);
}
- cairo_rectangle(cr, 0, 0, width, height);
cairo_fill(cr);
// Scale to view size
@@ -132,47 +156,60 @@ onDisplay(PuglView* view)
cairo_scale(cr, scaleX, scaleY);
// Draw button
- for (Button* b = buttons; b->label; ++b) {
- buttonDraw(cr, b, opts.continuous ? puglGetTime(world) : 0.0);
+ for (const Button* b = buttons; b->label; ++b) {
+ buttonDraw(app,
+ cr,
+ b,
+ app->opts.continuous ? puglGetTime(app->world) : 0.0);
}
- ++framesDrawn;
+ ++app->framesDrawn;
}
static void
onClose(PuglView* view)
{
- (void)view;
- quit = 1;
+ PuglTestApp* app = (PuglTestApp*)puglGetHandle(view);
+
+ app->quit = 1;
}
static PuglStatus
onEvent(PuglView* view, const PuglEvent* event)
{
+ PuglTestApp* app = (PuglTestApp*)puglGetHandle(view);
+
+ printEvent(event, "Event: ", app->opts.verbose);
+
switch (event->type) {
case PUGL_KEY_PRESS:
if (event->key.key == 'q' || event->key.key == PUGL_KEY_ESCAPE) {
- quit = 1;
+ app->quit = 1;
}
break;
case PUGL_BUTTON_PRESS:
- mouseDown = true;
- puglPostRedisplay(view);
+ app->mouseDown = true;
+ postButtonRedisplay(view);
break;
case PUGL_BUTTON_RELEASE:
- mouseDown = false;
- puglPostRedisplay(view);
+ app->mouseDown = false;
+ postButtonRedisplay(view);
break;
- case PUGL_ENTER_NOTIFY:
- entered = true;
+ case PUGL_POINTER_IN:
+ app->entered = true;
puglPostRedisplay(view);
break;
- case PUGL_LEAVE_NOTIFY:
- entered = false;
+ case PUGL_POINTER_OUT:
+ app->entered = false;
puglPostRedisplay(view);
break;
+ case PUGL_UPDATE:
+ if (app->opts.continuous) {
+ puglPostRedisplay(view);
+ }
+ break;
case PUGL_EXPOSE:
- onDisplay(view);
+ onDisplay(app, view, &event->expose);
break;
case PUGL_CLOSE:
onClose(view);
@@ -186,47 +223,48 @@ onEvent(PuglView* view, const PuglEvent* event)
int
main(int argc, char** argv)
{
- opts = puglParseTestOptions(&argc, &argv);
- if (opts.help) {
+ PuglTestApp app;
+ memset(&app, 0, sizeof(app));
+
+ app.opts = puglParseTestOptions(&argc, &argv);
+ if (app.opts.help) {
puglPrintTestUsage("pugl_test", "");
return 1;
}
- world = puglNewWorld();
- puglSetClassName(world, "PuglCairoTest");
+ app.world = puglNewWorld(PUGL_PROGRAM, 0);
+ puglSetClassName(app.world, "PuglCairoTest");
PuglRect frame = { 0, 0, 512, 512 };
- PuglView* view = puglNewView(world);
+ PuglView* view = puglNewView(app.world);
+
+ puglSetWindowTitle(view, "Pugl Cairo Demo");
puglSetFrame(view, frame);
puglSetMinSize(view, 256, 256);
- puglSetViewHint(view, PUGL_RESIZABLE, opts.resizable);
+ puglSetViewHint(view, PUGL_RESIZABLE, app.opts.resizable);
+ puglSetHandle(view, &app);
puglSetBackend(view, puglCairoBackend());
-
- puglSetViewHint(view, PUGL_IGNORE_KEY_REPEAT, opts.ignoreKeyRepeat);
+ puglSetViewHint(view, PUGL_IGNORE_KEY_REPEAT, app.opts.ignoreKeyRepeat);
puglSetEventFunc(view, onEvent);
- if (puglCreateWindow(view, "Pugl Test")) {
- return 1;
+ PuglStatus st = puglRealize(view);
+ if (st) {
+ return logError("Failed to create window (%s)\n", puglStrerror(st));
}
puglShowWindow(view);
- PuglFpsPrinter fpsPrinter = { puglGetTime(world) };
- while (!quit) {
- if (opts.continuous) {
- puglPostRedisplay(view);
- } else {
- puglPollEvents(world, -1);
- }
-
- puglDispatchEvents(world);
+ PuglFpsPrinter fpsPrinter = { puglGetTime(app.world) };
+ const double timeout = app.opts.continuous ? (1 / 60.0) : -1.0;
+ while (!app.quit) {
+ puglUpdate(app.world, timeout);
- if (opts.continuous) {
- puglPrintFps(world, &fpsPrinter, &framesDrawn);
+ if (app.opts.continuous) {
+ puglPrintFps(app.world, &fpsPrinter, &app.framesDrawn);
}
}
puglFreeView(view);
- puglFreeWorld(world);
+ puglFreeWorld(app.world);
return 0;
}
diff --git a/pugl/test/pugl_test.c b/pugl/examples/pugl_embed_demo.c
index 4bd5f80..3a7b051 100644
--- a/pugl/test/pugl_test.c
+++ b/pugl/examples/pugl_embed_demo.c
@@ -1,5 +1,5 @@
/*
- Copyright 2012-2019 David Robillard <http://drobilla.net>
+ Copyright 2012-2020 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,16 +15,16 @@
*/
/**
- @file pugl_test.c A simple Pugl test that creates a top-level window.
+ @file pugl_embed_demo.c An example of embedding a view in another.
*/
-#define GL_SILENCE_DEPRECATION 1
-
-#include "test_utils.h"
+#include "cube_view.h"
+#include "demo_utils.h"
+#include "test/test_utils.h"
#include "pugl/gl.h"
#include "pugl/pugl.h"
-#include "pugl/pugl_gl_backend.h"
+#include "pugl/pugl_gl.h"
#include <math.h>
#include <stdbool.h>
@@ -32,7 +32,8 @@
#include <stdio.h>
#include <string.h>
-static const int borderWidth = 64;
+static const int borderWidth = 64;
+static const uintptr_t reverseTimerId = 1u;
typedef struct
{
@@ -41,16 +42,24 @@ typedef struct
PuglView* child;
bool continuous;
int quit;
- float xAngle;
- float yAngle;
+ double xAngle;
+ double yAngle;
float dist;
double lastMouseX;
double lastMouseY;
double lastDrawTime;
- unsigned framesDrawn;
bool mouseEntered;
+ bool verbose;
+ bool reversing;
} PuglTestApp;
+static const float backgroundVertices[] = {
+ -1.0f, 1.0f, -1.0f, // Top left
+ 1.0f, 1.0f, -1.0f, // Top right
+ -1.0f, -1.0f, -1.0f, // Bottom left
+ 1.0f, -1.0f, -1.0f, // Bottom right
+};
+
static PuglRect
getChildFrame(const PuglRect parentFrame)
{
@@ -65,72 +74,26 @@ getChildFrame(const PuglRect parentFrame)
}
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(app->world);
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);
- }
+ const double dTime = (thisTime - app->lastDrawTime) *
+ (app->reversing ? -1.0 : 1.0);
- 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.1f;
- glClearColor(bg, bg, bg, 1.0f);
- glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
-
- if (puglHasFocus(app->child)) {
- // Draw cube surfaces
- glEnableClientState(GL_VERTEX_ARRAY);
- glEnableClientState(GL_COLOR_ARRAY);
- glVertexPointer(3, GL_FLOAT, 0, cubeStripVertices);
- glColorPointer(3, GL_FLOAT, 0, cubeStripVertices);
- glDrawArrays(GL_TRIANGLE_STRIP, 0, 14);
- glDisableClientState(GL_COLOR_ARRAY);
- glDisableClientState(GL_VERTEX_ARRAY);
-
- glColor3f(0.0f, 0.0f, 0.0f);
- } else {
- glColor3f(1.0f, 1.0f, 1.0f);
+ app->xAngle = fmod(app->xAngle + dTime * 100.0, 360.0);
+ app->yAngle = fmod(app->yAngle + dTime * 100.0, 360.0);
}
- // Draw cube wireframe
- glEnableClientState(GL_VERTEX_ARRAY);
- glVertexPointer(3, GL_FLOAT, 0, cubeFrontLineLoop);
- glDrawArrays(GL_LINE_LOOP, 0, 4);
- glVertexPointer(3, GL_FLOAT, 0, cubeBackLineLoop);
- glDrawArrays(GL_LINE_LOOP, 0, 4);
- glVertexPointer(3, GL_FLOAT, 0, cubeSideLines);
- glDrawArrays(GL_LINES, 0, 8);
- glDisableClientState(GL_VERTEX_ARRAY);
+ displayCube(view,
+ app->dist,
+ (float)app->xAngle,
+ (float)app->yAngle,
+ app->mouseEntered);
app->lastDrawTime = thisTime;
- ++app->framesDrawn;
}
static void
@@ -142,8 +105,10 @@ swapFocus(PuglTestApp* app)
puglGrabFocus(app->parent);
}
- puglPostRedisplay(app->parent);
- puglPostRedisplay(app->child);
+ if (!app->continuous) {
+ puglPostRedisplay(app->parent);
+ puglPostRedisplay(app->child);
+ }
}
static void
@@ -199,16 +164,20 @@ onParentEvent(PuglView* view, const PuglEvent* event)
PuglTestApp* app = (PuglTestApp*)puglGetHandle(view);
const PuglRect parentFrame = puglGetFrame(view);
- printEvent(event, "Parent: ");
+ printEvent(event, "Parent: ", app->verbose);
switch (event->type) {
case PUGL_CONFIGURE:
- onReshape(view,
- (int)event->configure.width,
- (int)event->configure.height);
+ reshapeCube((float)event->configure.width,
+ (float)event->configure.height);
puglSetFrame(app->child, getChildFrame(parentFrame));
break;
+ case PUGL_UPDATE:
+ if (app->continuous) {
+ puglPostRedisplay(view);
+ }
+ break;
case PUGL_EXPOSE:
if (puglHasFocus(app->parent)) {
glMatrixMode(GL_MODELVIEW);
@@ -217,9 +186,9 @@ onParentEvent(PuglView* view, const PuglEvent* event)
glEnableClientState(GL_VERTEX_ARRAY);
glEnableClientState(GL_COLOR_ARRAY);
- glVertexPointer(3, GL_FLOAT, 0, cubeStripVertices);
- glColorPointer(3, GL_FLOAT, 0, cubeStripVertices);
- glDrawArrays(GL_TRIANGLE_STRIP, 0, 14);
+ glVertexPointer(3, GL_FLOAT, 0, backgroundVertices);
+ glColorPointer(3, GL_FLOAT, 0, backgroundVertices);
+ glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
glDisableClientState(GL_COLOR_ARRAY);
glDisableClientState(GL_VERTEX_ARRAY);
} else {
@@ -230,7 +199,7 @@ onParentEvent(PuglView* view, const PuglEvent* event)
case PUGL_KEY_PRESS:
onKeyPress(view, &event->key, "Parent: ");
break;
- case PUGL_MOTION_NOTIFY:
+ case PUGL_MOTION:
break;
case PUGL_CLOSE:
app->quit = 1;
@@ -247,11 +216,17 @@ onEvent(PuglView* view, const PuglEvent* event)
{
PuglTestApp* app = (PuglTestApp*)puglGetHandle(view);
- printEvent(event, "Child: ");
+ printEvent(event, "Child: ", app->verbose);
switch (event->type) {
case PUGL_CONFIGURE:
- onReshape(view, (int)event->configure.width, (int)event->configure.height);
+ reshapeCube((float)event->configure.width,
+ (float)event->configure.height);
+ break;
+ case PUGL_UPDATE:
+ if (app->continuous) {
+ puglPostRedisplay(view);
+ }
break;
case PUGL_EXPOSE:
onDisplay(view);
@@ -260,26 +235,33 @@ onEvent(PuglView* view, const PuglEvent* event)
app->quit = 1;
break;
case PUGL_KEY_PRESS:
- onKeyPress(view, &event->key, "Child: ");
+ onKeyPress(view, &event->key, "Child: ");
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);
+ case PUGL_MOTION:
+ app->xAngle -= event->motion.x - app->lastMouseX;
+ app->yAngle += event->motion.y - app->lastMouseY;
app->lastMouseX = event->motion.x;
app->lastMouseY = event->motion.y;
- puglPostRedisplay(view);
- puglPostRedisplay(app->parent);
+ if (!app->continuous) {
+ puglPostRedisplay(view);
+ puglPostRedisplay(app->parent);
+ }
break;
case PUGL_SCROLL:
app->dist = fmaxf(10.0f, app->dist + (float)event->scroll.dy);
- puglPostRedisplay(view);
+ if (!app->continuous) {
+ puglPostRedisplay(view);
+ }
break;
- case PUGL_ENTER_NOTIFY:
+ case PUGL_POINTER_IN:
app->mouseEntered = true;
break;
- case PUGL_LEAVE_NOTIFY:
+ case PUGL_POINTER_OUT:
app->mouseEntered = false;
break;
+ case PUGL_TIMER:
+ app->reversing = !app->reversing;
+ break;
default:
break;
}
@@ -291,6 +273,7 @@ int
main(int argc, char** argv)
{
PuglTestApp app = {0};
+
app.dist = 10;
const PuglTestOptions opts = puglParseTestOptions(&argc, &argv);
@@ -300,8 +283,9 @@ main(int argc, char** argv)
}
app.continuous = opts.continuous;
+ app.verbose = opts.verbose;
- app.world = puglNewWorld();
+ app.world = puglNewWorld(PUGL_PROGRAM, 0);
app.parent = puglNewView(app.world);
app.child = puglNewView(app.world);
@@ -313,54 +297,56 @@ main(int argc, char** argv)
puglSetAspectRatio(app.parent, 1, 1, 16, 9);
puglSetBackend(app.parent, puglGlBackend());
+ puglSetViewHint(app.parent, PUGL_USE_DEBUG_CONTEXT, opts.errorChecking);
puglSetViewHint(app.parent, PUGL_RESIZABLE, opts.resizable);
puglSetViewHint(app.parent, PUGL_SAMPLES, opts.samples);
puglSetViewHint(app.parent, PUGL_DOUBLE_BUFFER, opts.doubleBuffer);
- puglSetViewHint(app.parent, PUGL_SWAP_INTERVAL, opts.doubleBuffer);
+ puglSetViewHint(app.parent, PUGL_SWAP_INTERVAL, opts.sync);
puglSetViewHint(app.parent, PUGL_IGNORE_KEY_REPEAT, opts.ignoreKeyRepeat);
puglSetHandle(app.parent, &app);
puglSetEventFunc(app.parent, onParentEvent);
+ PuglStatus st = PUGL_SUCCESS;
const uint8_t title[] = { 'P', 'u', 'g', 'l', ' ',
'P', 'r', 0xC3, 0xBC, 'f', 'u', 'n', 'g', 0 };
- if (puglCreateWindow(app.parent, (const char*)title)) {
- fprintf(stderr, "error: Failed to create parent window\n");
- return 1;
+
+ puglSetWindowTitle(app.parent, (const char*)title);
+
+ if ((st = puglRealize(app.parent))) {
+ return logError("Failed to create parent window (%s)\n",
+ puglStrerror(st));
}
puglSetFrame(app.child, getChildFrame(parentFrame));
puglSetParentWindow(app.child, puglGetNativeWindow(app.parent));
+ puglSetViewHint(app.child, PUGL_USE_DEBUG_CONTEXT, opts.errorChecking);
puglSetViewHint(app.child, PUGL_SAMPLES, opts.samples);
puglSetViewHint(app.child, PUGL_DOUBLE_BUFFER, opts.doubleBuffer);
- puglSetViewHint(app.child, PUGL_SWAP_INTERVAL, 0);
+ puglSetViewHint(app.child, PUGL_SWAP_INTERVAL, opts.sync);
puglSetBackend(app.child, puglGlBackend());
puglSetViewHint(app.child, PUGL_IGNORE_KEY_REPEAT, opts.ignoreKeyRepeat);
puglSetHandle(app.child, &app);
puglSetEventFunc(app.child, onEvent);
- const int st = puglCreateWindow(app.child, NULL);
- if (st) {
- fprintf(stderr, "error: Failed to create child window (%d)\n", st);
- return 1;
+ if ((st = puglRealize(app.child))) {
+ return logError("Failed to create child window (%s)\n",
+ puglStrerror(st));
}
puglShowWindow(app.parent);
puglShowWindow(app.child);
+ puglStartTimer(app.child, reverseTimerId, 3.6);
+
PuglFpsPrinter fpsPrinter = { puglGetTime(app.world) };
+ unsigned framesDrawn = 0;
bool requestedAttention = false;
while (!app.quit) {
const double thisTime = puglGetTime(app.world);
- if (app.continuous) {
- puglPostRedisplay(app.parent);
- puglPostRedisplay(app.child);
- } else {
- puglPollEvents(app.world, -1);
- }
-
- puglDispatchEvents(app.world);
+ puglUpdate(app.world, app.continuous ? 0.0 : -1.0);
+ ++framesDrawn;
if (!requestedAttention && thisTime > 5.0) {
puglRequestAttention(app.parent);
@@ -368,7 +354,7 @@ main(int argc, char** argv)
}
if (app.continuous) {
- puglPrintFps(app.world, &fpsPrinter, &app.framesDrawn);
+ puglPrintFps(app.world, &fpsPrinter, &framesDrawn);
}
}
diff --git a/pugl/examples/pugl_gl3_demo.c b/pugl/examples/pugl_gl3_demo.c
new file mode 100644
index 0000000..c49ed3d
--- /dev/null
+++ b/pugl/examples/pugl_gl3_demo.c
@@ -0,0 +1,432 @@
+/*
+ Copyright 2012-2020 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_gl3_demo.c An example of drawing with OpenGL 3.
+
+ This is an example of using OpenGL for pixel-perfect 2D drawing. It uses
+ pixel coordinates for positions and sizes so that things work roughly like a
+ typical 2D graphics API.
+
+ The program draws a bunch of rectangles with borders, using instancing.
+ Each rectangle has origin, size, and fill color attributes, which are shared
+ for all four vertices. On each frame, a single buffer with all the
+ rectangle data is sent to the GPU, and everything is drawn with a single
+ draw call.
+
+ This is not particularly realistic or optimal, but serves as a decent rough
+ benchmark for how much simple geometry you can draw. The number of
+ rectangles can be given on the command line. For reference, it begins to
+ struggle to maintain 60 FPS on my machine (1950x + Vega64) with more than
+ about 100000 rectangles.
+*/
+
+#include "demo_utils.h"
+#include "shader_utils.h"
+#include "test/test_utils.h"
+
+#include "glad/glad.h"
+
+#include "pugl/gl.h"
+#include "pugl/pugl.h"
+#include "pugl/pugl_gl.h"
+
+#include <math.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+static const int defaultWidth = 512;
+static const int defaultHeight = 512;
+
+typedef struct
+{
+ float pos[2];
+ float size[2];
+ float fillColor[4];
+} Rect;
+
+// clang-format off
+static const GLfloat rectVertices[] = {
+ 0.0f, 0.0f, // TL
+ 1.0f, 0.0f, // TR
+ 0.0f, 1.0f, // BL
+ 1.0f, 1.0f, // BR
+};
+// clang-format on
+
+static const GLuint rectIndices[4] = {0, 1, 2, 3};
+
+typedef struct
+{
+ PuglTestOptions opts;
+ PuglWorld* world;
+ PuglView* view;
+ size_t numRects;
+ Rect* rects;
+ Program drawRect;
+ GLuint vao;
+ GLuint vbo;
+ GLuint instanceVbo;
+ GLuint ibo;
+ GLint u_projection;
+ unsigned framesDrawn;
+ int quit;
+} PuglTestApp;
+
+static PuglStatus
+setupGl(PuglTestApp* app);
+
+static void
+teardownGl(PuglTestApp* app);
+
+static void
+onConfigure(PuglView* view, double width, double height)
+{
+ (void)view;
+
+ glEnable(GL_BLEND);
+ glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
+ glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
+ glViewport(0, 0, (int)width, (int)height);
+}
+
+static void
+onExpose(PuglView* view)
+{
+ PuglTestApp* app = (PuglTestApp*)puglGetHandle(view);
+ const PuglRect frame = puglGetFrame(view);
+ const float width = (float)frame.width;
+ const float height = (float)frame.height;
+ const double time = puglGetTime(puglGetWorld(view));
+
+ // Construct projection matrix for 2D window surface (in pixels)
+ mat4 proj;
+ mat4Ortho(proj,
+ 0.0f,
+ (float)frame.width,
+ 0.0f,
+ (float)frame.height,
+ -1.0f,
+ 1.0f);
+
+ // Clear and bind everything that is the same for every rect
+ glClear(GL_COLOR_BUFFER_BIT);
+ glUseProgram(app->drawRect.program);
+ glBindVertexArray(app->vao);
+
+ // Set projection matrix uniform
+ glUniformMatrix4fv(app->u_projection, 1, GL_FALSE, (const GLfloat*)&proj);
+
+ for (size_t i = 0; i < app->numRects; ++i) {
+ Rect* rect = &app->rects[i];
+ const float normal = i / (float)app->numRects;
+ const float offset[2] = {normal * 128.0f, normal * 128.0f};
+
+ // Move rect around in an arbitrary way that looks cool
+ rect->pos[0] = (width - rect->size[0] + offset[0]) *
+ (sinf((float)time * rect->size[0] / 64.0f + normal) +
+ 1.0f) /
+ 2.0f;
+ rect->pos[1] = (height - rect->size[1] + offset[1]) *
+ (cosf((float)time * rect->size[1] / 64.0f + normal) +
+ 1.0f) /
+ 2.0f;
+ }
+
+ glBufferSubData(GL_ARRAY_BUFFER,
+ 0,
+ (GLsizeiptr)(app->numRects * sizeof(Rect)),
+ app->rects);
+
+ glDrawElementsInstanced(GL_TRIANGLE_STRIP,
+ 4,
+ GL_UNSIGNED_INT,
+ NULL,
+ (GLsizei)(app->numRects * 4));
+
+ ++app->framesDrawn;
+}
+
+static PuglStatus
+onEvent(PuglView* view, const PuglEvent* event)
+{
+ PuglTestApp* app = (PuglTestApp*)puglGetHandle(view);
+
+ printEvent(event, "Event: ", app->opts.verbose);
+
+ switch (event->type) {
+ case PUGL_CREATE:
+ setupGl(app);
+ break;
+ case PUGL_DESTROY:
+ teardownGl(app);
+ break;
+ case PUGL_CONFIGURE:
+ onConfigure(view, event->configure.width, event->configure.height);
+ break;
+ case PUGL_UPDATE:
+ puglPostRedisplay(view);
+ break;
+ case PUGL_EXPOSE: onExpose(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;
+ default: break;
+ }
+
+ return PUGL_SUCCESS;
+}
+
+static Rect*
+makeRects(const size_t numRects)
+{
+ const float minSize = (float)defaultWidth / 64.0f;
+ const float maxSize = (float)defaultWidth / 6.0f;
+ const float boxAlpha = 0.2f;
+
+ Rect* rects = (Rect*)calloc(numRects, sizeof(Rect));
+ for (size_t i = 0; i < numRects; ++i) {
+ const float s = (sinf((float)i) / 2.0f + 0.5f);
+ const float c = (cosf((float)i) / 2.0f + 0.5f);
+
+ rects[i].size[0] = minSize + s * maxSize;
+ rects[i].size[1] = minSize + c * maxSize;
+ rects[i].fillColor[1] = s / 2.0f + 0.25f;
+ rects[i].fillColor[2] = c / 2.0f + 0.25f;
+ rects[i].fillColor[3] = boxAlpha;
+ }
+
+ return rects;
+}
+
+static char*
+loadShader(const char* const path)
+{
+ FILE* const file = fopen(path, "r");
+ if (!file) {
+ logError("Failed to open '%s'\n", path);
+ return NULL;
+ }
+
+ fseek(file, 0, SEEK_END);
+ const size_t fileSize = (size_t)ftell(file);
+
+ fseek(file, 0, SEEK_SET);
+ char* source = (char*)calloc(1, fileSize + 1u);
+
+ fread(source, 1, fileSize, file);
+ fclose(file);
+
+ return source;
+}
+
+static int
+parseOptions(PuglTestApp* app, int argc, char** argv)
+{
+ // Parse command line options
+ app->numRects = 1024;
+ app->opts = puglParseTestOptions(&argc, &argv);
+ if (app->opts.help) {
+ return 1;
+ }
+
+ // Parse number of rectangles argument, if given
+ if (argc == 1) {
+ char* endptr = NULL;
+
+ app->numRects = (size_t)strtol(argv[0], &endptr, 10);
+ if (endptr != argv[0] + strlen(argv[0])) {
+ return 1;
+ }
+ }
+
+ return 0;
+}
+
+static void
+setupPugl(PuglTestApp* app, const PuglRect frame)
+{
+ // Create world, view, and rect data
+ app->world = puglNewWorld(PUGL_PROGRAM, 0);
+ app->view = puglNewView(app->world);
+ app->rects = makeRects(app->numRects);
+
+ // Set up world and view
+ puglSetClassName(app->world, "PuglGL3Demo");
+ puglSetWindowTitle(app->view, "Pugl OpenGL 3");
+ puglSetFrame(app->view, frame);
+ puglSetMinSize(app->view, defaultWidth / 4, defaultHeight / 4);
+ puglSetAspectRatio(app->view, 1, 1, 16, 9);
+ puglSetBackend(app->view, puglGlBackend());
+ puglSetViewHint(app->view, PUGL_USE_COMPAT_PROFILE, PUGL_FALSE);
+ puglSetViewHint(app->view, PUGL_USE_DEBUG_CONTEXT, app->opts.errorChecking);
+ puglSetViewHint(app->view, PUGL_CONTEXT_VERSION_MAJOR, 3);
+ puglSetViewHint(app->view, PUGL_CONTEXT_VERSION_MINOR, 3);
+ puglSetViewHint(app->view, PUGL_RESIZABLE, app->opts.resizable);
+ puglSetViewHint(app->view, PUGL_SAMPLES, app->opts.samples);
+ puglSetViewHint(app->view, PUGL_DOUBLE_BUFFER, app->opts.doubleBuffer);
+ puglSetViewHint(app->view, PUGL_SWAP_INTERVAL, app->opts.sync);
+ puglSetViewHint(app->view, PUGL_IGNORE_KEY_REPEAT, PUGL_TRUE);
+ puglSetHandle(app->view, app);
+ puglSetEventFunc(app->view, onEvent);
+}
+
+static PuglStatus
+setupGl(PuglTestApp* app)
+{
+ // Load GL functions via GLAD
+ if (!gladLoadGLLoader((GLADloadproc)&puglGetProcAddress)) {
+ logError("Failed to load GL\n");
+ return PUGL_FAILURE;
+ }
+
+ // Load shader sources
+ char* const vertexSource = loadShader("shaders/rect.vert");
+ char* const fragmentSource = loadShader("shaders/rect.frag");
+ if (!vertexSource || !fragmentSource) {
+ logError("Failed to load shader sources\n");
+ return PUGL_FAILURE;
+ }
+
+ // Compile rectangle shaders and program
+ app->drawRect = compileProgram(vertexSource, fragmentSource);
+ free(fragmentSource);
+ free(vertexSource);
+ if (!app->drawRect.program) {
+ return PUGL_FAILURE;
+ }
+
+ // Get location of rectangle shader uniforms
+ app->u_projection =
+ glGetUniformLocation(app->drawRect.program, "u_projection");
+
+ // Generate/bind a VAO to track state
+ glGenVertexArrays(1, &app->vao);
+ glBindVertexArray(app->vao);
+
+ // Generate/bind a VBO to store vertex position data
+ glGenBuffers(1, &app->vbo);
+ glBindBuffer(GL_ARRAY_BUFFER, app->vbo);
+ glBufferData(GL_ARRAY_BUFFER,
+ sizeof(rectVertices),
+ rectVertices,
+ GL_STATIC_DRAW);
+
+ // Attribute 0 is position, 2 floats from the VBO
+ glEnableVertexAttribArray(0);
+ glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 2 * sizeof(GLfloat), NULL);
+
+ // Generate/bind a VBO to store instance attribute data
+ glGenBuffers(1, &app->instanceVbo);
+ glBindBuffer(GL_ARRAY_BUFFER, app->instanceVbo);
+ glBufferData(GL_ARRAY_BUFFER,
+ (GLsizeiptr)(app->numRects * sizeof(Rect)),
+ app->rects,
+ GL_STREAM_DRAW);
+
+ // Attribute 1 is Rect::position
+ glEnableVertexAttribArray(1);
+ glVertexAttribDivisor(1, 4);
+ glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, sizeof(Rect), NULL);
+
+ // Attribute 2 is Rect::size
+ glEnableVertexAttribArray(2);
+ glVertexAttribDivisor(2, 4);
+ glVertexAttribPointer(2,
+ 2,
+ GL_FLOAT,
+ GL_FALSE,
+ sizeof(Rect),
+ (const void*)offsetof(Rect, size));
+
+ // Attribute 3 is Rect::fillColor
+ glEnableVertexAttribArray(3);
+ glVertexAttribDivisor(3, 4);
+ glVertexAttribPointer(3,
+ 4,
+ GL_FLOAT,
+ GL_FALSE,
+ sizeof(Rect),
+ (const void*)offsetof(Rect, fillColor));
+
+ // Set up the IBO to index into the VBO
+ glGenBuffers(1, &app->ibo);
+ glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, app->ibo);
+ glBufferData(GL_ELEMENT_ARRAY_BUFFER,
+ sizeof(rectIndices),
+ rectIndices,
+ GL_STATIC_DRAW);
+
+ return PUGL_SUCCESS;
+}
+
+static void
+teardownGl(PuglTestApp* app)
+{
+ glDeleteBuffers(1, &app->ibo);
+ glDeleteBuffers(1, &app->vbo);
+ glDeleteBuffers(1, &app->instanceVbo);
+ glDeleteVertexArrays(1, &app->vao);
+ deleteProgram(app->drawRect);
+}
+
+int
+main(int argc, char** argv)
+{
+ PuglTestApp app;
+ memset(&app, 0, sizeof(app));
+
+ const PuglRect frame = {0, 0, defaultWidth, defaultHeight};
+
+ // Parse command line options
+ if (parseOptions(&app, argc, argv)) {
+ puglPrintTestUsage("pugl_gl3_demo", "[NUM_RECTS]");
+ return 1;
+ }
+
+ // Create and configure world and view
+ setupPugl(&app, frame);
+
+ // Create window (which will send a PUGL_CREATE event)
+ const PuglStatus st = puglRealize(app.view);
+ if (st) {
+ return logError("Failed to create window (%s)\n", puglStrerror(st));
+ }
+
+ // Show window
+ puglShowWindow(app.view);
+
+ // Grind away, drawing continuously
+ PuglFpsPrinter fpsPrinter = {puglGetTime(app.world)};
+ while (!app.quit) {
+ puglUpdate(app.world, 0.0);
+ puglPrintFps(app.world, &fpsPrinter, &app.framesDrawn);
+ }
+
+ // Destroy window (which will send a PUGL_DESTROY event)
+ puglFreeView(app.view);
+
+ // Free everything else
+ puglFreeWorld(app.world);
+ free(app.rects);
+
+ return 0;
+}
diff --git a/pugl/examples/pugl_print_events.c b/pugl/examples/pugl_print_events.c
new file mode 100644
index 0000000..52b58c4
--- /dev/null
+++ b/pugl/examples/pugl_print_events.c
@@ -0,0 +1,79 @@
+/*
+ Copyright 2012-2020 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_print_events.c A utility program that prints view events.
+*/
+
+#include "test/test_utils.h"
+
+#include "pugl/pugl.h"
+#include "pugl/pugl_stub.h"
+
+#include <stdbool.h>
+#include <stdio.h>
+
+typedef struct
+{
+ PuglWorld* world;
+ PuglView* view;
+ int quit;
+} PuglPrintEventsApp;
+
+static PuglStatus
+onEvent(PuglView* view, const PuglEvent* event)
+{
+ PuglPrintEventsApp* app = (PuglPrintEventsApp*)puglGetHandle(view);
+
+ printEvent(event, "Event: ", true);
+
+ switch (event->type) {
+ case PUGL_CLOSE: app->quit = 1; break;
+ default: break;
+ }
+
+ return PUGL_SUCCESS;
+}
+
+int
+main(void)
+{
+ PuglPrintEventsApp app = {NULL, NULL, 0};
+
+ app.world = puglNewWorld(PUGL_PROGRAM, 0);
+ app.view = puglNewView(app.world);
+
+ puglSetClassName(app.world, "Pugl Print Events");
+ puglSetWindowTitle(app.view, "Pugl Event Printer");
+ puglSetBackend(app.view, puglStubBackend());
+ puglSetHandle(app.view, &app);
+ puglSetEventFunc(app.view, onEvent);
+
+ if (puglRealize(app.view)) {
+ return logError("Failed to create window\n");
+ }
+
+ puglShowWindow(app.view);
+
+ while (!app.quit) {
+ puglUpdate(app.world, -1.0);
+ }
+
+ puglFreeView(app.view);
+ puglFreeWorld(app.world);
+
+ return 0;
+}
diff --git a/pugl/examples/pugl_window_demo.c b/pugl/examples/pugl_window_demo.c
new file mode 100644
index 0000000..183119c
--- /dev/null
+++ b/pugl/examples/pugl_window_demo.c
@@ -0,0 +1,252 @@
+/*
+ Copyright 2012-2020 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_window_demo.c A demonstration of multiple Pugl windows.
+*/
+
+#include "cube_view.h"
+#include "demo_utils.h"
+#include "test/test_utils.h"
+
+#include "pugl/pugl.h"
+#include "pugl/pugl_gl.h"
+
+#include <math.h>
+#include <stdbool.h>
+#include <string.h>
+
+typedef struct {
+ PuglView* view;
+ double xAngle;
+ double yAngle;
+ float dist;
+ double lastMouseX;
+ double lastMouseY;
+ double lastDrawTime;
+ bool entered;
+} CubeView;
+
+typedef struct {
+ PuglWorld* world;
+ CubeView cubes[2];
+ bool continuous;
+ int quit;
+ bool verbose;
+} PuglTestApp;
+
+static void
+onDisplay(PuglView* view)
+{
+ PuglWorld* world = puglGetWorld(view);
+ PuglTestApp* app = (PuglTestApp*)puglGetWorldHandle(world);
+ CubeView* cube = (CubeView*)puglGetHandle(view);
+
+ const double thisTime = puglGetTime(app->world);
+ if (app->continuous) {
+ const double dTime = thisTime - cube->lastDrawTime;
+
+ cube->xAngle = fmod(cube->xAngle + dTime * 100.0, 360.0);
+ cube->yAngle = fmod(cube->yAngle + dTime * 100.0, 360.0);
+ }
+
+ displayCube(view,
+ cube->dist,
+ (float)cube->xAngle,
+ (float)cube->yAngle,
+ cube->entered);
+
+ cube->lastDrawTime = thisTime;
+}
+
+static void
+onKeyPress(PuglView* view, const PuglEventKey* event)
+{
+ PuglWorld* world = puglGetWorld(view);
+ PuglTestApp* app = (PuglTestApp*)puglGetWorldHandle(world);
+ PuglRect frame = puglGetFrame(view);
+
+ if (event->key == 'q' || event->key == PUGL_KEY_ESCAPE) {
+ app->quit = 1;
+ } else if (event->state & PUGL_MOD_SHIFT) {
+ if (event->key == PUGL_KEY_UP) {
+ frame.height += 10;
+ } else if (event->key == PUGL_KEY_DOWN) {
+ frame.height -= 10;
+ } else if (event->key == PUGL_KEY_LEFT) {
+ frame.width -= 10;
+ } else if (event->key == PUGL_KEY_RIGHT) {
+ frame.width += 10;
+ } else {
+ return;
+ }
+ puglSetFrame(view, frame);
+ } else {
+ if (event->key == PUGL_KEY_UP) {
+ frame.y -= 10;
+ } else if (event->key == PUGL_KEY_DOWN) {
+ frame.y += 10;
+ } else if (event->key == PUGL_KEY_LEFT) {
+ frame.x -= 10;
+ } else if (event->key == PUGL_KEY_RIGHT) {
+ frame.x += 10;
+ } else {
+ return;
+ }
+ puglSetFrame(view, frame);
+ }
+}
+
+static void
+redisplayView(PuglTestApp* app, PuglView* view)
+{
+ if (!app->continuous) {
+ puglPostRedisplay(view);
+ }
+}
+
+static PuglStatus
+onEvent(PuglView* view, const PuglEvent* event)
+{
+ PuglWorld* world = puglGetWorld(view);
+ PuglTestApp* app = (PuglTestApp*)puglGetWorldHandle(world);
+ CubeView* cube = (CubeView*)puglGetHandle(view);
+
+ const char* const prefix = cube == &app->cubes[0] ? "View 1: " : "View 2: ";
+ printEvent(event, prefix, app->verbose);
+
+ switch (event->type) {
+ case PUGL_CONFIGURE:
+ reshapeCube((float)event->configure.width,
+ (float)event->configure.height);
+ break;
+ case PUGL_UPDATE:
+ if (app->continuous) {
+ puglPostRedisplay(view);
+ }
+ break;
+ case PUGL_EXPOSE:
+ onDisplay(view);
+ break;
+ case PUGL_CLOSE:
+ app->quit = 1;
+ break;
+ case PUGL_KEY_PRESS:
+ onKeyPress(view, &event->key);
+ break;
+ case PUGL_MOTION:
+ cube->xAngle -= event->motion.x - cube->lastMouseX;
+ cube->yAngle += event->motion.y - cube->lastMouseY;
+ cube->lastMouseX = event->motion.x;
+ cube->lastMouseY = event->motion.y;
+ redisplayView(app, view);
+ break;
+ case PUGL_SCROLL:
+ cube->dist = fmaxf(10.0f, cube->dist + (float)event->scroll.dy);
+ redisplayView(app, view);
+ break;
+ case PUGL_POINTER_IN:
+ cube->entered = true;
+ redisplayView(app, view);
+ break;
+ case PUGL_POINTER_OUT:
+ cube->entered = false;
+ redisplayView(app, view);
+ break;
+ case PUGL_FOCUS_IN:
+ case PUGL_FOCUS_OUT:
+ redisplayView(app, view);
+ break;
+ default:
+ break;
+ }
+
+ return PUGL_SUCCESS;
+}
+
+int
+main(int argc, char** argv)
+{
+ PuglTestApp app = {0};
+
+ const PuglTestOptions opts = puglParseTestOptions(&argc, &argv);
+ if (opts.help) {
+ puglPrintTestUsage(argv[0], "");
+ return 1;
+ }
+
+ app.continuous = opts.continuous;
+ app.verbose = opts.verbose;
+
+ app.world = puglNewWorld(PUGL_PROGRAM, 0);
+ app.cubes[0].view = puglNewView(app.world);
+ app.cubes[1].view = puglNewView(app.world);
+
+ puglSetWorldHandle(app.world, &app);
+ puglSetClassName(app.world, "Pugl Test");
+
+ PuglStatus st = PUGL_SUCCESS;
+ for (size_t i = 0; i < 2; ++i) {
+ CubeView* cube = &app.cubes[i];
+ PuglView* view = cube->view;
+ static const double pad = 64.0;
+ const PuglRect frame = {pad + (256.0 + pad) * i,
+ pad + (256.0 + pad) * i,
+ 256.0,
+ 256.0};
+
+ cube->dist = 10;
+
+ puglSetWindowTitle(view, "Pugl Window Demo");
+ puglSetFrame(view, frame);
+ puglSetMinSize(view, 128, 128);
+ puglSetBackend(view, puglGlBackend());
+
+ puglSetViewHint(view, PUGL_USE_DEBUG_CONTEXT, opts.errorChecking);
+ puglSetViewHint(view, PUGL_RESIZABLE, opts.resizable);
+ puglSetViewHint(view, PUGL_SAMPLES, opts.samples);
+ puglSetViewHint(view, PUGL_DOUBLE_BUFFER, opts.doubleBuffer);
+ puglSetViewHint(view, PUGL_SWAP_INTERVAL, opts.sync);
+ puglSetViewHint(view, PUGL_IGNORE_KEY_REPEAT, opts.ignoreKeyRepeat);
+ puglSetHandle(view, cube);
+ puglSetEventFunc(view, onEvent);
+
+ if ((st = puglRealize(view))) {
+ return logError("Failed to create window (%s)\n", puglStrerror(st));
+ }
+
+ puglShowWindow(view);
+ }
+
+ PuglFpsPrinter fpsPrinter = {puglGetTime(app.world)};
+ unsigned framesDrawn = 0;
+ while (!app.quit) {
+ puglUpdate(app.world, app.continuous ? 0.0 : -1.0);
+ ++framesDrawn;
+
+ if (app.continuous) {
+ puglPrintFps(app.world, &fpsPrinter, &framesDrawn);
+ }
+ }
+
+ for (size_t i = 0; i < 2; ++i) {
+ puglFreeView(app.cubes[i].view);
+ }
+
+ puglFreeWorld(app.world);
+
+ return 0;
+}
diff --git a/pugl/test/shader_utils.h b/pugl/examples/shader_utils.h
index 834d8fc..834d8fc 100644
--- a/pugl/test/shader_utils.h
+++ b/pugl/examples/shader_utils.h
diff --git a/pugl/pugl/detail/implementation.c b/pugl/pugl/detail/implementation.c
index 80a7a32..ee9b242 100644
--- a/pugl/pugl/detail/implementation.c
+++ b/pugl/pugl/detail/implementation.c
@@ -1,5 +1,5 @@
/*
- Copyright 2012-2019 David Robillard <http://drobilla.net>
+ Copyright 2012-2020 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
@@ -21,17 +21,67 @@
#include "pugl/detail/implementation.h"
#include "pugl/pugl.h"
+#include <assert.h>
#include <stdbool.h>
+#include <stdio.h>
#include <stdlib.h>
#include <string.h>
+static const char*
+puglLogLevelPrefix(const PuglLogLevel level)
+{
+ switch (level) {
+ case PUGL_LOG_LEVEL_ERR:
+ return "error: ";
+ case PUGL_LOG_LEVEL_WARNING:
+ return "warning: ";
+ case PUGL_LOG_LEVEL_INFO:
+ case PUGL_LOG_LEVEL_DEBUG:
+ return "";
+ }
+
+ return "";
+}
+
+static void
+puglDefaultLogFunc(PuglWorld* PUGL_UNUSED(world),
+ PuglLogLevel level,
+ const char* msg)
+{
+ fprintf(stderr, "%s%s", puglLogLevelPrefix(level), msg);
+}
+
+const char*
+puglStrerror(const PuglStatus status)
+{
+ // clang-format off
+ switch (status) {
+ case PUGL_SUCCESS: return "Success";
+ case PUGL_FAILURE: return "Non-fatal failure";
+ case PUGL_UNKNOWN_ERROR: return "Unknown system error";
+ case PUGL_BAD_BACKEND: return "Invalid or missing backend";
+ case PUGL_BAD_PARAMETER: return "Invalid parameter";
+ case PUGL_BACKEND_FAILED: return "Backend initialisation failed";
+ case PUGL_REGISTRATION_FAILED: return "Class registration failed";
+ case PUGL_REALIZE_FAILED: return "View creation failed";
+ case PUGL_SET_FORMAT_FAILED: return "Failed to set pixel format";
+ case PUGL_CREATE_CONTEXT_FAILED: return "Failed to create drawing context";
+ case PUGL_UNSUPPORTED_TYPE: return "Unsupported data type";
+ }
+ // clang-format on
+
+ return "Unknown error";
+}
+
void
puglSetString(char** dest, const char* string)
{
- const size_t len = strlen(string);
+ if (*dest != string) {
+ const size_t len = strlen(string);
- *dest = (char*)realloc(*dest, len + 1);
- strncpy(*dest, string, len + 1);
+ *dest = (char*)realloc(*dest, len + 1);
+ strncpy(*dest, string, len + 1);
+ }
}
void
@@ -61,22 +111,25 @@ puglSetDefaultHints(PuglHints hints)
hints[PUGL_DEPTH_BITS] = 24;
hints[PUGL_STENCIL_BITS] = 8;
hints[PUGL_SAMPLES] = 0;
- hints[PUGL_DOUBLE_BUFFER] = PUGL_FALSE;
- hints[PUGL_SWAP_INTERVAL] = 0;
+ hints[PUGL_DOUBLE_BUFFER] = PUGL_TRUE;
+ hints[PUGL_SWAP_INTERVAL] = PUGL_DONT_CARE;
hints[PUGL_RESIZABLE] = PUGL_FALSE;
hints[PUGL_IGNORE_KEY_REPEAT] = PUGL_FALSE;
}
PuglWorld*
-puglNewWorld(void)
+puglNewWorld(PuglWorldType type, PuglWorldFlags flags)
{
PuglWorld* world = (PuglWorld*)calloc(1, sizeof(PuglWorld));
- if (!world || !(world->impl = puglInitWorldInternals())) {
+ if (!world || !(world->impl = puglInitWorldInternals(type, flags))) {
free(world);
return NULL;
}
world->startTime = puglGetTime(world);
+ world->logFunc = puglDefaultLogFunc;
+ world->logLevel = PUGL_LOG_LEVEL_INFO;
+
puglSetString(&world->className, "Pugl");
return world;
@@ -91,6 +144,34 @@ puglFreeWorld(PuglWorld* const world)
free(world);
}
+void
+puglSetWorldHandle(PuglWorld* world, PuglWorldHandle handle)
+{
+ world->handle = handle;
+}
+
+PuglWorldHandle
+puglGetWorldHandle(PuglWorld* world)
+{
+ return world->handle;
+}
+
+PuglStatus
+puglSetLogFunc(PuglWorld* world, PuglLogFunc logFunc)
+{
+ world->logFunc = logFunc;
+
+ return PUGL_SUCCESS;
+}
+
+PuglStatus
+puglSetLogLevel(PuglWorld* world, PuglLogLevel level)
+{
+ world->logLevel = level;
+
+ return PUGL_SUCCESS;
+}
+
PuglStatus
puglSetClassName(PuglWorld* const world, const char* const name)
{
@@ -117,6 +198,7 @@ puglNewView(PuglWorld* const world)
++world->numViews;
world->views = (PuglView**)realloc(world->views,
world->numViews * sizeof(PuglView*));
+
world->views[world->numViews - 1] = view;
return view;
@@ -125,6 +207,8 @@ puglNewView(PuglWorld* const world)
void
puglFreeView(PuglView* view)
{
+ puglDispatchSimpleEvent(view, PUGL_DESTROY);
+
// Remove from world view list
PuglWorld* world = view->world;
for (size_t i = 0; i < world->numViews; ++i) {
@@ -155,15 +239,16 @@ puglGetWorld(PuglView* view)
PuglStatus
puglSetViewHint(PuglView* view, PuglViewHint hint, int value)
{
- if (hint < PUGL_NUM_WINDOW_HINTS) {
+ if (hint < PUGL_NUM_VIEW_HINTS) {
view->hints[hint] = value;
+ return PUGL_SUCCESS;
}
- return PUGL_SUCCESS;
+ return PUGL_BAD_PARAMETER;
}
PuglStatus
-puglSetParentWindow(PuglView* view, PuglNativeWindow parent)
+puglSetParentWindow(PuglView* view, PuglNativeView parent)
{
view->parent = parent;
return PUGL_SUCCESS;
@@ -189,7 +274,7 @@ puglGetHandle(PuglView* view)
}
bool
-puglGetVisible(PuglView* view)
+puglGetVisible(const PuglView* view)
{
return view->visible;
}
@@ -206,20 +291,44 @@ puglGetContext(PuglView* view)
return view->backend->getContext(view);
}
+#ifndef PUGL_DISABLE_DEPRECATED
+
+PuglStatus
+puglPollEvents(PuglWorld* world, double timeout)
+{
+ return puglUpdate(world, timeout);
+}
+
+PuglStatus
+puglDispatchEvents(PuglWorld* world)
+{
+ return puglUpdate(world, 0.0);
+}
+
PuglStatus
puglEnterContext(PuglView* view, bool drawing)
{
- view->backend->enter(view, drawing);
+ const PuglEventExpose expose = {
+ PUGL_EXPOSE, 0, 0, 0, view->frame.width, view->frame.height, 0};
+
+ view->backend->enter(view, drawing ? &expose : NULL);
+
return PUGL_SUCCESS;
}
PuglStatus
puglLeaveContext(PuglView* view, bool drawing)
{
- view->backend->leave(view, drawing);
+ const PuglEventExpose expose = {
+ PUGL_EXPOSE, 0, 0, 0, view->frame.width, view->frame.height, 0};
+
+ view->backend->leave(view, drawing ? &expose : NULL);
+
return PUGL_SUCCESS;
}
+#endif
+
PuglStatus
puglSetEventFunc(PuglView* view, PuglEventFunc eventFunc)
{
@@ -240,45 +349,87 @@ puglDecodeUTF8(const uint8_t* buf)
} else if (buf[0] < 0xC2) {
return 0xFFFD;
} else if (buf[0] < 0xE0) {
- FAIL_IF((buf[1] & 0xC0) != 0x80);
- return (buf[0] << 6) + buf[1] - 0x3080u;
+ FAIL_IF((buf[1] & 0xC0u) != 0x80);
+ return ((uint32_t)buf[0] << 6u) + buf[1] - 0x3080u;
} else if (buf[0] < 0xF0) {
- FAIL_IF((buf[1] & 0xC0) != 0x80);
+ FAIL_IF((buf[1] & 0xC0u) != 0x80);
FAIL_IF(buf[0] == 0xE0 && buf[1] < 0xA0);
- FAIL_IF((buf[2] & 0xC0) != 0x80);
- return (buf[0] << 12) + (buf[1] << 6) + buf[2] - 0xE2080u;
+ FAIL_IF((buf[2] & 0xC0u) != 0x80);
+ return ((uint32_t)buf[0] << 12u) + //
+ ((uint32_t)buf[1] << 6u) + //
+ ((uint32_t)buf[2] - 0xE2080u);
} else if (buf[0] < 0xF5) {
- FAIL_IF((buf[1] & 0xC0) != 0x80);
+ FAIL_IF((buf[1] & 0xC0u) != 0x80);
FAIL_IF(buf[0] == 0xF0 && buf[1] < 0x90);
FAIL_IF(buf[0] == 0xF4 && buf[1] >= 0x90);
- FAIL_IF((buf[2] & 0xC0) != 0x80);
- FAIL_IF((buf[3] & 0xC0) != 0x80);
- return ((buf[0] << 18) +
- (buf[1] << 12) +
- (buf[2] << 6) +
- buf[3] - 0x3C82080u);
+ FAIL_IF((buf[2] & 0xC0u) != 0x80u);
+ FAIL_IF((buf[3] & 0xC0u) != 0x80u);
+ return (((uint32_t)buf[0] << 18u) + //
+ ((uint32_t)buf[1] << 12u) + //
+ ((uint32_t)buf[2] << 6u) + //
+ ((uint32_t)buf[3] - 0x3C82080u));
}
return 0xFFFD;
}
+static inline bool
+puglMustConfigure(PuglView* view, const PuglEventConfigure* configure)
+{
+ return memcmp(configure, &view->lastConfigure, sizeof(PuglEventConfigure));
+}
+
+void
+puglDispatchSimpleEvent(PuglView* view, const PuglEventType type)
+{
+ assert(type == PUGL_CREATE || type == PUGL_DESTROY || type == PUGL_MAP ||
+ type == PUGL_UNMAP || type == PUGL_UPDATE || type == PUGL_CLOSE);
+
+ const PuglEvent event = {{type, 0}};
+ puglDispatchEvent(view, &event);
+}
+
+void
+puglDispatchEventInContext(PuglView* view, const PuglEvent* event)
+{
+ if (event->type == PUGL_CONFIGURE) {
+ view->frame.x = event->configure.x;
+ view->frame.y = event->configure.y;
+ view->frame.width = event->configure.width;
+ view->frame.height = event->configure.height;
+
+ if (puglMustConfigure(view, &event->configure)) {
+ view->eventFunc(view, event);
+ view->lastConfigure = event->configure;
+ }
+ } else {
+ view->eventFunc(view, event);
+ }
+}
+
void
puglDispatchEvent(PuglView* view, const PuglEvent* event)
{
switch (event->type) {
case PUGL_NOTHING:
break;
- case PUGL_CONFIGURE:
- puglEnterContext(view, false);
+ case PUGL_CREATE:
+ case PUGL_DESTROY:
+ view->backend->enter(view, NULL);
view->eventFunc(view, event);
- puglLeaveContext(view, false);
+ view->backend->leave(view, NULL);
break;
- case PUGL_EXPOSE:
- if (event->expose.count == 0) {
- puglEnterContext(view, true);
- view->eventFunc(view, event);
- puglLeaveContext(view, true);
+ case PUGL_CONFIGURE:
+ if (puglMustConfigure(view, &event->configure)) {
+ view->backend->enter(view, NULL);
+ puglDispatchEventInContext(view, event);
+ view->backend->leave(view, NULL);
}
break;
+ case PUGL_EXPOSE:
+ view->backend->enter(view, &event->expose);
+ view->eventFunc(view, event);
+ view->backend->leave(view, &event->expose);
+ break;
default:
view->eventFunc(view, event);
}
diff --git a/pugl/pugl/detail/implementation.h b/pugl/pugl/detail/implementation.h
index 874e7e1..bcecd85 100644
--- a/pugl/pugl/detail/implementation.h
+++ b/pugl/pugl/detail/implementation.h
@@ -1,5 +1,5 @@
/*
- Copyright 2012-2019 David Robillard <http://drobilla.net>
+ Copyright 2012-2020 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
@@ -27,18 +27,17 @@
#include <stddef.h>
#include <stdint.h>
-#ifdef __cplusplus
-extern "C" {
-#endif
+PUGL_BEGIN_DECLS
/** Set `blob` to `data` with length `len`, reallocating if necessary. */
-void puglSetBlob(PuglBlob* blob, const void* data, size_t len);
+void puglSetBlob(PuglBlob* dest, const void* data, size_t len);
/** Reallocate and set `*dest` to `string`. */
void puglSetString(char** dest, const char* string);
/** Allocate and initialise world internals (implemented once per platform) */
-PuglWorldInternals* puglInitWorldInternals(void);
+PuglWorldInternals*
+puglInitWorldInternals(PuglWorldType type, PuglWorldFlags flags);
/** Destroy and free world internals (implemented once per platform) */
void puglFreeWorldInternals(PuglWorld* world);
@@ -52,7 +51,13 @@ void puglFreeViewInternals(PuglView* view);
/** 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. */
+/** Dispatch an event with a simple `type` to `view`. */
+void puglDispatchSimpleEvent(PuglView* view, PuglEventType type);
+
+/** Dispatch `event` to `view` while already in the graphics context. */
+void puglDispatchEventInContext(PuglView* view, const PuglEvent* event);
+
+/** Dispatch `event` to `view`, entering graphics context if necessary. */
void puglDispatchEvent(PuglView* view, const PuglEvent* event);
/** Set internal (stored in view) clipboard contents. */
@@ -66,8 +71,6 @@ puglSetInternalClipboard(PuglView* view,
const void* data,
size_t len);
-#ifdef __cplusplus
-} /* extern "C" */
-#endif
+PUGL_END_DECLS
#endif // PUGL_DETAIL_IMPLEMENTATION_H
diff --git a/pugl/pugl/detail/mac.h b/pugl/pugl/detail/mac.h
index 0652733..2243337 100644
--- a/pugl/pugl/detail/mac.h
+++ b/pugl/pugl/detail/mac.h
@@ -16,7 +16,7 @@
*/
/**
- @file win.h Shared definitions for MacOS implementation.
+ @file mac.h Shared definitions for MacOS implementation.
*/
#include "pugl/pugl.h"
@@ -30,13 +30,12 @@
@public
PuglView* puglview;
NSTrackingArea* trackingArea;
- NSMutableAttributedString* markedText;
+ NSMutableAttributedString* markedText;
NSTimer* timer;
- NSTimer* urgentTimer;
+ NSMutableDictionary* userTimers;
bool reshaped;
}
-- (void) dispatchConfigure:(NSRect)bounds;
- (void) dispatchExpose:(NSRect)rect;
- (void) setReshaped;
@@ -61,6 +60,6 @@ struct PuglInternalsImpl {
NSApplication* app;
PuglWrapperView* wrapperView;
NSView* drawView;
- id window;
+ PuglWindow* window;
uint32_t mods;
};
diff --git a/pugl/pugl/detail/mac.m b/pugl/pugl/detail/mac.m
index ed85e0d..501be02 100644
--- a/pugl/pugl/detail/mac.m
+++ b/pugl/pugl/detail/mac.m
@@ -1,5 +1,5 @@
/*
- Copyright 2012-2019 David Robillard <http://drobilla.net>
+ Copyright 2012-2020 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
@@ -98,18 +98,33 @@ updateViewRect(PuglView* view)
return YES;
}
-@end
+- (void) setIsVisible:(BOOL)flag
+{
+ if (flag && !puglview->visible) {
+ const PuglEventConfigure ev = {
+ PUGL_CONFIGURE,
+ 0,
+ puglview->frame.x,
+ puglview->frame.y,
+ puglview->frame.width,
+ puglview->frame.height,
+ };
-@implementation PuglWrapperView
+ puglDispatchEvent(puglview, (const PuglEvent*)&ev);
+ puglDispatchSimpleEvent(puglview, PUGL_MAP);
+ } else if (!flag && puglview->visible) {
+ puglDispatchSimpleEvent(puglview, PUGL_UNMAP);
+ }
-- (void) resizeWithOldSuperviewSize:(NSSize)oldSize
-{
- [super resizeWithOldSuperviewSize:oldSize];
+ puglview->visible = flag;
- const NSRect bounds = [self bounds];
- puglview->backend->resize(puglview, bounds.size.width, bounds.size.height);
+ [super setIsVisible:flag];
}
+@end
+
+@implementation PuglWrapperView
+
- (void) dispatchExpose:(NSRect)rect
{
if (reshaped) {
@@ -128,6 +143,10 @@ updateViewRect(PuglView* view)
reshaped = false;
}
+ if (![[puglview->impl->drawView window] isVisible]) {
+ return;
+ }
+
const PuglEventExpose ev = {
PUGL_EXPOSE,
0,
@@ -173,38 +192,34 @@ keySymToSpecial(const NSEvent* const ev)
NSString* chars = [ev charactersIgnoringModifiers];
if ([chars length] == 1) {
switch ([chars characterAtIndex:0]) {
- case NSDeleteCharacter: return PUGL_KEY_BACKSPACE;
- case NSTabCharacter: return PUGL_KEY_TAB;
- case NSCarriageReturnCharacter: return PUGL_KEY_RETURN;
- case 0x001B: return PUGL_KEY_ESCAPE;
- case NSDeleteFunctionKey: return PUGL_KEY_DELETE;
- 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;
- 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;
- default: break;
+ 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]
}
@@ -248,19 +263,20 @@ handleCrossing(PuglWrapperView* view, NSEvent* event, const PuglEventType type)
rloc.x,
[[NSScreen mainScreen] frame].size.height - rloc.y,
getModifiers(event),
- PUGL_CROSSING_NORMAL
+ PUGL_CROSSING_NORMAL,
};
+
puglDispatchEvent(view->puglview, (const PuglEvent*)&ev);
}
- (void) mouseEntered:(NSEvent*)event
{
- handleCrossing(self, event, PUGL_ENTER_NOTIFY);
+ handleCrossing(self, event, PUGL_POINTER_IN);
}
- (void) mouseExited:(NSEvent*)event
{
- handleCrossing(self, event, PUGL_LEAVE_NOTIFY);
+ handleCrossing(self, event, PUGL_POINTER_OUT);
}
- (void) mouseMoved:(NSEvent*)event
@@ -268,7 +284,7 @@ handleCrossing(PuglWrapperView* view, NSEvent* event, const PuglEventType type)
const NSPoint wloc = [self eventLocation:event];
const NSPoint rloc = [NSEvent mouseLocation];
const PuglEventMotion ev = {
- PUGL_MOTION_NOTIFY,
+ PUGL_MOTION,
0,
[event timestamp],
wloc.x,
@@ -277,8 +293,9 @@ handleCrossing(PuglWrapperView* view, NSEvent* event, const PuglEventType type)
[[NSScreen mainScreen] frame].size.height - rloc.y,
getModifiers(event),
0,
- 1
+ 1,
};
+
puglDispatchEvent(puglview, (const PuglEvent*)&ev);
}
@@ -310,8 +327,9 @@ handleCrossing(PuglWrapperView* view, NSEvent* event, const PuglEventType type)
rloc.x,
[[NSScreen mainScreen] frame].size.height - rloc.y,
getModifiers(event),
- (uint32_t)[event buttonNumber] + 1
+ (uint32_t)[event buttonNumber] + 1,
};
+
puglDispatchEvent(puglview, (const PuglEvent*)&ev);
}
@@ -328,8 +346,9 @@ handleCrossing(PuglWrapperView* view, NSEvent* event, const PuglEventType type)
rloc.x,
[[NSScreen mainScreen] frame].size.height - rloc.y,
getModifiers(event),
- (uint32_t)[event buttonNumber] + 1
+ (uint32_t)[event buttonNumber] + 1,
};
+
puglDispatchEvent(puglview, (const PuglEvent*)&ev);
}
@@ -367,8 +386,9 @@ handleCrossing(PuglWrapperView* view, NSEvent* event, const PuglEventType type)
[[NSScreen mainScreen] frame].size.height - rloc.y,
getModifiers(event),
[event scrollingDeltaX],
- [event scrollingDeltaY]
+ [event scrollingDeltaY],
};
+
puglDispatchEvent(puglview, (const PuglEvent*)&ev);
}
@@ -378,13 +398,12 @@ handleCrossing(PuglWrapperView* view, NSEvent* event, const PuglEventType type)
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 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,
@@ -396,7 +415,7 @@ handleCrossing(PuglWrapperView* view, NSEvent* event, const PuglEventType type)
[[NSScreen mainScreen] frame].size.height - rloc.y,
getModifiers(event),
[event keyCode],
- (code != 0xFFFD) ? code : 0
+ (code != 0xFFFD) ? code : 0,
};
puglDispatchEvent(puglview, (const PuglEvent*)&ev);
@@ -406,15 +425,14 @@ handleCrossing(PuglWrapperView* view, NSEvent* event, const PuglEventType type)
}
}
-- (void) keyUp:(NSEvent*)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 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,
@@ -426,8 +444,9 @@ handleCrossing(PuglWrapperView* view, NSEvent* event, const PuglEventType type)
[[NSScreen mainScreen] frame].size.height - rloc.y,
getModifiers(event),
[event keyCode],
- (code != 0xFFFD) ? code : 0
+ (code != 0xFFFD) ? code : 0,
};
+
puglDispatchEvent(puglview, (const PuglEvent*)&ev);
}
@@ -456,7 +475,7 @@ handleCrossing(PuglWrapperView* view, NSEvent* event, const PuglEventType type)
(void)replacement;
[markedText release];
markedText = (
- [string isKindOfClass:[NSAttributedString class]]
+ [(NSObject*)string isKindOfClass:[NSAttributedString class]]
? [[NSMutableAttributedString alloc] initWithAttributedString:string]
: [[NSMutableAttributedString alloc] initWithString:string]);
}
@@ -492,7 +511,7 @@ handleCrossing(PuglWrapperView* view, NSEvent* event, const PuglEventType type)
(void)range;
(void)actual;
- const NSRect frame = [(id)puglview bounds];
+ const NSRect frame = [self bounds];
return NSMakeRect(frame.origin.x, frame.origin.y, 0.0, 0.0);
}
@@ -508,8 +527,8 @@ handleCrossing(PuglWrapperView* view, NSEvent* event, const PuglEventType type)
NSEvent* const event = [NSApp currentEvent];
NSString* const characters =
- ([string isKindOfClass:[NSAttributedString class]]
- ? [string string]
+ ([(NSObject*)string isKindOfClass:[NSAttributedString class]]
+ ? [(NSAttributedString*)string string]
: (NSString*)string);
const NSPoint wloc = [self eventLocation:event];
@@ -527,17 +546,19 @@ handleCrossing(PuglWrapperView* view, NSEvent* event, const PuglEventType type)
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 } };
+ 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);
@@ -548,7 +569,7 @@ handleCrossing(PuglWrapperView* view, NSEvent* event, const PuglEventType type)
{
const uint32_t mods = getModifiers(event);
PuglEventType type = PUGL_NOTHING;
- PuglKey special = 0;
+ PuglKey special = (PuglKey)0;
if ((mods & PUGL_MOD_SHIFT) != (puglview->impl->mods & PUGL_MOD_SHIFT)) {
type = mods & PUGL_MOD_SHIFT ? PUGL_KEY_PRESS : PUGL_KEY_RELEASE;
@@ -603,14 +624,23 @@ handleCrossing(PuglWrapperView* view, NSEvent* event, const PuglEventType type)
[super viewWillStartLiveResize];
}
+- (void) viewWillDraw
+{
+ puglDispatchSimpleEvent(puglview, PUGL_UPDATE);
+ [super viewWillDraw];
+}
+
- (void) resizeTick
{
puglPostRedisplay(puglview);
}
-- (void) urgentTick
+- (void) timerTick:(NSTimer*)userTimer
{
- [puglview->world->impl->app requestUserAttention:NSInformationalRequest];
+ const NSNumber* userInfo = userTimer.userInfo;
+ const PuglEventTimer ev = {PUGL_TIMER, 0, userInfo.unsignedLongValue};
+
+ puglDispatchEvent(puglview, (const PuglEvent*)&ev);
}
- (void) viewDidEndLiveResize
@@ -646,9 +676,7 @@ handleCrossing(PuglWrapperView* view, NSEvent* event, const PuglEventType type)
{
(void)sender;
- PuglEvent ev = { 0 };
- ev.type = PUGL_CLOSE;
- puglDispatchEvent(window->puglview, &ev);
+ puglDispatchSimpleEvent(window->puglview, PUGL_CLOSE);
return YES;
}
@@ -663,14 +691,7 @@ handleCrossing(PuglWrapperView* view, NSEvent* event, const PuglEventType type)
{
(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;
+ PuglEvent ev = {{PUGL_FOCUS_IN, 0}};
ev.focus.grab = false;
puglDispatchEvent(window->puglview, &ev);
}
@@ -679,8 +700,7 @@ handleCrossing(PuglWrapperView* view, NSEvent* event, const PuglEventType type)
{
(void)notification;
- PuglEvent ev = { 0 };
- ev.type = PUGL_FOCUS_OUT;
+ PuglEvent ev = {{PUGL_FOCUS_OUT, 0}};
ev.focus.grab = false;
puglDispatchEvent(window->puglview, &ev);
}
@@ -688,7 +708,8 @@ handleCrossing(PuglWrapperView* view, NSEvent* event, const PuglEventType type)
@end
PuglWorldInternals*
-puglInitWorldInternals(void)
+puglInitWorldInternals(PuglWorldType PUGL_UNUSED(type),
+ PuglWorldFlags PUGL_UNUSED(flags))
{
PuglWorldInternals* impl = (PuglWorldInternals*)calloc(
1, sizeof(PuglWorldInternals));
@@ -706,6 +727,12 @@ puglFreeWorldInternals(PuglWorld* world)
free(world->impl);
}
+void*
+puglGetNativeWorld(PuglWorld* PUGL_UNUSED(world))
+{
+ return NULL;
+}
+
PuglInternals*
puglInitViewInternals(void)
{
@@ -726,13 +753,14 @@ puglConstraint(id item, NSLayoutAttribute attribute, float constant)
}
PuglStatus
-puglCreateWindow(PuglView* view, const char* title)
+puglRealize(PuglView* view)
{
PuglInternals* impl = view->impl;
// Create wrapper view to handle input
impl->wrapperView = [PuglWrapperView alloc];
impl->wrapperView->puglview = view;
+ impl->wrapperView->userTimers = [[NSMutableDictionary alloc] init];
impl->wrapperView->markedText = [[NSMutableAttributedString alloc] init];
[impl->wrapperView setAutoresizesSubviews:YES];
[impl->wrapperView initWithFrame:
@@ -743,13 +771,13 @@ puglCreateWindow(PuglView* view, const char* title)
puglConstraint(impl->wrapperView, NSLayoutAttributeHeight, view->minHeight)];
// Create draw view to be rendered to
- int st = 0;
+ PuglStatus st = PUGL_SUCCESS;
if ((st = view->backend->configure(view)) ||
(st = view->backend->create(view))) {
return st;
}
- // Add draw view to wraper view
+ // Add draw view to wrapper view
[impl->wrapperView addSubview:impl->drawView];
[impl->wrapperView setHidden:NO];
[impl->drawView setHidden:NO];
@@ -760,11 +788,6 @@ puglCreateWindow(PuglView* view, const char* title)
[impl->drawView setHidden:NO];
[[impl->drawView window] makeFirstResponder:impl->wrapperView];
} else {
- NSString* titleString = [[NSString alloc]
- initWithBytes:title
- length:strlen(title)
- encoding:NSUTF8StringEncoding];
-
const NSRect frame = rectToScreen(
NSMakeRect(view->frame.x, view->frame.y,
view->minWidth, view->minHeight));
@@ -776,20 +799,28 @@ puglCreateWindow(PuglView* view, const char* title)
style |= NSResizableWindowMask;
}
- id window = [[[PuglWindow alloc]
+ PuglWindow* window = [[[PuglWindow alloc]
initWithContentRect:frame
styleMask:style
backing:NSBackingStoreBuffered
defer:NO
] retain];
[window setPuglview:view];
- [window setTitle:titleString];
+
+ if (view->title) {
+ NSString* titleString = [[NSString alloc]
+ initWithBytes:view->title
+ length:strlen(view->title)
+ encoding:NSUTF8StringEncoding];
+
+ [window setTitle:titleString];
+ }
+
if (view->minWidth || view->minHeight) {
[window setContentMinSize:NSMakeSize(view->minWidth,
view->minHeight)];
}
impl->window = window;
- puglSetWindowTitle(view, title);
((NSWindow*)window).delegate = [[PuglWindowDelegate alloc]
initWithPuglWindow:window];
@@ -803,19 +834,25 @@ puglCreateWindow(PuglView* view, const char* title)
[view->world->impl->app activateIgnoringOtherApps:YES];
[window makeFirstResponder:impl->wrapperView];
[window makeKeyAndOrderFront:window];
+ [impl->window setIsVisible:NO];
}
[impl->wrapperView updateTrackingAreas];
+ puglDispatchSimpleEvent(view, PUGL_CREATE);
+
return 0;
}
PuglStatus
puglShowWindow(PuglView* view)
{
- [view->impl->window setIsVisible:YES];
- updateViewRect(view);
- view->visible = true;
+ if (![view->impl->window isVisible]) {
+ [view->impl->window setIsVisible:YES];
+ [view->impl->drawView setNeedsDisplay: YES];
+ updateViewRect(view);
+ }
+
return PUGL_SUCCESS;
}
@@ -823,24 +860,30 @@ PuglStatus
puglHideWindow(PuglView* view)
{
[view->impl->window setIsVisible:NO];
- view->visible = false;
return PUGL_SUCCESS;
}
void
puglFreeViewInternals(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];
+ if (view) {
+ if (view->backend) {
+ view->backend->destroy(view);
+ }
+
+ if (view->impl) {
+ [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->impl);
+ }
}
- free(view->impl);
}
PuglStatus
@@ -867,80 +910,145 @@ puglRequestAttention(PuglView* view)
{
if (![view->impl->window isKeyWindow]) {
[view->world->impl->app requestUserAttention:NSInformationalRequest];
- view->impl->wrapperView->urgentTimer =
- [NSTimer scheduledTimerWithTimeInterval:2.0
- target:view->impl->wrapperView
- selector:@selector(urgentTick)
- userInfo:nil
- repeats:YES];
}
return PUGL_SUCCESS;
}
PuglStatus
-puglPollEvents(PuglWorld* world, const double timeout)
+puglStartTimer(PuglView* view, uintptr_t id, double timeout)
{
- NSDate* date = ((timeout < 0) ? [NSDate distantFuture] :
- (timeout == 0) ? nil :
- [NSDate dateWithTimeIntervalSinceNow:timeout]);
+ puglStopTimer(view, id);
- /* Note that dequeue:NO is broken (it blocks forever even when events are
- pending), so we work around this by dequeueing the event then posting it
- back to the front of the queue. */
- NSEvent* event = [world->impl->app
- nextEventMatchingMask:NSAnyEventMask
- untilDate:date
- inMode:NSDefaultRunLoopMode
- dequeue:YES];
+ NSNumber* idNumber = [NSNumber numberWithUnsignedLong:id];
- [world->impl->app postEvent:event atStart:true];
+ NSTimer* timer = [NSTimer timerWithTimeInterval:timeout
+ target:view->impl->wrapperView
+ selector:@selector(timerTick:)
+ userInfo:idNumber
+ repeats:YES];
+
+ [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
+
+ view->impl->wrapperView->userTimers[idNumber] = timer;
return PUGL_SUCCESS;
}
PuglStatus
+puglStopTimer(PuglView* view, uintptr_t id)
+{
+ NSNumber* idNumber = [NSNumber numberWithUnsignedLong:id];
+ NSTimer* timer = view->impl->wrapperView->userTimers[idNumber];
+
+ if (timer) {
+ [view->impl->wrapperView->userTimers removeObjectForKey:timer];
+ [timer invalidate];
+ return PUGL_SUCCESS;
+ }
+
+ return PUGL_UNKNOWN_ERROR;
+}
+
+PuglStatus puglSendEvent(PuglView* view, const PuglEvent* event)
+{
+ if (event->type == PUGL_CLIENT) {
+ PuglWrapperView* wrapper = view->impl->wrapperView;
+ const NSWindow* window = [wrapper window];
+ const NSRect rect = [wrapper frame];
+ const NSPoint center = {NSMidX(rect), NSMidY(rect)};
+
+ NSEvent* nsevent = [NSEvent
+ otherEventWithType:NSApplicationDefined
+ location:center
+ modifierFlags:0
+ timestamp:[[NSProcessInfo processInfo] systemUptime]
+ windowNumber:window.windowNumber
+ context:nil
+ subtype:PUGL_CLIENT
+ data1:(NSInteger)event->client.data1
+ data2:(NSInteger)event->client.data2];
+
+ [view->world->impl->app postEvent:nsevent atStart:false];
+ return PUGL_SUCCESS;
+ }
+
+ return PUGL_UNSUPPORTED_TYPE;
+}
+
+#ifndef PUGL_DISABLE_DEPRECATED
+PuglStatus
puglWaitForEvent(PuglView* view)
{
return puglPollEvents(view->world, -1.0);
}
+#endif
+
+static void
+dispatchClientEvent(PuglWorld* world, NSEvent* ev)
+{
+ NSWindow* win = [ev window];
+ NSPoint loc = [ev locationInWindow];
+ for (size_t i = 0; i < world->numViews; ++i) {
+ PuglView* view = world->views[i];
+ PuglWrapperView* wrapper = view->impl->wrapperView;
+ if ([wrapper window] == win && NSPointInRect(loc, [wrapper frame])) {
+ const PuglEventClient event = {PUGL_CLIENT,
+ 0,
+ (uintptr_t)[ev data1],
+ (uintptr_t)[ev data2]};
+
+ puglDispatchEvent(view, (const PuglEvent*)&event);
+ }
+ }
+}
-PUGL_API PuglStatus
-puglDispatchEvents(PuglWorld* world)
+PuglStatus
+puglUpdate(PuglWorld* world, const double timeout)
{
+ NSDate* date = ((timeout < 0)
+ ? [NSDate distantFuture]
+ : [NSDate dateWithTimeIntervalSinceNow:timeout]);
+
for (NSEvent* ev = NULL;
(ev = [world->impl->app nextEventMatchingMask:NSAnyEventMask
- untilDate:nil
+ untilDate:date
inMode:NSDefaultRunLoopMode
dequeue:YES]);) {
+
+ if ([ev type] == NSApplicationDefined && [ev subtype] == PUGL_CLIENT) {
+ dispatchClientEvent(world, ev);
+ }
+
[world->impl->app sendEvent: ev];
+
+ if (timeout < 0) {
+ // Now that we've waited and got an event, set the date to now to
+ // avoid looping forever
+ date = [NSDate date];
+ }
+ }
+
+ for (size_t i = 0; i < world->numViews; ++i) {
+ PuglView* const view = world->views[i];
+
+ if ([[view->impl->drawView window] isVisible]) {
+ puglDispatchSimpleEvent(view, PUGL_UPDATE);
+ }
+
+ [view->impl->drawView displayIfNeeded];
}
return PUGL_SUCCESS;
}
+#ifndef PUGL_DISABLE_DEPRECATED
PuglStatus
puglProcessEvents(PuglView* view)
{
return puglDispatchEvents(view->world);
}
-
-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;
-}
+#endif
double
puglGetTime(const PuglWorld* world)
@@ -955,10 +1063,19 @@ puglPostRedisplay(PuglView* view)
return PUGL_SUCCESS;
}
-PuglNativeWindow
+PuglStatus
+puglPostRedisplayRect(PuglView* view, const PuglRect rect)
+{
+ [view->impl->drawView setNeedsDisplayInRect:
+ NSMakeRect(rect.x, rect.y, rect.width, rect.height)];
+
+ return PUGL_SUCCESS;
+}
+
+PuglNativeView
puglGetNativeWindow(PuglView* view)
{
- return (PuglNativeWindow)view->impl->wrapperView;
+ return (PuglNativeView)view->impl->wrapperView;
}
PuglStatus
@@ -966,12 +1083,12 @@ puglSetWindowTitle(PuglView* view, const char* title)
{
puglSetString(&view->title, title);
- NSString* titleString = [[NSString alloc]
- initWithBytes:title
- length:strlen(title)
- encoding:NSUTF8StringEncoding];
-
if (view->impl->window) {
+ NSString* titleString = [[NSString alloc]
+ initWithBytes:title
+ length:strlen(title)
+ encoding:NSUTF8StringEncoding];
+
[view->impl->window setTitle:titleString];
}
@@ -1068,11 +1185,16 @@ puglSetClipboard(PuglView* const view,
return st;
}
- [pasteboard declareTypes:[NSArray arrayWithObjects:NSStringPboardType, nil]
- owner:nil];
+ NSString* nsString = [NSString stringWithUTF8String:str];
+ if (nsString) {
+ [pasteboard
+ declareTypes:[NSArray arrayWithObjects:NSStringPboardType, nil]
+ owner:nil];
- [pasteboard setString:[NSString stringWithUTF8String:str]
- forType:NSStringPboardType];
+ [pasteboard setString:nsString forType:NSStringPboardType];
- return PUGL_SUCCESS;
+ return PUGL_SUCCESS;
+ }
+
+ return PUGL_UNKNOWN_ERROR;
}
diff --git a/pugl/pugl/detail/mac_cairo.m b/pugl/pugl/detail/mac_cairo.m
index 143fbb0..51c1c13 100644
--- a/pugl/pugl/detail/mac_cairo.m
+++ b/pugl/pugl/detail/mac_cairo.m
@@ -20,7 +20,8 @@
#include "pugl/detail/implementation.h"
#include "pugl/detail/mac.h"
-#include "pugl/pugl_cairo_backend.h"
+#include "pugl/pugl_cairo.h"
+#include "pugl/pugl_stub.h"
#include <cairo-quartz.h>
@@ -62,12 +63,6 @@
@end
static PuglStatus
-puglMacCairoConfigure(PuglView* PUGL_UNUSED(view))
-{
- return PUGL_SUCCESS;
-}
-
-static PuglStatus
puglMacCairoCreate(PuglView* view)
{
PuglInternals* impl = view->impl;
@@ -98,10 +93,10 @@ puglMacCairoDestroy(PuglView* view)
}
static PuglStatus
-puglMacCairoEnter(PuglView* view, bool drawing)
+puglMacCairoEnter(PuglView* view, const PuglEventExpose* expose)
{
PuglCairoView* const drawView = (PuglCairoView*)view->impl->drawView;
- if (!drawing) {
+ if (!expose) {
return PUGL_SUCCESS;
}
@@ -119,10 +114,10 @@ puglMacCairoEnter(PuglView* view, bool drawing)
}
static PuglStatus
-puglMacCairoLeave(PuglView* view, bool drawing)
+puglMacCairoLeave(PuglView* view, const PuglEventExpose* expose)
{
PuglCairoView* const drawView = (PuglCairoView*)view->impl->drawView;
- if (!drawing) {
+ if (!expose) {
return PUGL_SUCCESS;
}
@@ -141,15 +136,6 @@ puglMacCairoLeave(PuglView* view, bool drawing)
return PUGL_SUCCESS;
}
-static PuglStatus
-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 PUGL_SUCCESS;
-}
-
static void*
puglMacCairoGetContext(PuglView* view)
{
@@ -158,15 +144,12 @@ puglMacCairoGetContext(PuglView* view)
const PuglBackend* puglCairoBackend(void)
{
- static const PuglBackend backend = {
- puglMacCairoConfigure,
- puglMacCairoCreate,
- puglMacCairoDestroy,
- puglMacCairoEnter,
- puglMacCairoLeave,
- puglMacCairoResize,
- puglMacCairoGetContext
- };
+ static const PuglBackend backend = {puglStubConfigure,
+ puglMacCairoCreate,
+ puglMacCairoDestroy,
+ puglMacCairoEnter,
+ puglMacCairoLeave,
+ puglMacCairoGetContext};
return &backend;
}
diff --git a/pugl/pugl/detail/mac_gl.m b/pugl/pugl/detail/mac_gl.m
index cb9117e..eda4371 100644
--- a/pugl/pugl/detail/mac_gl.m
+++ b/pugl/pugl/detail/mac_gl.m
@@ -1,5 +1,5 @@
/*
- Copyright 2019 David Robillard <http://drobilla.net>
+ Copyright 2019-2020 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
@@ -20,7 +20,8 @@
#include "pugl/detail/implementation.h"
#include "pugl/detail/mac.h"
-#include "pugl/pugl_gl_backend.h"
+#include "pugl/pugl_gl.h"
+#include "pugl/pugl_stub.h"
#ifndef __MAC_10_10
# define NSOpenGLProfileVersion4_1Core NSOpenGLProfileVersion3_2Core
@@ -38,14 +39,14 @@
- (id) initWithFrame:(NSRect)frame
{
- const bool compat = puglview->hints[PUGL_USE_COMPAT_PROFILE];
- const int samples = puglview->hints[PUGL_SAMPLES];
- const int major = puglview->hints[PUGL_CONTEXT_VERSION_MAJOR];
- const int profile = ((compat || major < 3)
- ? NSOpenGLProfileVersionLegacy
- : (major >= 4
- ? NSOpenGLProfileVersion4_1Core
- : NSOpenGLProfileVersion3_2Core));
+ const bool compat = puglview->hints[PUGL_USE_COMPAT_PROFILE];
+ const unsigned samples = (unsigned)puglview->hints[PUGL_SAMPLES];
+ const int major = puglview->hints[PUGL_CONTEXT_VERSION_MAJOR];
+ const unsigned profile = ((compat || major < 3)
+ ? NSOpenGLProfileVersionLegacy
+ : (major >= 4
+ ? NSOpenGLProfileVersion4_1Core
+ : NSOpenGLProfileVersion3_2Core));
NSOpenGLPixelFormatAttribute pixelAttribs[16] = {
NSOpenGLPFADoubleBuffer,
@@ -92,12 +93,6 @@
@end
static PuglStatus
-puglMacGlConfigure(PuglView* PUGL_UNUSED(view))
-{
- return PUGL_SUCCESS;
-}
-
-static PuglStatus
puglMacGlCreate(PuglView* view)
{
PuglInternals* impl = view->impl;
@@ -130,7 +125,7 @@ puglMacGlDestroy(PuglView* view)
}
static PuglStatus
-puglMacGlEnter(PuglView* view, bool PUGL_UNUSED(drawing))
+puglMacGlEnter(PuglView* view, const PuglEventExpose* PUGL_UNUSED(expose))
{
PuglOpenGLView* const drawView = (PuglOpenGLView*)view->impl->drawView;
@@ -139,11 +134,11 @@ puglMacGlEnter(PuglView* view, bool PUGL_UNUSED(drawing))
}
static PuglStatus
-puglMacGlLeave(PuglView* view, bool drawing)
+puglMacGlLeave(PuglView* view, const PuglEventExpose* expose)
{
PuglOpenGLView* const drawView = (PuglOpenGLView*)view->impl->drawView;
- if (drawing) {
+ if (expose) {
[[drawView openGLContext] flushBuffer];
}
@@ -152,33 +147,31 @@ puglMacGlLeave(PuglView* view, bool drawing)
return PUGL_SUCCESS;
}
-static PuglStatus
-puglMacGlResize(PuglView* view, int PUGL_UNUSED(width), int PUGL_UNUSED(height))
+PuglGlFunc
+puglGetProcAddress(const char *name)
{
- PuglOpenGLView* const drawView = (PuglOpenGLView*)view->impl->drawView;
+ CFBundleRef framework =
+ CFBundleGetBundleWithIdentifier(CFSTR("com.apple.opengl"));
- [drawView reshape];
+ CFStringRef symbol = CFStringCreateWithCString(
+ kCFAllocatorDefault, name, kCFStringEncodingASCII);
- return PUGL_SUCCESS;
-}
+ PuglGlFunc func = (PuglGlFunc)CFBundleGetFunctionPointerForName(
+ framework, symbol);
-static void*
-puglMacGlGetContext(PuglView* PUGL_UNUSED(view))
-{
- return NULL;
+ CFRelease(symbol);
+
+ return func;
}
const PuglBackend* puglGlBackend(void)
{
- static const PuglBackend backend = {
- puglMacGlConfigure,
- puglMacGlCreate,
- puglMacGlDestroy,
- puglMacGlEnter,
- puglMacGlLeave,
- puglMacGlResize,
- puglMacGlGetContext
- };
+ static const PuglBackend backend = {puglStubConfigure,
+ puglMacGlCreate,
+ puglMacGlDestroy,
+ puglMacGlEnter,
+ puglMacGlLeave,
+ puglStubGetContext};
return &backend;
}
diff --git a/pugl/pugl/detail/mac_stub.m b/pugl/pugl/detail/mac_stub.m
new file mode 100644
index 0000000..71a54b8
--- /dev/null
+++ b/pugl/pugl/detail/mac_stub.m
@@ -0,0 +1,95 @@
+/*
+ Copyright 2019-2020 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_stub.m Stub graphics backend for MacOS.
+*/
+
+#include "pugl/detail/implementation.h"
+#include "pugl/detail/mac.h"
+#include "pugl/pugl_stub.h"
+
+#import <Cocoa/Cocoa.h>
+
+@interface PuglStubView : NSView
+{
+@public
+ PuglView* puglview;
+}
+
+@end
+
+@implementation PuglStubView
+
+- (void) resizeWithOldSuperviewSize:(NSSize)oldSize
+{
+ PuglWrapperView* wrapper = (PuglWrapperView*)[self superview];
+
+ [super resizeWithOldSuperviewSize:oldSize];
+ [wrapper setReshaped];
+}
+
+- (void) drawRect:(NSRect)rect
+{
+ PuglWrapperView* wrapper = (PuglWrapperView*)[self superview];
+
+ [wrapper dispatchExpose:rect];
+}
+
+@end
+
+static PuglStatus
+puglMacStubCreate(PuglView* view)
+{
+ PuglInternals* impl = view->impl;
+ PuglStubView* drawView = [PuglStubView alloc];
+
+ drawView->puglview = view;
+ [drawView initWithFrame:NSMakeRect(0, 0, view->frame.width, view->frame.height)];
+ if (view->hints[PUGL_RESIZABLE]) {
+ [drawView setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable];
+ } else {
+ [drawView setAutoresizingMask:NSViewNotSizable];
+ }
+
+ impl->drawView = drawView;
+ return PUGL_SUCCESS;
+}
+
+static PuglStatus
+puglMacStubDestroy(PuglView* view)
+{
+ PuglStubView* const drawView = (PuglStubView*)view->impl->drawView;
+
+ [drawView removeFromSuperview];
+ [drawView release];
+
+ view->impl->drawView = nil;
+ return PUGL_SUCCESS;
+}
+
+const PuglBackend*
+puglStubBackend(void)
+{
+ static const PuglBackend backend = {puglStubConfigure,
+ puglMacStubCreate,
+ puglMacStubDestroy,
+ puglStubEnter,
+ puglStubLeave,
+ puglStubGetContext};
+
+ return &backend;
+}
diff --git a/pugl/pugl/detail/types.h b/pugl/pugl/detail/types.h
index d018be5..eb450e1 100644
--- a/pugl/pugl/detail/types.h
+++ b/pugl/pugl/detail/types.h
@@ -28,12 +28,12 @@
#include <stdint.h>
// Unused parameter macro to suppresses warnings and make it impossible to use
-#if defined(__cplusplus) || defined(_MSC_VER)
+#if defined(__cplusplus)
# define PUGL_UNUSED(name)
#elif defined(__GNUC__)
# define PUGL_UNUSED(name) name##_unused __attribute__((__unused__))
#else
-# define PUGL_UNUSED(name)
+# define PUGL_UNUSED(name) name
#endif
/** Platform-specific world internals. */
@@ -43,7 +43,7 @@ typedef struct PuglWorldInternalsImpl PuglWorldInternals;
typedef struct PuglInternalsImpl PuglInternals;
/** View hints. */
-typedef int PuglHints[PUGL_NUM_WINDOW_HINTS];
+typedef int PuglHints[PUGL_NUM_VIEW_HINTS];
/** Blob of arbitrary data. */
typedef struct {
@@ -60,10 +60,11 @@ struct PuglViewImpl {
PuglEventFunc eventFunc;
char* title;
PuglBlob clipboard;
- PuglNativeWindow parent;
+ PuglNativeView parent;
uintptr_t transientParent;
PuglHints hints;
PuglRect frame;
+ PuglEventConfigure lastConfigure;
int minWidth;
int minHeight;
int minAspectX;
@@ -71,16 +72,18 @@ struct PuglViewImpl {
int maxAspectX;
int maxAspectY;
bool visible;
- bool redisplay;
};
/** Cross-platform world definition. */
struct PuglWorldImpl {
PuglWorldInternals* impl;
+ PuglWorldHandle handle;
+ PuglLogFunc logFunc;
char* className;
double startTime;
size_t numViews;
PuglView** views;
+ PuglLogLevel logLevel;
};
/** Opaque surface used by graphics backend. */
@@ -97,14 +100,11 @@ struct PuglBackendImpl {
/** Destroy surface and drawing context. */
PuglStatus (*destroy)(PuglView*);
- /** Enter drawing context, for drawing if parameter is true. */
- PuglStatus (*enter)(PuglView*, bool);
-
- /** Leave drawing context, after drawing if parameter is true. */
- PuglStatus (*leave)(PuglView*, bool);
+ /** Enter drawing context, for drawing if expose is non-null. */
+ PuglStatus (*enter)(PuglView*, const PuglEventExpose*);
- /** Resize drawing context to the given width and height. */
- PuglStatus (*resize)(PuglView*, int, int);
+ /** Leave drawing context, after drawing if expose is non-null. */
+ PuglStatus (*leave)(PuglView*, const PuglEventExpose*);
/** Return the puglGetContext() handle for the application, if any. */
void* (*getContext)(PuglView*);
diff --git a/pugl/pugl/detail/win.c b/pugl/pugl/detail/win.c
index 8886496..44ba6cd 100644
--- a/pugl/pugl/detail/win.c
+++ b/pugl/pugl/detail/win.c
@@ -1,5 +1,5 @@
/*
- Copyright 2012-2019 David Robillard <http://drobilla.net>
+ Copyright 2012-2020 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,15 +18,17 @@
@file win.c Windows implementation.
*/
-#include "pugl/detail/implementation.h"
#include "pugl/detail/win.h"
+
+#include "pugl/detail/implementation.h"
#include "pugl/pugl.h"
+#include "pugl/pugl_stub.h"
#include <windows.h>
#include <windowsx.h>
+#include <math.h>
#include <stdbool.h>
-#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <wctype.h>
@@ -44,10 +46,11 @@
# define GWLP_USERDATA (-21)
#endif
-#define PUGL_LOCAL_CLOSE_MSG (WM_USER + 50)
-#define PUGL_LOCAL_MARK_MSG (WM_USER + 51)
-#define PUGL_RESIZE_TIMER_ID 9461
-#define PUGL_URGENT_TIMER_ID 9462
+#define PUGL_LOCAL_CLOSE_MSG (WM_USER + 50)
+#define PUGL_LOCAL_MARK_MSG (WM_USER + 51)
+#define PUGL_LOCAL_CLIENT_MSG (WM_USER + 52)
+#define PUGL_RESIZE_TIMER_ID 9461
+#define PUGL_USER_TIMER_MIN 9470
typedef BOOL (WINAPI *PFN_SetProcessDPIAware)(void);
@@ -102,7 +105,8 @@ puglRegisterWindowClass(const char* name)
}
PuglWorldInternals*
-puglInitWorldInternals(void)
+puglInitWorldInternals(PuglWorldType PUGL_UNUSED(type),
+ PuglWorldFlags PUGL_UNUSED(flags))
{
PuglWorldInternals* impl = (PuglWorldInternals*)calloc(
1, sizeof(PuglWorldInternals));
@@ -129,14 +133,20 @@ puglInitWorldInternals(void)
return impl;
}
+void*
+puglGetNativeWorld(PuglWorld* PUGL_UNUSED(world))
+{
+ return GetModuleHandle(NULL);
+}
+
PuglInternals*
puglInitViewInternals(void)
{
return (PuglInternals*)calloc(1, sizeof(PuglInternals));
}
-PuglStatus
-puglPollEvents(PuglWorld* world, const double timeout)
+static PuglStatus
+puglPollWinEvents(PuglWorld* world, const double timeout)
{
(void)world;
@@ -150,12 +160,10 @@ puglPollEvents(PuglWorld* world, const double timeout)
}
PuglStatus
-puglCreateWindow(PuglView* view, const char* title)
+puglRealize(PuglView* view)
{
PuglInternals* impl = view->impl;
- title = title ? title : view->world->className;
-
// Get refresh rate for resize draw timer
DEVMODEA devMode = {0};
EnumDisplaySettingsA(NULL, ENUM_CURRENT_SETTINGS, &devMode);
@@ -171,18 +179,21 @@ puglCreateWindow(PuglView* view, const char* title)
}
PuglStatus st = view->backend->configure(view);
- if (st || !impl->surface) {
+ if (st) {
return PUGL_SET_FORMAT_FAILED;
} else if ((st = view->backend->create(view))) {
return PUGL_CREATE_CONTEXT_FAILED;
}
- if (title) {
- puglSetWindowTitle(view, title);
+ if (view->title) {
+ puglSetWindowTitle(view, view->title);
}
+ puglSetFrame(view, view->frame);
SetWindowLongPtr(impl->hwnd, GWLP_USERDATA, (LONG_PTR)view);
+ puglDispatchSimpleEvent(view, PUGL_CREATE);
+
return PUGL_SUCCESS;
}
@@ -193,7 +204,6 @@ puglShowWindow(PuglView* view)
ShowWindow(impl->hwnd, SW_SHOWNORMAL);
SetFocus(impl->hwnd);
- view->visible = true;
return PUGL_SUCCESS;
}
@@ -203,7 +213,6 @@ puglHideWindow(PuglView* view)
PuglInternals* impl = view->impl;
ShowWindow(impl->hwnd, SW_HIDE);
- view->visible = false;
return PUGL_SUCCESS;
}
@@ -211,7 +220,10 @@ void
puglFreeViewInternals(PuglView* view)
{
if (view) {
- view->backend->destroy(view);
+ if (view->backend) {
+ view->backend->destroy(view);
+ }
+
ReleaseDC(view->impl->hwnd, view->impl->hdc);
DestroyWindow(view->impl->hwnd);
free(view->impl);
@@ -228,12 +240,8 @@ puglFreeWorldInternals(PuglWorld* world)
static PuglKey
keySymToSpecial(WPARAM sym)
{
+ // clang-format off
switch (sym) {
- case VK_BACK: return PUGL_KEY_BACKSPACE;
- case VK_TAB: return PUGL_KEY_TAB;
- case VK_RETURN: return PUGL_KEY_RETURN;
- case VK_ESCAPE: return PUGL_KEY_ESCAPE;
- case VK_DELETE: return PUGL_KEY_DELETE;
case VK_F1: return PUGL_KEY_F1;
case VK_F2: return PUGL_KEY_F2;
case VK_F3: return PUGL_KEY_F3;
@@ -246,6 +254,8 @@ keySymToSpecial(WPARAM sym)
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;
@@ -271,19 +281,22 @@ keySymToSpecial(WPARAM sym)
case VK_NUMLOCK: return PUGL_KEY_NUM_LOCK;
case VK_SNAPSHOT: return PUGL_KEY_PRINT_SCREEN;
case VK_PAUSE: return PUGL_KEY_PAUSE;
- default: break;
}
+ // clang-format on
+
return (PuglKey)0;
}
static uint32_t
getModifiers(void)
{
+ // clang-format off
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));
+ // clang-format on
}
static void
@@ -392,8 +405,9 @@ initKeyEvent(PuglEventKey* event,
// 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);
+
+ event->key = puglDecodeUTF16(
+ buf, ToUnicode(vkey, vcode, keyboardState, buf, 4, 1 << 2));
}
}
@@ -428,20 +442,23 @@ handleConfigure(PuglView* view, PuglEvent* event)
(LPPOINT)&rect,
2);
- view->frame.x = rect.left;
- view->frame.y = rect.top;
- view->frame.width = rect.right - rect.left;
- view->frame.height = rect.bottom - rect.top;
+ const LONG width = rect.right - rect.left;
+ const LONG height = rect.bottom - rect.top;
+
+ view->frame.x = rect.left;
+ view->frame.y = rect.top;
event->configure.type = PUGL_CONFIGURE;
event->configure.x = view->frame.x;
event->configure.y = view->frame.y;
- event->configure.width = view->frame.width;
- event->configure.height = view->frame.height;
+ event->configure.width = width;
+ event->configure.height = height;
+
+ if (view->frame.width != width || view->frame.height != height) {
+ view->frame.width = width;
+ view->frame.height = height;
+ }
- view->backend->resize(view,
- rect.right - rect.left,
- rect.bottom - rect.top);
return rect;
}
@@ -460,18 +477,10 @@ handleCrossing(PuglView* view, const PuglEventType type, POINT pos)
(double)root_pos.x,
(double)root_pos.y,
getModifiers(),
- PUGL_CROSSING_NORMAL
+ 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);
- }
+ puglDispatchEvent(view, (const PuglEvent*)&ev);
}
static void
@@ -531,12 +540,22 @@ handleMessage(PuglView* view, UINT message, WPARAM wParam, LPARAM lParam)
switch (message) {
case WM_SHOWWINDOW:
- rect = handleConfigure(view, &event);
- RedrawWindow(view->impl->hwnd, NULL, NULL,
- RDW_INVALIDATE|RDW_ALLCHILDREN|RDW_INTERNALPAINT);
+ if (wParam) {
+ handleConfigure(view, &event);
+ puglDispatchEvent(view, &event);
+ event.type = PUGL_NOTHING;
+
+ RedrawWindow(view->impl->hwnd, NULL, NULL,
+ RDW_INVALIDATE|RDW_ALLCHILDREN|RDW_INTERNALPAINT);
+ }
+
+ if ((bool)wParam != view->visible) {
+ view->visible = wParam;
+ event.any.type = wParam ? PUGL_MAP : PUGL_UNMAP;
+ }
break;
case WM_SIZE:
- rect = handleConfigure(view, &event);
+ handleConfigure(view, &event);
InvalidateRect(view->impl->hwnd, NULL, false);
break;
case WM_SIZING:
@@ -557,8 +576,9 @@ handleMessage(PuglView* view, UINT message, WPARAM wParam, LPARAM lParam)
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);
+ } else if (wParam >= PUGL_USER_TIMER_MIN) {
+ const PuglEventTimer ev = {PUGL_TIMER, 0, wParam - PUGL_USER_TIMER_MIN};
+ puglDispatchEvent(view, (const PuglEvent*)&ev);
}
break;
case WM_EXITSIZEMOVE:
@@ -589,18 +609,18 @@ handleMessage(PuglView* view, UINT message, WPARAM wParam, 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);
+ handleCrossing(view, PUGL_POINTER_IN, pt);
view->impl->mouseTracked = true;
}
ClientToScreen(view->impl->hwnd, &pt);
- event.motion.type = PUGL_MOTION_NOTIFY;
+ event.motion.type = PUGL_MOTION;
event.motion.time = GetMessageTime() / 1e3;
event.motion.x = GET_X_LPARAM(lParam);
event.motion.y = GET_Y_LPARAM(lParam);
@@ -612,7 +632,7 @@ handleMessage(PuglView* view, UINT message, WPARAM wParam, LPARAM lParam)
case WM_MOUSELEAVE:
GetCursorPos(&pt);
ScreenToClient(view->impl->hwnd, &pt);
- handleCrossing(view, PUGL_LEAVE_NOTIFY, pt);
+ handleCrossing(view, PUGL_POINTER_OUT, pt);
view->impl->mouseTracked = false;
break;
case WM_LBUTTONDOWN:
@@ -653,7 +673,6 @@ handleMessage(PuglView* view, UINT message, WPARAM wParam, LPARAM lParam)
initCharEvent(&event, view, wParam, lParam);
break;
case WM_SETFOCUS:
- stopFlashing(view);
event.type = PUGL_FOCUS_IN;
break;
case WM_KILLFOCUS:
@@ -667,9 +686,14 @@ handleMessage(PuglView* view, UINT message, WPARAM wParam, LPARAM lParam)
break;
case WM_SYSCHAR:
return TRUE;
+ case PUGL_LOCAL_CLIENT_MSG:
+ event.client.type = PUGL_CLIENT;
+ event.client.data1 = (uintptr_t)wParam;
+ event.client.data2 = (uintptr_t)lParam;
+ break;
case WM_QUIT:
case PUGL_LOCAL_CLOSE_MSG:
- event.close.type = PUGL_CLOSE;
+ event.any.type = PUGL_CLOSE;
break;
default:
return DefWindowProc(view->impl->hwnd, message, wParam, lParam);
@@ -696,43 +720,72 @@ puglHasFocus(const PuglView* view)
PuglStatus
puglRequestAttention(PuglView* view)
{
- if (!view->impl->mouseTracked || !puglHasFocus(view)) {
- FlashWindow(view->impl->hwnd, TRUE);
- SetTimer(view->impl->hwnd, PUGL_URGENT_TIMER_ID, 500, NULL);
- view->impl->flashing = true;
- }
+ FLASHWINFO info = {sizeof(FLASHWINFO),
+ view->impl->hwnd,
+ FLASHW_ALL|FLASHW_TIMERNOFG,
+ 1,
+ 0};
+
+ FlashWindowEx(&info);
return PUGL_SUCCESS;
}
PuglStatus
+puglStartTimer(PuglView* view, uintptr_t id, double timeout)
+{
+ const UINT msec = (UINT)floor(timeout * 1000.0);
+
+ return (SetTimer(view->impl->hwnd, PUGL_USER_TIMER_MIN + id, msec, NULL)
+ ? PUGL_SUCCESS
+ : PUGL_UNKNOWN_ERROR);
+}
+
+PuglStatus
+puglStopTimer(PuglView* view, uintptr_t id)
+{
+ return (KillTimer(view->impl->hwnd, PUGL_USER_TIMER_MIN + id)
+ ? PUGL_SUCCESS
+ : PUGL_UNKNOWN_ERROR);
+}
+
+PuglStatus
+puglSendEvent(PuglView* view, const PuglEvent* event)
+{
+ if (event->type == PUGL_CLIENT) {
+ PostMessage(view->impl->hwnd,
+ PUGL_LOCAL_CLIENT_MSG,
+ (WPARAM)event->client.data1,
+ (LPARAM)event->client.data2);
+
+ return PUGL_SUCCESS;
+ }
+
+ return PUGL_UNSUPPORTED_TYPE;
+}
+
+#ifndef PUGL_DISABLE_DEPRECATED
+PuglStatus
puglWaitForEvent(PuglView* PUGL_UNUSED(view))
{
WaitMessage();
return PUGL_SUCCESS;
}
+#endif
-PUGL_API PuglStatus
-puglDispatchEvents(PuglWorld* world)
+static PuglStatus
+puglDispatchViewEvents(PuglView* view)
{
- for (size_t i = 0; i < world->numViews; ++i) {
- if (world->views[i]->redisplay) {
- UpdateWindow(world->views[i]->impl->hwnd);
- world->views[i]->redisplay = false;
- }
- }
-
/* Windows has no facility to process only currently queued messages, which
causes the event loop to run forever in cases like mouse movement where
the queue is constantly being filled with new messages. To work around
this, we post a message to ourselves before starting, record its time
when it is received, then break the loop on the first message that was
created afterwards. */
- PostMessage(NULL, PUGL_LOCAL_MARK_MSG, 0, 0);
long markTime = 0;
MSG msg;
- while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) {
+ while (PeekMessage(&msg, view->impl->hwnd, 0, 0, PM_REMOVE)) {
if (msg.message == PUGL_LOCAL_MARK_MSG) {
markTime = GetMessageTime();
} else {
@@ -747,11 +800,59 @@ puglDispatchEvents(PuglWorld* world)
return PUGL_SUCCESS;
}
+static PuglStatus
+puglDispatchWinEvents(PuglWorld* world)
+{
+ for (size_t i = 0; i < world->numViews; ++i) {
+ PostMessage(world->views[i]->impl->hwnd, PUGL_LOCAL_MARK_MSG, 0, 0);
+ }
+
+ for (size_t i = 0; i < world->numViews; ++i) {
+ puglDispatchViewEvents(world->views[i]);
+ }
+
+ return PUGL_SUCCESS;
+}
+
+PuglStatus
+puglUpdate(PuglWorld* world, double timeout)
+{
+ const double startTime = puglGetTime(world);
+ PuglStatus st = PUGL_SUCCESS;
+
+ if (timeout < 0.0) {
+ st = puglPollWinEvents(world, timeout);
+ st = st ? st : puglDispatchWinEvents(world);
+ } else if (timeout == 0.0) {
+ st = puglDispatchWinEvents(world);
+ } else {
+ const double endTime = startTime + timeout - 0.001;
+ for (double t = startTime; t < endTime; t = puglGetTime(world)) {
+ if ((st = puglPollWinEvents(world, endTime - t)) ||
+ (st = puglDispatchWinEvents(world))) {
+ break;
+ }
+ }
+ }
+
+ for (size_t i = 0; i < world->numViews; ++i) {
+ if (world->views[i]->visible) {
+ puglDispatchSimpleEvent(world->views[i], PUGL_UPDATE);
+ }
+
+ UpdateWindow(world->views[i]->impl->hwnd);
+ }
+
+ return st;
+}
+
+#ifndef PUGL_DISABLE_DEPRECATED
PuglStatus
puglProcessEvents(PuglView* view)
{
- return puglDispatchEvents(view->world);
+ return puglUpdate(view->world, 0.0);
}
+#endif
LRESULT CALLBACK
wndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
@@ -789,14 +890,26 @@ PuglStatus
puglPostRedisplay(PuglView* view)
{
InvalidateRect(view->impl->hwnd, NULL, false);
- view->redisplay = true;
return PUGL_SUCCESS;
}
-PuglNativeWindow
+PuglStatus
+puglPostRedisplayRect(PuglView* view, const PuglRect rect)
+{
+ const RECT r = {(long)floor(rect.x),
+ (long)floor(rect.y),
+ (long)ceil(rect.x + rect.width),
+ (long)ceil(rect.y + rect.height)};
+
+ InvalidateRect(view->impl->hwnd, &r, false);
+
+ return PUGL_SUCCESS;
+}
+
+PuglNativeView
puglGetNativeWindow(PuglView* view)
{
- return (PuglNativeWindow)view->impl->hwnd;
+ return (PuglNativeView)view->impl->hwnd;
}
PuglStatus
@@ -804,10 +917,12 @@ puglSetWindowTitle(PuglView* view, const char* title)
{
puglSetString(&view->title, title);
- wchar_t* wtitle = puglUtf8ToWideChar(title);
- if (wtitle) {
- SetWindowTextW(view->impl->hwnd, wtitle);
- free(wtitle);
+ if (view->impl->hwnd) {
+ wchar_t* wtitle = puglUtf8ToWideChar(title);
+ if (wtitle) {
+ SetWindowTextW(view->impl->hwnd, wtitle);
+ free(wtitle);
+ }
}
return PUGL_SUCCESS;
@@ -828,10 +943,13 @@ puglSetFrame(PuglView* view, const PuglRect frame)
FALSE,
puglWinGetWindowExFlags(view));
- if (!SetWindowPos(view->impl->hwnd, HWND_TOP,
- rect.left, rect.top,
- rect.right - rect.left, rect.bottom - rect.top,
- (SWP_NOACTIVATE|SWP_NOOWNERZORDER|SWP_NOZORDER))) {
+ if (!SetWindowPos(view->impl->hwnd,
+ HWND_TOP,
+ rect.left,
+ rect.top,
+ rect.right - rect.left,
+ rect.bottom - rect.top,
+ SWP_NOACTIVATE | SWP_NOOWNERZORDER | SWP_NOZORDER)) {
return PUGL_UNKNOWN_ERROR;
}
}
@@ -928,3 +1046,39 @@ puglSetClipboard(PuglView* const view,
CloseClipboard();
return PUGL_SUCCESS;
}
+
+static PuglStatus
+puglWinStubEnter(PuglView* view, const PuglEventExpose* expose)
+{
+ if (expose) {
+ PAINTSTRUCT ps;
+ BeginPaint(view->impl->hwnd, &ps);
+ }
+
+ return PUGL_SUCCESS;
+}
+
+static PuglStatus
+puglWinStubLeave(PuglView* view, const PuglEventExpose* expose)
+{
+ if (expose) {
+ PAINTSTRUCT ps;
+ EndPaint(view->impl->hwnd, &ps);
+ SwapBuffers(view->impl->hdc);
+ }
+
+ return PUGL_SUCCESS;
+}
+
+const PuglBackend*
+puglStubBackend(void)
+{
+ static const PuglBackend backend = {puglWinStubConfigure,
+ puglStubCreate,
+ puglStubDestroy,
+ puglWinStubEnter,
+ puglWinStubLeave,
+ puglStubGetContext};
+
+ return &backend;
+}
diff --git a/pugl/pugl/detail/win.h b/pugl/pugl/detail/win.h
index 6d89759..949fa90 100644
--- a/pugl/pugl/detail/win.h
+++ b/pugl/pugl/detail/win.h
@@ -45,16 +45,17 @@ struct PuglInternalsImpl {
static inline PuglWinPFD
puglWinGetPixelFormatDescriptor(const PuglHints hints)
{
- const int rgbBits = (hints[PUGL_RED_BITS] +
- hints[PUGL_GREEN_BITS] +
+ const int rgbBits = (hints[PUGL_RED_BITS] + //
+ hints[PUGL_GREEN_BITS] + //
hints[PUGL_BLUE_BITS]);
+ const DWORD dwFlags = hints[PUGL_DOUBLE_BUFFER] ? PFD_DOUBLEBUFFER : 0u;
+
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[PUGL_DOUBLE_BUFFER] ? PFD_DOUBLEBUFFER : 0;
+ pfd.dwFlags = PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | dwFlags;
pfd.iPixelType = PFD_TYPE_RGBA;
pfd.cColorBits = (BYTE)rgbBits;
pfd.cRedBits = (BYTE)hints[PUGL_RED_BITS];
@@ -70,12 +71,13 @@ puglWinGetPixelFormatDescriptor(const PuglHints hints)
static inline unsigned
puglWinGetWindowFlags(const PuglView* const view)
{
- const bool resizable = view->hints[PUGL_RESIZABLE];
+ const bool resizable = view->hints[PUGL_RESIZABLE];
+ const unsigned sizeFlags = resizable ? (WS_SIZEBOX | WS_MAXIMIZEBOX) : 0u;
+
return (WS_CLIPCHILDREN | WS_CLIPSIBLINGS |
(view->parent
- ? WS_CHILD
- : (WS_POPUPWINDOW | WS_CAPTION | WS_MINIMIZEBOX |
- (resizable ? (WS_SIZEBOX | WS_MAXIMIZEBOX) : 0))));
+ ? WS_CHILD
+ : (WS_POPUPWINDOW | WS_CAPTION | WS_MINIMIZEBOX | sizeFlags)));
}
static inline unsigned
@@ -104,11 +106,35 @@ puglWinCreateWindow(const PuglView* const view,
CW_USEDEFAULT, CW_USEDEFAULT,
wr.right-wr.left, wr.bottom-wr.top,
(HWND)view->parent, NULL, NULL, NULL))) {
- return PUGL_CREATE_WINDOW_FAILED;
+ return PUGL_REALIZE_FAILED;
} else if (!(*hdc = GetDC(*hwnd))) {
DestroyWindow(*hwnd);
*hwnd = NULL;
- return PUGL_CREATE_WINDOW_FAILED;
+ return PUGL_REALIZE_FAILED;
+ }
+
+ return PUGL_SUCCESS;
+}
+
+static inline PuglStatus
+puglWinStubConfigure(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_SET_FORMAT_FAILED;
}
return PUGL_SUCCESS;
diff --git a/pugl/pugl/detail/win_cairo.c b/pugl/pugl/detail/win_cairo.c
index 497711b..a8b371f 100644
--- a/pugl/pugl/detail/win_cairo.c
+++ b/pugl/pugl/detail/win_cairo.c
@@ -1,5 +1,5 @@
/*
- Copyright 2012-2019 David Robillard <http://drobilla.net>
+ Copyright 2012-2020 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
@@ -20,7 +20,8 @@
#include "pugl/detail/types.h"
#include "pugl/detail/win.h"
-#include "pugl/pugl_cairo_backend.h"
+#include "pugl/pugl_cairo.h"
+#include "pugl/pugl_stub.h"
#include <cairo-win32.h>
#include <cairo.h>
@@ -46,15 +47,6 @@ puglWinCairoCreateDrawContext(PuglView* view)
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_CREATE_CONTEXT_FAILED;
- }
-
- cairo_save(surface->cr);
return PUGL_SUCCESS;
}
@@ -66,11 +58,7 @@ puglWinCairoDestroyDrawContext(PuglView* view)
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;
@@ -80,34 +68,42 @@ puglWinCairoDestroyDrawContext(PuglView* view)
static PuglStatus
puglWinCairoConfigure(PuglView* view)
{
- PuglInternals* const impl = view->impl;
- PuglStatus st = PUGL_SUCCESS;
+ const PuglStatus st = puglWinStubConfigure(view);
- if ((st = puglWinCreateWindow(view, "Pugl", &impl->hwnd, &impl->hdc))) {
- return st;
+ if (!st) {
+ view->impl->surface = (PuglWinCairoSurface*)calloc(
+ 1, sizeof(PuglWinCairoSurface));
}
- impl->pfd = puglWinGetPixelFormatDescriptor(view->hints);
- impl->pfId = ChoosePixelFormat(impl->hdc, &impl->pfd);
+ return st;
+}
- if (!SetPixelFormat(impl->hdc, impl->pfId, &impl->pfd)) {
- ReleaseDC(impl->hwnd, impl->hdc);
- DestroyWindow(impl->hwnd);
- impl->hwnd = NULL;
- impl->hdc = NULL;
- return PUGL_SET_FORMAT_FAILED;
- }
+static void
+puglWinCairoClose(PuglView* view)
+{
+ PuglInternals* const impl = view->impl;
+ PuglWinCairoSurface* const surface = (PuglWinCairoSurface*)impl->surface;
- impl->surface = (PuglWinCairoSurface*)calloc(
- 1, sizeof(PuglWinCairoSurface));
+ cairo_surface_destroy(surface->surface);
- return PUGL_SUCCESS;
+ surface->surface = NULL;
}
static PuglStatus
-puglWinCairoCreate(PuglView* view)
+puglWinCairoOpen(PuglView* view)
{
- return puglWinCairoCreateDrawContext(view);
+ PuglInternals* const impl = view->impl;
+ PuglWinCairoSurface* const surface = (PuglWinCairoSurface*)impl->surface;
+
+ 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_CREATE_CONTEXT_FAILED;
+ }
+
+ return PUGL_SUCCESS;
}
static PuglStatus
@@ -116,6 +112,7 @@ puglWinCairoDestroy(PuglView* view)
PuglInternals* const impl = view->impl;
PuglWinCairoSurface* const surface = (PuglWinCairoSurface*)impl->surface;
+ puglWinCairoClose(view);
puglWinCairoDestroyDrawContext(view);
free(surface);
impl->surface = NULL;
@@ -124,52 +121,38 @@ puglWinCairoDestroy(PuglView* view)
}
static PuglStatus
-puglWinCairoEnter(PuglView* view, bool drawing)
+puglWinCairoEnter(PuglView* view, const PuglEventExpose* expose)
{
- PuglInternals* const impl = view->impl;
- PuglWinCairoSurface* const surface = (PuglWinCairoSurface*)impl->surface;
- if (!drawing) {
- return PUGL_SUCCESS;
- }
+ PuglStatus st = PUGL_SUCCESS;
- PAINTSTRUCT ps;
- BeginPaint(view->impl->hwnd, &ps);
- cairo_save(surface->cr);
+ if (expose &&
+ !(st = puglWinCairoCreateDrawContext(view)) &&
+ !(st = puglWinCairoOpen(view))) {
+ PAINTSTRUCT ps;
+ BeginPaint(view->impl->hwnd, &ps);
+ }
- return PUGL_SUCCESS;
+ return st;
}
static PuglStatus
-puglWinCairoLeave(PuglView* view, bool drawing)
+puglWinCairoLeave(PuglView* view, const PuglEventExpose* expose)
{
PuglInternals* const impl = view->impl;
PuglWinCairoSurface* const surface = (PuglWinCairoSurface*)impl->surface;
- if (!drawing) {
- return PUGL_SUCCESS;
- }
-
- cairo_restore(surface->cr);
- cairo_surface_flush(surface->surface);
- BitBlt(impl->hdc,
- 0, 0, (int)view->frame.width, (int)view->frame.height,
- surface->drawDc, 0, 0, SRCCOPY);
- PAINTSTRUCT ps;
- EndPaint(view->impl->hwnd, &ps);
- SwapBuffers(view->impl->hdc);
+ if (expose) {
+ cairo_surface_flush(surface->surface);
+ BitBlt(impl->hdc,
+ 0, 0, (int)view->frame.width, (int)view->frame.height,
+ surface->drawDc, 0, 0, SRCCOPY);
- return PUGL_SUCCESS;
-}
+ puglWinCairoClose(view);
+ puglWinCairoDestroyDrawContext(view);
-static PuglStatus
-puglWinCairoResize(PuglView* view,
- int PUGL_UNUSED(width),
- int PUGL_UNUSED(height))
-{
- PuglStatus st = PUGL_SUCCESS;
- if ((st = puglWinCairoDestroyDrawContext(view)) ||
- (st = puglWinCairoCreateDrawContext(view))) {
- return st;
+ PAINTSTRUCT ps;
+ EndPaint(view->impl->hwnd, &ps);
+ SwapBuffers(view->impl->hdc);
}
return PUGL_SUCCESS;
@@ -184,15 +167,12 @@ puglWinCairoGetContext(PuglView* view)
const PuglBackend*
puglCairoBackend()
{
- static const PuglBackend backend = {
- puglWinCairoConfigure,
- puglWinCairoCreate,
- puglWinCairoDestroy,
- puglWinCairoEnter,
- puglWinCairoLeave,
- puglWinCairoResize,
- puglWinCairoGetContext
- };
+ static const PuglBackend backend = {puglWinCairoConfigure,
+ puglStubCreate,
+ puglWinCairoDestroy,
+ puglWinCairoEnter,
+ puglWinCairoLeave,
+ puglWinCairoGetContext};
return &backend;
}
diff --git a/pugl/pugl/detail/win_gl.c b/pugl/pugl/detail/win_gl.c
index 4cbb796..f5acfd6 100644
--- a/pugl/pugl/detail/win_gl.c
+++ b/pugl/pugl/detail/win_gl.c
@@ -20,7 +20,8 @@
#include "pugl/detail/types.h"
#include "pugl/detail/win.h"
-#include "pugl/pugl_gl_backend.h"
+#include "pugl/pugl_gl.h"
+#include "pugl/pugl_stub.h"
#include <windows.h>
@@ -108,6 +109,7 @@ puglWinGlConfigure(PuglView* view)
{
PuglInternals* impl = view->impl;
+ // clang-format off
const int pixelAttrs[] = {
WGL_DRAW_TO_WINDOW_ARB, GL_TRUE,
WGL_ACCELERATION_ARB, WGL_FULL_ACCELERATION_ARB,
@@ -124,16 +126,17 @@ puglWinGlConfigure(PuglView* view)
WGL_STENCIL_BITS_ARB, view->hints[PUGL_STENCIL_BITS],
0,
};
+ // clang-format on
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))) {
+ PuglStatus st = PUGL_SUCCESS;
+ PuglFakeWindow fakeWin = {0, 0};
+ static const char* title = "Pugl Configuration";
+ if ((st = puglWinCreateWindow(view, title, &fakeWin.hwnd, &fakeWin.hdc))) {
return puglWinError(&fakeWin, st);
}
@@ -189,16 +192,21 @@ puglWinGlCreate(PuglView* view)
PuglStatus st = PUGL_SUCCESS;
const int contextAttribs[] = {
- WGL_CONTEXT_MAJOR_VERSION_ARB, view->hints[PUGL_CONTEXT_VERSION_MAJOR],
- WGL_CONTEXT_MINOR_VERSION_ARB, view->hints[PUGL_CONTEXT_VERSION_MINOR],
- WGL_CONTEXT_FLAGS_ARB, (view->hints[PUGL_USE_DEBUG_CONTEXT]
- ? WGL_CONTEXT_DEBUG_BIT_ARB
- : 0),
- (view->hints[PUGL_USE_COMPAT_PROFILE]
- ? WGL_CONTEXT_CORE_PROFILE_BIT_ARB
- : WGL_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB),
- 0
- };
+ WGL_CONTEXT_MAJOR_VERSION_ARB,
+ view->hints[PUGL_CONTEXT_VERSION_MAJOR],
+
+ WGL_CONTEXT_MINOR_VERSION_ARB,
+ view->hints[PUGL_CONTEXT_VERSION_MINOR],
+
+ WGL_CONTEXT_FLAGS_ARB,
+ (view->hints[PUGL_USE_DEBUG_CONTEXT] ? WGL_CONTEXT_DEBUG_BIT_ARB : 0),
+
+ WGL_CONTEXT_PROFILE_MASK_ARB,
+ (view->hints[PUGL_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))) {
@@ -222,8 +230,9 @@ puglWinGlCreate(PuglView* view)
// Enter context and set swap interval
wglMakeCurrent(impl->hdc, surface->hglrc);
- if (surface->procs.wglSwapInterval) {
- surface->procs.wglSwapInterval(view->hints[PUGL_SWAP_INTERVAL]);
+ const int swapInterval = view->hints[PUGL_SWAP_INTERVAL];
+ if (surface->procs.wglSwapInterval && swapInterval != PUGL_DONT_CARE) {
+ surface->procs.wglSwapInterval(swapInterval);
}
return PUGL_SUCCESS;
@@ -244,13 +253,13 @@ puglWinGlDestroy(PuglView* view)
}
static PuglStatus
-puglWinGlEnter(PuglView* view, bool drawing)
+puglWinGlEnter(PuglView* view, const PuglEventExpose* expose)
{
PuglWinGlSurface* surface = (PuglWinGlSurface*)view->impl->surface;
wglMakeCurrent(view->impl->hdc, surface->hglrc);
- if (drawing) {
+ if (expose) {
PAINTSTRUCT ps;
BeginPaint(view->impl->hwnd, &ps);
}
@@ -259,9 +268,9 @@ puglWinGlEnter(PuglView* view, bool drawing)
}
static PuglStatus
-puglWinGlLeave(PuglView* view, bool drawing)
+puglWinGlLeave(PuglView* view, const PuglEventExpose* expose)
{
- if (drawing) {
+ if (expose) {
PAINTSTRUCT ps;
EndPaint(view->impl->hwnd, &ps);
SwapBuffers(view->impl->hdc);
@@ -271,20 +280,6 @@ puglWinGlLeave(PuglView* view, bool drawing)
return PUGL_SUCCESS;
}
-static PuglStatus
-puglWinGlResize(PuglView* PUGL_UNUSED(view),
- int PUGL_UNUSED(width),
- int PUGL_UNUSED(height))
-{
- return PUGL_SUCCESS;
-}
-
-static void*
-puglWinGlGetContext(PuglView* PUGL_UNUSED(view))
-{
- return NULL;
-}
-
PuglGlFunc
puglGetProcAddress(const char* name)
{
@@ -302,15 +297,12 @@ puglGetProcAddress(const char* name)
const PuglBackend*
puglGlBackend()
{
- static const PuglBackend backend = {
- puglWinGlConfigure,
- puglWinGlCreate,
- puglWinGlDestroy,
- puglWinGlEnter,
- puglWinGlLeave,
- puglWinGlResize,
- puglWinGlGetContext
- };
+ static const PuglBackend backend = {puglWinGlConfigure,
+ puglWinGlCreate,
+ puglWinGlDestroy,
+ puglWinGlEnter,
+ puglWinGlLeave,
+ puglStubGetContext};
return &backend;
}
diff --git a/pugl/pugl/detail/x11.c b/pugl/pugl/detail/x11.c
index cc84bc0..e3fb264 100644
--- a/pugl/pugl/detail/x11.c
+++ b/pugl/pugl/detail/x11.c
@@ -1,5 +1,5 @@
/*
- Copyright 2012-2019 David Robillard <http://drobilla.net>
+ Copyright 2012-2020 David Robillard <http://drobilla.net>
Copyright 2013 Robin Gareus <robin@gareus.org>
Copyright 2011-2012 Ben Loftis, Harrison Consoles
@@ -22,10 +22,12 @@
#define _POSIX_C_SOURCE 199309L
+#include "pugl/detail/x11.h"
+
#include "pugl/detail/implementation.h"
#include "pugl/detail/types.h"
-#include "pugl/detail/x11.h"
#include "pugl/pugl.h"
+#include "pugl/pugl_stub.h"
#include <X11/X.h>
#include <X11/Xatom.h>
@@ -33,13 +35,17 @@
#include <X11/Xutil.h>
#include <X11/keysym.h>
+#ifdef HAVE_XSYNC
+# include <X11/extensions/sync.h>
+# include <X11/extensions/syncconst.h>
+#endif
+
#include <sys/select.h>
#include <sys/time.h>
-#include <limits.h>
+#include <math.h>
#include <stdbool.h>
#include <stdint.h>
-#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
@@ -59,13 +65,49 @@ enum WmClientStateMessageAction {
};
static const long eventMask =
- (ExposureMask | StructureNotifyMask | FocusChangeMask |
+ (ExposureMask | StructureNotifyMask |
+ VisibilityChangeMask | FocusChangeMask |
EnterWindowMask | LeaveWindowMask | PointerMotionMask |
ButtonPressMask | ButtonReleaseMask | KeyPressMask | KeyReleaseMask);
+static bool
+puglInitXSync(PuglWorldInternals* impl)
+{
+#ifdef HAVE_XSYNC
+ int syncMajor;
+ int syncMinor;
+ int errorBase;
+ XSyncSystemCounter* counters;
+ int numCounters;
+
+ if (XSyncQueryExtension(impl->display, &impl->syncEventBase, &errorBase) &&
+ XSyncInitialize(impl->display, &syncMajor, &syncMinor) &&
+ (counters = XSyncListSystemCounters(impl->display, &numCounters))) {
+
+ for (int n = 0; n < numCounters; ++n) {
+ if (!strcmp(counters[n].name, "SERVERTIME")) {
+ impl->serverTimeCounter = counters[n].counter;
+ impl->syncSupported = true;
+ break;
+ }
+ }
+
+ XSyncFreeSystemCounterList(counters);
+ }
+#else
+ (void)impl;
+#endif
+
+ return false;
+}
+
PuglWorldInternals*
-puglInitWorldInternals(void)
+puglInitWorldInternals(PuglWorldType type, PuglWorldFlags flags)
{
+ if (type == PUGL_PROGRAM && (flags & PUGL_WORLD_THREADS)) {
+ XInitThreads();
+ }
+
Display* display = XOpenDisplay(NULL);
if (!display) {
return NULL;
@@ -81,6 +123,7 @@ puglInitWorldInternals(void)
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.PUGL_CLIENT_MSG = XInternAtom(display, "_PUGL_CLIENT_MSG", 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 =
@@ -90,45 +133,51 @@ puglInitWorldInternals(void)
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");
- }
+ impl->xim = XOpenIM(display, NULL, NULL, NULL);
}
+ puglInitXSync(impl);
XFlush(display);
return impl;
}
+void*
+puglGetNativeWorld(PuglWorld* world)
+{
+ return world->impl->display;
+}
+
PuglInternals*
puglInitViewInternals(void)
{
return (PuglInternals*)calloc(1, sizeof(PuglInternals));
}
-PuglStatus
-puglPollEvents(PuglWorld* world, const double timeout)
+static PuglStatus
+puglPollX11Socket(PuglWorld* world, const double timeout)
{
- XFlush(world->impl->display);
+ if (XPending(world->impl->display) > 0) {
+ return PUGL_SUCCESS;
+ }
const int fd = ConnectionNumber(world->impl->display);
const int nfds = fd + 1;
int ret = 0;
fd_set fds;
- FD_ZERO(&fds);
+ FD_ZERO(&fds); // NOLINT
FD_SET(fd, &fds);
if (timeout < 0.0) {
ret = select(nfds, &fds, NULL, NULL, NULL);
} else {
const long sec = (long)timeout;
- const long msec = (long)((timeout - (double)sec) * 1e6);
- struct timeval tv = {sec, msec};
+ const long usec = (long)((timeout - (double)sec) * 1e6);
+ struct timeval tv = {sec, usec};
ret = select(nfds, &fds, NULL, NULL, &tv);
}
- return ret < 0 ? PUGL_UNKNOWN_ERROR
- : ret == 0 ? PUGL_FAILURE : PUGL_SUCCESS;
+ return ret < 0 ? PUGL_UNKNOWN_ERROR : PUGL_SUCCESS;
}
static PuglView*
@@ -173,14 +222,12 @@ getSizeHints(const PuglView* view)
}
PuglStatus
-puglCreateWindow(PuglView* view, const char* title)
+puglRealize(PuglView* view)
{
PuglInternals* const impl = view->impl;
PuglWorld* const world = view->world;
PuglX11Atoms* const atoms = &view->world->impl->atoms;
Display* const display = world->impl->display;
- const int width = (int)view->frame.width;
- const int height = (int)view->frame.height;
impl->display = display;
impl->screen = DefaultScreen(display);
@@ -207,7 +254,8 @@ puglCreateWindow(PuglView* view, const char* title)
const Window win = impl->win = XCreateWindow(
display, xParent,
- (int)view->frame.x, (int)view->frame.y, width, height,
+ (int)view->frame.x, (int)view->frame.y,
+ (unsigned)view->frame.width, (unsigned)view->frame.height,
0, impl->vi->depth, InputOutput,
impl->vi->visual, CWColormap | CWEventMask, &attr);
@@ -221,8 +269,8 @@ puglCreateWindow(PuglView* view, const char* title)
XClassHint classHint = { world->className, world->className };
XSetClassHint(display, win, &classHint);
- if (title) {
- puglSetWindowTitle(view, title);
+ if (view->title) {
+ puglSetWindowTitle(view, view->title);
}
if (!view->parent) {
@@ -240,38 +288,53 @@ puglCreateWindow(PuglView* view, const char* title)
XNClientWindow, win,
XNFocusWindow, win,
NULL))) {
- fprintf(stderr, "warning: XCreateIC failed\n");
+ view->world->logFunc(view->world,
+ PUGL_LOG_LEVEL_WARNING,
+ "XCreateID failed\n");
}
+ puglDispatchSimpleEvent(view, PUGL_CREATE);
+
return PUGL_SUCCESS;
}
PuglStatus
puglShowWindow(PuglView* view)
{
+ PuglStatus st = PUGL_SUCCESS;
+
+ if (!view->impl->win) {
+ if ((st = puglRealize(view))) {
+ return st;
+ }
+ }
+
XMapRaised(view->impl->display, view->impl->win);
puglPostRedisplay(view);
- view->visible = true;
- return PUGL_SUCCESS;
+
+ return st;
}
PuglStatus
puglHideWindow(PuglView* view)
{
XUnmapWindow(view->impl->display, view->impl->win);
- view->visible = false;
return PUGL_SUCCESS;
}
void
puglFreeViewInternals(PuglView* view)
{
- if (view) {
+ if (view && view->impl) {
if (view->impl->xic) {
XDestroyIC(view->impl->xic);
}
- view->backend->destroy(view);
- XDestroyWindow(view->impl->display, view->impl->win);
+ if (view->backend) {
+ view->backend->destroy(view);
+ }
+ if (view->impl->display) {
+ XDestroyWindow(view->impl->display, view->impl->win);
+ }
XFree(view->impl->vi);
free(view->impl);
}
@@ -284,6 +347,7 @@ puglFreeWorldInternals(PuglWorld* world)
XCloseIM(world->impl->xim);
}
XCloseDisplay(world->impl->display);
+ free(world->impl->timers);
free(world->impl);
}
@@ -291,11 +355,6 @@ static PuglKey
keySymToSpecial(KeySym sym)
{
switch (sym) {
- case XK_BackSpace: return PUGL_KEY_BACKSPACE;
- case XK_Tab: return PUGL_KEY_TAB;
- case XK_Return: return PUGL_KEY_RETURN;
- case XK_Escape: return PUGL_KEY_ESCAPE;
- case XK_Delete: return PUGL_KEY_DELETE;
case XK_F1: return PUGL_KEY_F1;
case XK_F2: return PUGL_KEY_F2;
case XK_F3: return PUGL_KEY_F3;
@@ -332,7 +391,7 @@ keySymToSpecial(KeySym sym)
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;
+ default: break;
}
return (PuglKey)0;
}
@@ -402,7 +461,7 @@ translateEvent(PuglView* view, XEvent xevent)
{
const PuglX11Atoms* atoms = &view->world->impl->atoms;
- PuglEvent event = {0};
+ PuglEvent event = {{PUGL_NOTHING, 0}};
event.any.flags = xevent.xany.send_event ? PUGL_IS_SEND_EVENT : 0;
switch (xevent.type) {
@@ -412,18 +471,22 @@ translateEvent(PuglView* view, XEvent xevent)
if (protocol == atoms->WM_DELETE_WINDOW) {
event.type = PUGL_CLOSE;
}
+ } else if (xevent.xclient.message_type == atoms->PUGL_CLIENT_MSG) {
+ event.type = PUGL_CLIENT;
+ event.client.data1 = (uintptr_t)xevent.xclient.data.l[0];
+ event.client.data2 = (uintptr_t)xevent.xclient.data.l[1];
}
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;
+ case VisibilityNotify:
+ view->visible = xevent.xvisibility.state != VisibilityFullyObscured;
+ break;
+ case MapNotify:
+ event.type = PUGL_MAP;
+ break;
+ case UnmapNotify:
+ event.type = PUGL_UNMAP;
+ view->visible = false;
break;
- }
case ConfigureNotify:
event.type = PUGL_CONFIGURE;
event.configure.x = xevent.xconfigure.x;
@@ -440,7 +503,7 @@ translateEvent(PuglView* view, XEvent xevent)
event.expose.count = xevent.xexpose.count;
break;
case MotionNotify:
- event.type = PUGL_MOTION_NOTIFY;
+ event.type = PUGL_MOTION;
event.motion.time = xevent.xmotion.time / 1e3;
event.motion.x = xevent.xmotion.x;
event.motion.y = xevent.xmotion.y;
@@ -499,8 +562,8 @@ translateEvent(PuglView* view, XEvent xevent)
case EnterNotify:
case LeaveNotify:
event.type = ((xevent.type == EnterNotify)
- ? PUGL_ENTER_NOTIFY
- : PUGL_LEAVE_NOTIFY);
+ ? PUGL_POINTER_IN
+ : PUGL_POINTER_OUT);
event.crossing.time = xevent.xcrossing.time / 1e3;
event.crossing.x = xevent.xcrossing.x;
event.crossing.y = xevent.xcrossing.y;
@@ -557,7 +620,7 @@ puglRequestAttention(PuglView* view)
event.xclient.format = 32;
event.xclient.message_type = atoms->NET_WM_STATE;
event.xclient.data.l[0] = WM_STATE_ADD;
- event.xclient.data.l[1] = atoms->NET_WM_STATE_DEMANDS_ATTENTION;
+ event.xclient.data.l[1] = (long)atoms->NET_WM_STATE_DEMANDS_ATTENTION;
event.xclient.data.l[2] = 0;
event.xclient.data.l[3] = 1;
event.xclient.data.l[4] = 0;
@@ -573,15 +636,148 @@ puglRequestAttention(PuglView* view)
}
PuglStatus
+puglStartTimer(PuglView* view, uintptr_t id, double timeout)
+{
+#ifdef HAVE_XSYNC
+ if (view->world->impl->syncSupported) {
+ XSyncValue value;
+ XSyncIntToValue(&value, (int)floor(timeout * 1000.0));
+
+ PuglWorldInternals* w = view->world->impl;
+ Display* const display = w->display;
+ const XSyncCounter counter = w->serverTimeCounter;
+ const XSyncTrigger trigger = {counter, XSyncRelative, value, 0};
+ XSyncAlarmAttributes attr = {trigger, value, True, XSyncAlarmActive};
+ const XSyncAlarm alarm = XSyncCreateAlarm(display, 0x17, &attr);
+ const PuglTimer timer = {alarm, view, id};
+
+ if (alarm != None) {
+ for (size_t i = 0; i < w->numTimers; ++i) {
+ if (w->timers[i].view == view && w->timers[i].id == id) {
+ // Replace existing timer
+ XSyncDestroyAlarm(w->display, w->timers[i].alarm);
+ w->timers[i] = timer;
+ return PUGL_SUCCESS;
+ }
+ }
+
+ // Add new timer
+ const size_t size = ++w->numTimers * sizeof(timer);
+ w->timers = (PuglTimer*)realloc(w->timers, size);
+ w->timers[w->numTimers - 1] = timer;
+ return PUGL_SUCCESS;
+ }
+ }
+#else
+ (void)view;
+ (void)id;
+ (void)timeout;
+#endif
+
+ return PUGL_FAILURE;
+}
+
+PuglStatus
+puglStopTimer(PuglView* view, uintptr_t id)
+{
+#ifdef HAVE_XSYNC
+ PuglWorldInternals* w = view->world->impl;
+
+ for (size_t i = 0; i < w->numTimers; ++i) {
+ if (w->timers[i].view == view && w->timers[i].id == id) {
+ XSyncDestroyAlarm(w->display, w->timers[i].alarm);
+
+ if (i == w->numTimers - 1) {
+ memset(&w->timers[i], 0, sizeof(PuglTimer));
+ } else {
+ memmove(w->timers + i,
+ w->timers + i + 1,
+ sizeof(PuglTimer) * (w->numTimers - i - 1));
+
+ memset(&w->timers[i], 0, sizeof(PuglTimer));
+ }
+
+ --w->numTimers;
+ return PUGL_SUCCESS;
+ }
+ }
+#else
+ (void)view;
+ (void)id;
+#endif
+
+ return PUGL_FAILURE;
+}
+
+static XEvent
+puglEventToX(PuglView* view, const PuglEvent* event)
+{
+ XEvent xev = {0};
+ xev.xany.send_event = True;
+
+ switch (event->type) {
+ case PUGL_EXPOSE: {
+ const double x = floor(event->expose.x);
+ const double y = floor(event->expose.y);
+ const double w = ceil(event->expose.x + event->expose.width) - x;
+ const double h = ceil(event->expose.y + event->expose.height) - y;
+
+ xev.xexpose.type = Expose;
+ xev.xexpose.serial = 0;
+ xev.xexpose.display = view->impl->display;
+ xev.xexpose.window = view->impl->win;
+ xev.xexpose.x = (int)x;
+ xev.xexpose.y = (int)y;
+ xev.xexpose.width = (int)w;
+ xev.xexpose.height = (int)h;
+ break;
+ }
+
+ case PUGL_CLIENT:
+ xev.xclient.type = ClientMessage;
+ xev.xclient.serial = 0;
+ xev.xclient.send_event = True;
+ xev.xclient.display = view->impl->display;
+ xev.xclient.window = view->impl->win;
+ xev.xclient.message_type = view->world->impl->atoms.PUGL_CLIENT_MSG;
+ xev.xclient.format = 32;
+ xev.xclient.data.l[0] = (long)event->client.data1;
+ xev.xclient.data.l[1] = (long)event->client.data2;
+ break;
+
+ default:
+ break;
+ }
+
+ return xev;
+}
+
+PuglStatus
+puglSendEvent(PuglView* view, const PuglEvent* event)
+{
+ XEvent xev = puglEventToX(view, event);
+
+ if (xev.type) {
+ if (XSendEvent(view->impl->display, view->impl->win, False, 0, &xev)) {
+ return PUGL_SUCCESS;
+ }
+ }
+
+ return PUGL_UNSUPPORTED_TYPE;
+}
+
+#ifndef PUGL_DISABLE_DEPRECATED
+PuglStatus
puglWaitForEvent(PuglView* view)
{
XEvent xevent;
XPeekEvent(view->impl->display, &xevent);
return PUGL_SUCCESS;
}
+#endif
static void
-merge_expose_events(PuglEvent* dst, const PuglEvent* src)
+mergeExposeEvents(PuglEvent* dst, const PuglEvent* src)
{
if (!dst->type) {
*dst = *src;
@@ -599,20 +795,140 @@ merge_expose_events(PuglEvent* dst, const PuglEvent* src)
}
}
-PUGL_API PuglStatus
-puglDispatchEvents(PuglWorld* world)
+static void
+handleSelectionNotify(const PuglWorld* world, PuglView* view)
+{
+ uint8_t* str = NULL;
+ Atom type = 0;
+ int fmt = 0;
+ unsigned long len = 0;
+ unsigned long left = 0;
+
+ XGetWindowProperty(world->impl->display,
+ view->impl->win,
+ XA_PRIMARY,
+ 0,
+ 0x1FFFFFFF,
+ False,
+ AnyPropertyType,
+ &type,
+ &fmt,
+ &len,
+ &left,
+ &str);
+
+ if (str && fmt == 8 && type == world->impl->atoms.UTF8_STRING &&
+ left == 0) {
+ puglSetBlob(&view->clipboard, str, len);
+ }
+
+ XFree(str);
+}
+
+static void
+handleSelectionRequest(const PuglWorld* world,
+ PuglView* view,
+ const XSelectionRequestEvent* request)
+{
+ XSelectionEvent note = {SelectionNotify,
+ request->serial,
+ False,
+ world->impl->display,
+ request->requestor,
+ request->selection,
+ request->target,
+ None,
+ request->time};
+
+ const char* type = NULL;
+ size_t len = 0;
+ const void* data = puglGetInternalClipboard(view, &type, &len);
+ if (data && request->selection == world->impl->atoms.CLIPBOARD &&
+ request->target == world->impl->atoms.UTF8_STRING) {
+ note.property = request->property;
+ XChangeProperty(world->impl->display,
+ note.requestor,
+ note.property,
+ note.target,
+ 8,
+ PropModeReplace,
+ (const uint8_t*)data,
+ (int)len);
+ } else {
+ note.property = None;
+ }
+
+ XSendEvent(world->impl->display, note.requestor, True, 0, (XEvent*)&note);
+}
+
+/// Flush pending configure and expose events for all views
+static void
+flushExposures(PuglWorld* world)
+{
+ for (size_t i = 0; i < world->numViews; ++i) {
+ PuglView* const view = world->views[i];
+
+ if (view->visible) {
+ puglDispatchSimpleEvent(view, PUGL_UPDATE);
+ }
+
+ const PuglEvent configure = view->impl->pendingConfigure;
+ const PuglEvent expose = view->impl->pendingExpose;
+
+ view->impl->pendingConfigure.type = PUGL_NOTHING;
+ view->impl->pendingExpose.type = PUGL_NOTHING;
+
+ if (configure.type || expose.type) {
+ view->backend->enter(view, expose.type ? &expose.expose : NULL);
+ puglDispatchEventInContext(view, &configure);
+ puglDispatchEventInContext(view, &expose);
+ view->backend->leave(view, expose.type ? &expose.expose : NULL);
+ }
+ }
+}
+
+static bool
+handleTimerEvent(PuglWorld* world, XEvent xevent)
+{
+#ifdef HAVE_XSYNC
+ if (xevent.type == world->impl->syncEventBase + XSyncAlarmNotify) {
+ XSyncAlarmNotifyEvent* notify = ((XSyncAlarmNotifyEvent*)&xevent);
+
+ for (size_t i = 0; i < world->impl->numTimers; ++i) {
+ if (world->impl->timers[i].alarm == notify->alarm) {
+ const PuglEventTimer ev = {PUGL_TIMER, 0, world->impl->timers[i].id};
+ puglDispatchEvent(world->impl->timers[i].view, (const PuglEvent*)&ev);
+ }
+ }
+
+ return true;
+ }
+#else
+ (void)world;
+ (void)xevent;
+#endif
+
+ return false;
+}
+
+static PuglStatus
+puglDispatchX11Events(PuglWorld* world)
{
const PuglX11Atoms* const atoms = &world->impl->atoms;
- // Flush just once at the start to fill event queue
+ // Flush output to the server once at the start
Display* display = world->impl->display;
XFlush(display);
- // Process all queued events (locally, without flushing or reading)
- while (XEventsQueued(display, QueuedAlready) > 0) {
+ // Process all queued events (without further flushing)
+ while (XEventsQueued(display, QueuedAfterReading) > 0) {
XEvent xevent;
XNextEvent(display, &xevent);
+ if (handleTimerEvent(world, xevent)) {
+ continue;
+ }
+
PuglView* view = puglFindView(world, xevent.xany.window);
if (!view) {
continue;
@@ -634,53 +950,13 @@ puglDispatchEvents(PuglWorld* world)
XUnsetICFocus(impl->xic);
} else if (xevent.type == SelectionClear) {
puglSetBlob(&view->clipboard, NULL, 0);
- continue;
} else if (xevent.type == SelectionNotify &&
xevent.xselection.selection == atoms->CLIPBOARD &&
xevent.xselection.target == atoms->UTF8_STRING &&
xevent.xselection.property == XA_PRIMARY) {
-
- uint8_t* str = NULL;
- Atom type = 0;
- int fmt = 0;
- unsigned long len = 0;
- unsigned long left = 0;
- XGetWindowProperty(impl->display, impl->win, XA_PRIMARY,
- 0, 8, False, AnyPropertyType,
- &type, &fmt, &len, &left, &str);
-
- if (str && fmt == 8 && type == atoms->UTF8_STRING && left == 0) {
- puglSetBlob(&view->clipboard, str, len);
- }
-
- XFree(str);
- continue;
+ handleSelectionNotify(world, view);
} else if (xevent.type == SelectionRequest) {
- const XSelectionRequestEvent* request = &xevent.xselectionrequest;
-
- XSelectionEvent note = {0};
- note.type = SelectionNotify;
- note.requestor = request->requestor;
- note.selection = request->selection;
- note.target = request->target;
- note.time = request->time;
-
- const char* type = NULL;
- size_t len = 0;
- const void* data = puglGetInternalClipboard(view, &type, &len);
- if (data &&
- request->selection == atoms->CLIPBOARD &&
- request->target == atoms->UTF8_STRING) {
- note.property = request->property;
- XChangeProperty(impl->display, note.requestor,
- note.property, note.target, 8, PropModeReplace,
- (const uint8_t*)data, len);
- } else {
- note.property = None;
- }
-
- XSendEvent(impl->display, note.requestor, True, 0, (XEvent*)&note);
- continue;
+ handleSelectionRequest(world, view, &xevent.xselectionrequest);
}
// Translate X11 event to Pugl event
@@ -688,61 +964,68 @@ puglDispatchEvents(PuglWorld* world)
if (event.type == PUGL_EXPOSE) {
// Expand expose event to be dispatched after loop
- merge_expose_events(&view->impl->pendingExpose, &event);
+ mergeExposeEvents(&view->impl->pendingExpose, &event);
} else if (event.type == PUGL_CONFIGURE) {
// Expand configure event to be dispatched after loop
view->impl->pendingConfigure = event;
+ view->frame.x = event.configure.x;
+ view->frame.y = event.configure.y;
+ view->frame.width = event.configure.width;
+ view->frame.height = event.configure.height;
+ } else if (event.type == PUGL_MAP && view->parent) {
+ XWindowAttributes attrs;
+ XGetWindowAttributes(view->impl->display, view->impl->win, &attrs);
+
+ const PuglEventConfigure configure = {
+ PUGL_CONFIGURE, 0, attrs.x, attrs.y, attrs.width, attrs.height};
+
+ puglDispatchEvent(view, (const PuglEvent*)&configure);
+ puglDispatchEvent(view, &event);
} else {
// Dispatch event to application immediately
puglDispatchEvent(view, &event);
}
}
- // Flush pending configure and expose events for all views
- for (size_t i = 0; i < world->numViews; ++i) {
- PuglView* const view = world->views[i];
- PuglEvent* const configure = &view->impl->pendingConfigure;
- PuglEvent* const expose = &view->impl->pendingExpose;
-
- if (view->redisplay)
- {
- expose->type = PUGL_EXPOSE;
- view->redisplay = false;
- }
+ return PUGL_SUCCESS;
+}
- if (configure->type || expose->type) {
- const bool mustExpose = expose->type && expose->expose.count == 0;
- puglEnterContext(view, mustExpose);
+#ifndef PUGL_DISABLE_DEPRECATED
+PuglStatus
+puglProcessEvents(PuglView* view)
+{
+ return puglUpdate(view->world, 0.0);
+}
+#endif
- if (configure->type) {
- view->frame.x = configure->configure.x;
- view->frame.y = configure->configure.y;
- view->frame.width = configure->configure.width;
- view->frame.height = configure->configure.height;
+PuglStatus
+puglUpdate(PuglWorld* world, double timeout)
+{
+ const double startTime = puglGetTime(world);
+ PuglStatus st = PUGL_SUCCESS;
- view->backend->resize(view,
- (int)view->frame.width,
- (int)view->frame.height);
- view->eventFunc(view, &view->impl->pendingConfigure);
- }
+ world->impl->dispatchingEvents = true;
- if (mustExpose) {
- view->eventFunc(view, &view->impl->pendingExpose);
+ if (timeout < 0.0) {
+ st = puglPollX11Socket(world, timeout);
+ st = st ? st : puglDispatchX11Events(world);
+ } else if (timeout <= 0.001) {
+ st = puglDispatchX11Events(world);
+ } else {
+ const double endTime = startTime + timeout - 0.001;
+ for (double t = startTime; t < endTime; t = puglGetTime(world)) {
+ if ((st = puglPollX11Socket(world, endTime - t)) ||
+ (st = puglDispatchX11Events(world))) {
+ break;
}
-
- puglLeaveContext(view, mustExpose);
- configure->type = 0;
- expose->type = 0;
}
}
- return PUGL_SUCCESS;
-}
+ flushExposures(world);
-PuglStatus
-puglProcessEvents(PuglView* view)
-{
- return puglDispatchEvents(view->world);
+ world->impl->dispatchingEvents = false;
+
+ return st;
}
double
@@ -756,14 +1039,33 @@ puglGetTime(const PuglWorld* world)
PuglStatus
puglPostRedisplay(PuglView* view)
{
- view->redisplay = true;
+ const PuglRect rect = { 0, 0, view->frame.width, view->frame.height };
+
+ return puglPostRedisplayRect(view, rect);
+}
+
+PuglStatus
+puglPostRedisplayRect(PuglView* view, PuglRect rect)
+{
+ const PuglEventExpose event = {
+ PUGL_EXPOSE, 0, rect.x, rect.y, rect.width, rect.height, 0
+ };
+
+ if (view->world->impl->dispatchingEvents) {
+ // Currently dispatching events, add/expand expose for the loop end
+ mergeExposeEvents(&view->impl->pendingExpose, (const PuglEvent*)&event);
+ } else if (view->visible) {
+ // Not dispatching events, send an X expose so we wake up next time
+ return puglSendEvent(view, (const PuglEvent*)&event);
+ }
+
return PUGL_SUCCESS;
}
-PuglNativeWindow
+PuglNativeView
puglGetNativeWindow(PuglView* view)
{
- return (PuglNativeWindow)view->impl->win;
+ return (PuglNativeView)view->impl->win;
}
PuglStatus
@@ -773,10 +1075,13 @@ puglSetWindowTitle(PuglView* view, const char* title)
const PuglX11Atoms* const atoms = &view->world->impl->atoms;
puglSetString(&view->title, title);
- XStoreName(display, view->impl->win, title);
- XChangeProperty(display, view->impl->win, atoms->NET_WM_NAME,
- atoms->UTF8_STRING, 8, PropModeReplace,
- (const uint8_t*)title, (int)strlen(title));
+
+ if (view->impl->win) {
+ XStoreName(display, view->impl->win, title);
+ XChangeProperty(display, view->impl->win, atoms->NET_WM_NAME,
+ atoms->UTF8_STRING, 8, PropModeReplace,
+ (const uint8_t*)title, (int)strlen(title));
+ }
return PUGL_SUCCESS;
}
@@ -787,9 +1092,9 @@ puglSetFrame(PuglView* view, const PuglRect frame)
view->frame = frame;
if (view->impl->win &&
- XMoveResizeWindow(view->world->impl->display, view->impl->win,
- (int)frame.x, (int)frame.y,
- (int)frame.width, (int)frame.height)) {
+ !XMoveResizeWindow(view->world->impl->display, view->impl->win,
+ (int)frame.x, (int)frame.y,
+ (unsigned)frame.width, (unsigned)frame.height)) {
return PUGL_UNKNOWN_ERROR;
}
@@ -835,7 +1140,7 @@ puglSetAspectRatio(PuglView* const view,
}
PuglStatus
-puglSetTransientFor(PuglView* view, PuglNativeWindow parent)
+puglSetTransientFor(PuglView* view, PuglNativeView parent)
{
Display* display = view->world->impl->display;
@@ -872,8 +1177,7 @@ puglGetClipboard(PuglView* const view,
// Run event loop until data is received
while (!view->clipboard.data) {
- puglPollEvents(view->world, -1);
- puglDispatchEvents(view->world);
+ puglUpdate(view->world, -1.0);
}
}
@@ -897,3 +1201,18 @@ puglSetClipboard(PuglView* const view,
XSetSelectionOwner(impl->display, atoms->CLIPBOARD, impl->win, CurrentTime);
return PUGL_SUCCESS;
}
+
+const PuglBackend*
+puglStubBackend(void)
+{
+ static const PuglBackend backend = {
+ puglX11StubConfigure,
+ puglStubCreate,
+ puglStubDestroy,
+ puglStubEnter,
+ puglStubLeave,
+ puglStubGetContext,
+ };
+
+ return &backend;
+}
diff --git a/pugl/pugl/detail/x11.h b/pugl/pugl/detail/x11.h
index bfdbf60..6b7a150 100644
--- a/pugl/pugl/detail/x11.h
+++ b/pugl/pugl/detail/x11.h
@@ -18,25 +18,44 @@
@file x11.h Shared definitions for X11 implementation.
*/
-#include "pugl/detail/implementation.h"
+#include "pugl/detail/types.h"
+#include "pugl/pugl.h"
+#include <X11/X.h>
#include <X11/Xlib.h>
#include <X11/Xutil.h>
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdint.h>
+
typedef struct {
Atom CLIPBOARD;
Atom UTF8_STRING;
Atom WM_PROTOCOLS;
Atom WM_DELETE_WINDOW;
+ Atom PUGL_CLIENT_MSG;
Atom NET_WM_NAME;
Atom NET_WM_STATE;
Atom NET_WM_STATE_DEMANDS_ATTENTION;
} PuglX11Atoms;
+typedef struct {
+ XID alarm;
+ PuglView* view;
+ uint64_t id;
+} PuglTimer;
+
struct PuglWorldInternalsImpl {
Display* display;
PuglX11Atoms atoms;
XIM xim;
+ PuglTimer* timers;
+ size_t numTimers;
+ XID serverTimeCounter;
+ int syncEventBase;
+ bool syncSupported;
+ bool dispatchingEvents;
};
struct PuglInternalsImpl {
@@ -49,3 +68,16 @@ struct PuglInternalsImpl {
PuglEvent pendingConfigure;
PuglEvent pendingExpose;
};
+
+static inline PuglStatus
+puglX11StubConfigure(PuglView* view)
+{
+ PuglInternals* const impl = view->impl;
+ XVisualInfo pat = {0};
+ int n = 0;
+
+ pat.screen = impl->screen;
+ impl->vi = XGetVisualInfo(impl->display, VisualScreenMask, &pat, &n);
+
+ return PUGL_SUCCESS;
+}
diff --git a/pugl/pugl/detail/x11_cairo.c b/pugl/pugl/detail/x11_cairo.c
index 0753317..0229d97 100644
--- a/pugl/pugl/detail/x11_cairo.c
+++ b/pugl/pugl/detail/x11_cairo.c
@@ -21,132 +21,128 @@
#include "pugl/detail/types.h"
#include "pugl/detail/x11.h"
#include "pugl/pugl.h"
-#include "pugl/pugl_cairo_backend.h"
+#include "pugl/pugl_cairo.h"
#include <X11/Xutil.h>
#include <cairo-xlib.h>
#include <cairo.h>
-#include <stdbool.h>
-#include <stdio.h>
#include <stdlib.h>
-typedef struct {
+typedef struct {
cairo_surface_t* back;
- cairo_t* backCr;
cairo_surface_t* front;
- cairo_t* frontCr;
+ cairo_t* cr;
} PuglX11CairoSurface;
-static PuglStatus
-puglX11CairoConfigure(PuglView* view)
+static void
+puglX11CairoClose(PuglView* view)
{
- PuglInternals* const impl = view->impl;
-
- XVisualInfo pat;
- int n;
- pat.screen = impl->screen;
- impl->vi = XGetVisualInfo(impl->display, VisualScreenMask, &pat, &n);
+ PuglInternals* const impl = view->impl;
+ PuglX11CairoSurface* const surface = (PuglX11CairoSurface*)impl->surface;
- return PUGL_SUCCESS;
+ cairo_surface_destroy(surface->front);
+ cairo_surface_destroy(surface->back);
+ surface->front = surface->back = NULL;
}
static PuglStatus
-puglX11CairoCreate(PuglView* view)
+puglX11CairoOpen(PuglView* view)
{
- PuglInternals* const impl = view->impl;
- const int width = (int)view->frame.width;
- const int height = (int)view->frame.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);
+ PuglInternals* const impl = view->impl;
+ PuglX11CairoSurface* const surface = (PuglX11CairoSurface*)impl->surface;
+
+ surface->back = cairo_xlib_surface_create(impl->display,
+ impl->win,
+ impl->vi->visual,
+ (int)view->frame.width,
+ (int)view->frame.height);
+
+ surface->front = cairo_surface_create_similar(
+ surface->back,
+ cairo_surface_get_content(surface->back),
+ (int)view->frame.width,
+ (int)view->frame.height);
+
+ if (cairo_surface_status(surface->back) ||
+ cairo_surface_status(surface->front)) {
+ puglX11CairoClose(view);
return PUGL_CREATE_CONTEXT_FAILED;
}
- impl->surface = calloc(1, sizeof(PuglX11CairoSurface));
- *(PuglX11CairoSurface*)impl->surface = surface;
-
return PUGL_SUCCESS;
}
static PuglStatus
-puglX11CairoDestroy(PuglView* view)
+puglX11CairoCreate(PuglView* view)
{
- PuglInternals* const impl = view->impl;
- PuglX11CairoSurface* const surface = (PuglX11CairoSurface*)impl->surface;
+ PuglInternals* const impl = view->impl;
+
+ impl->surface = (cairo_surface_t*)calloc(1, sizeof(PuglX11CairoSurface));
- cairo_destroy(surface->frontCr);
- cairo_destroy(surface->backCr);
- cairo_surface_destroy(surface->front);
- cairo_surface_destroy(surface->back);
- free(surface);
- impl->surface = NULL;
return PUGL_SUCCESS;
}
static PuglStatus
-puglX11CairoEnter(PuglView* view, bool drawing)
+puglX11CairoDestroy(PuglView* view)
{
PuglInternals* const impl = view->impl;
PuglX11CairoSurface* const surface = (PuglX11CairoSurface*)impl->surface;
- if (drawing) {
- cairo_save(surface->frontCr);
- }
+ puglX11CairoClose(view);
+ free(surface);
return PUGL_SUCCESS;
}
static PuglStatus
-puglX11CairoLeave(PuglView* view, bool drawing)
+puglX11CairoEnter(PuglView* view, const PuglEventExpose* expose)
{
PuglInternals* const impl = view->impl;
PuglX11CairoSurface* const surface = (PuglX11CairoSurface*)impl->surface;
+ PuglStatus st = PUGL_SUCCESS;
+
+ if (expose && !(st = puglX11CairoOpen(view))) {
+ surface->cr = cairo_create(surface->front);
- if (drawing) {
- cairo_set_source_surface(surface->backCr, surface->front, 0, 0);
- cairo_paint(surface->backCr);
- cairo_restore(surface->frontCr);
+ if (cairo_status(surface->cr)) {
+ st = PUGL_CREATE_CONTEXT_FAILED;
+ }
}
- return PUGL_SUCCESS;
+ return st;
}
static PuglStatus
-puglX11CairoResize(PuglView* view, int width, int height)
+puglX11CairoLeave(PuglView* view, const PuglEventExpose* expose)
{
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_CREATE_CONTEXT_FAILED;
+ if (expose) {
+ // Destroy front context and create a new one for drawing to the back
+ cairo_destroy(surface->cr);
+ surface->cr = cairo_create(surface->back);
+
+ // Clip to expose region
+ cairo_rectangle(surface->cr,
+ expose->x,
+ expose->y,
+ expose->width,
+ expose->height);
+ cairo_clip(surface->cr);
+
+ // Paint front onto back
+ cairo_set_source_surface(surface->cr, surface->front, 0, 0);
+ cairo_paint(surface->cr);
+
+ // Flush to X and close everything
+ cairo_destroy(surface->cr);
+ cairo_surface_flush(surface->back);
+ puglX11CairoClose(view);
+ surface->cr = NULL;
}
- surface->frontCr = cairo_create(surface->front);
- cairo_save(surface->frontCr);
-
return PUGL_SUCCESS;
}
@@ -156,21 +152,18 @@ puglX11CairoGetContext(PuglView* view)
PuglInternals* const impl = view->impl;
PuglX11CairoSurface* const surface = (PuglX11CairoSurface*)impl->surface;
- return surface->frontCr;
+ return surface->cr;
}
const PuglBackend*
puglCairoBackend(void)
{
- static const PuglBackend backend = {
- puglX11CairoConfigure,
- puglX11CairoCreate,
- puglX11CairoDestroy,
- puglX11CairoEnter,
- puglX11CairoLeave,
- puglX11CairoResize,
- puglX11CairoGetContext
- };
+ static const PuglBackend backend = {puglX11StubConfigure,
+ puglX11CairoCreate,
+ puglX11CairoDestroy,
+ puglX11CairoEnter,
+ puglX11CairoLeave,
+ puglX11CairoGetContext};
return &backend;
}
diff --git a/pugl/pugl/detail/x11_gl.c b/pugl/pugl/detail/x11_gl.c
index f5dbe2c..33a05df 100644
--- a/pugl/pugl/detail/x11_gl.c
+++ b/pugl/pugl/detail/x11_gl.c
@@ -21,15 +21,15 @@
#include "pugl/detail/types.h"
#include "pugl/detail/x11.h"
#include "pugl/pugl.h"
-#include "pugl/pugl_gl_backend.h"
+#include "pugl/pugl_gl.h"
+#include "pugl/pugl_stub.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 <stdint.h>
#include <stdio.h>
#include <stdlib.h>
@@ -39,16 +39,16 @@ typedef struct {
int double_buffered;
} PuglX11GlSurface;
-static PuglStatus
+static int
puglX11GlHintValue(const int value)
{
return value == PUGL_DONT_CARE ? (int)GLX_DONT_CARE : value;
}
-static PuglStatus
-puglX11GlGetAttrib(Display* const display,
- const GLXFBConfig fb_config,
- const int attrib)
+static int
+puglX11GlGetAttrib(Display* const display,
+ GLXFBConfig fb_config,
+ const int attrib)
{
int value = 0;
glXGetFBConfigAttrib(display, fb_config, attrib, &value);
@@ -85,23 +85,28 @@ puglX11GlConfigure(PuglView* view)
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 PUGL_CREATE_CONTEXT_FAILED;
}
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));
+ char msg[128];
+
+ snprintf(
+ msg,
+ sizeof(msg),
+ "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));
+
+ view->world->logFunc(view->world, PUGL_LOG_LEVEL_INFO, msg);
XFree(fbc);
@@ -114,7 +119,7 @@ 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;
+ GLXFBConfig fb_config = surface->fb_config;
const int ctx_attrs[] = {
GLX_CONTEXT_MAJOR_VERSION_ARB, view->hints[PUGL_CONTEXT_VERSION_MAJOR],
@@ -127,14 +132,15 @@ puglX11GlCreate(PuglView* view)
: GLX_CONTEXT_CORE_PROFILE_BIT_ARB),
0};
- typedef GLXContext (*CreateContextAttribs)(
- Display*, GLXFBConfig, GLXContext, Bool, const int*);
+ PFNGLXCREATECONTEXTATTRIBSARBPROC create_context =
+ (PFNGLXCREATECONTEXTATTRIBSARBPROC)glXGetProcAddress(
+ (const uint8_t*)"glXCreateContextAttribsARB");
- CreateContextAttribs create_context =
- (CreateContextAttribs)glXGetProcAddress(
- (const GLubyte*)"glXCreateContextAttribsARB");
+ PFNGLXSWAPINTERVALEXTPROC glXSwapIntervalEXT =
+ (PFNGLXSWAPINTERVALEXTPROC) glXGetProcAddress(
+ (const uint8_t*)"glXSwapIntervalEXT");
- surface->ctx = create_context(display, fb_config, 0, GL_TRUE, ctx_attrs);
+ surface->ctx = create_context(display, fb_config, 0, True, ctx_attrs);
if (!surface->ctx) {
surface->ctx =
glXCreateNewContext(display, fb_config, GLX_RGBA_TYPE, 0, True);
@@ -144,6 +150,11 @@ puglX11GlCreate(PuglView* view)
return PUGL_CREATE_CONTEXT_FAILED;
}
+ const int swapInterval = view->hints[PUGL_SWAP_INTERVAL];
+ if (glXSwapIntervalEXT && swapInterval != PUGL_DONT_CARE) {
+ glXSwapIntervalEXT(display, impl->win, swapInterval);
+ }
+
glXGetConfig(impl->display,
impl->vi,
GLX_DOUBLEBUFFER,
@@ -165,7 +176,7 @@ puglX11GlDestroy(PuglView* view)
}
static PuglStatus
-puglX11GlEnter(PuglView* view, bool PUGL_UNUSED(drawing))
+puglX11GlEnter(PuglView* view, const PuglEventExpose* PUGL_UNUSED(expose))
{
PuglX11GlSurface* surface = (PuglX11GlSurface*)view->impl->surface;
glXMakeCurrent(view->impl->display, view->impl->win, surface->ctx);
@@ -173,14 +184,12 @@ puglX11GlEnter(PuglView* view, bool PUGL_UNUSED(drawing))
}
static PuglStatus
-puglX11GlLeave(PuglView* view, bool drawing)
+puglX11GlLeave(PuglView* view, const PuglEventExpose* expose)
{
PuglX11GlSurface* surface = (PuglX11GlSurface*)view->impl->surface;
- if (drawing && surface->double_buffered) {
+ if (expose && surface->double_buffered) {
glXSwapBuffers(view->impl->display, view->impl->win);
- } else if (drawing) {
- glFlush();
}
glXMakeCurrent(view->impl->display, None, NULL);
@@ -188,37 +197,20 @@ puglX11GlLeave(PuglView* view, bool drawing)
return PUGL_SUCCESS;
}
-static PuglStatus
-puglX11GlResize(PuglView* PUGL_UNUSED(view),
- int PUGL_UNUSED(width),
- int PUGL_UNUSED(height))
-{
- return PUGL_SUCCESS;
-}
-
-static void*
-puglX11GlGetContext(PuglView* PUGL_UNUSED(view))
-{
- return NULL;
-}
-
PuglGlFunc
puglGetProcAddress(const char* name)
{
- return glXGetProcAddress((const GLubyte*)name);
+ return glXGetProcAddress((const uint8_t*)name);
}
const PuglBackend* puglGlBackend(void)
{
- static const PuglBackend backend = {
- puglX11GlConfigure,
- puglX11GlCreate,
- puglX11GlDestroy,
- puglX11GlEnter,
- puglX11GlLeave,
- puglX11GlResize,
- puglX11GlGetContext
- };
+ static const PuglBackend backend = {puglX11GlConfigure,
+ puglX11GlCreate,
+ puglX11GlDestroy,
+ puglX11GlEnter,
+ puglX11GlLeave,
+ puglStubGetContext};
return &backend;
}
diff --git a/pugl/pugl/pugl.h b/pugl/pugl/pugl.h
index 1299fa7..57e23fa 100644
--- a/pugl/pugl/pugl.h
+++ b/pugl/pugl/pugl.h
@@ -1,5 +1,5 @@
/*
- Copyright 2012-2019 David Robillard <http://drobilla.net>
+ Copyright 2012-2020 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,11 +15,11 @@
*/
/**
- @file pugl.h Public C API.
+ @file pugl.h Pugl API.
*/
-#ifndef PUGL_H_INCLUDED
-#define PUGL_H_INCLUDED
+#ifndef PUGL_PUGL_H
+#define PUGL_PUGL_H
#include <stdbool.h>
#include <stddef.h>
@@ -42,68 +42,31 @@
# define PUGL_API
#endif
-#ifdef __cplusplus
-extern "C" {
+#ifndef PUGL_DISABLE_DEPRECATED
+# if defined(__clang__)
+# define PUGL_DEPRECATED_BY(rep) __attribute__((deprecated("", rep)))
+# elif defined(__GNUC__)
+# define PUGL_DEPRECATED_BY(rep) __attribute__((deprecated("Use " rep)))
+# else
+# define PUGL_DEPRECATED_BY(rep)
+# endif
#endif
-/**
- @defgroup pugl Pugl
- Pugl C API.
- @{
-*/
-
-/**
- Handle for opaque user data.
-*/
-typedef void* PuglHandle;
+#ifdef __cplusplus
+# define PUGL_BEGIN_DECLS extern "C" {
+# define PUGL_END_DECLS }
+#else
+# define PUGL_BEGIN_DECLS
+# define PUGL_END_DECLS
+#endif
-/**
- Return status code.
-*/
-typedef enum {
- PUGL_SUCCESS, /**< Success */
- PUGL_FAILURE, /**< Non-fatal failure */
- PUGL_UNKNOWN_ERROR, /**< Unknown system error */
- PUGL_BAD_BACKEND, /**< Invalid or missing backend */
- PUGL_BACKEND_FAILED, /**< Backend initialisation failed */
- PUGL_REGISTRATION_FAILED, /**< Window class registration failed */
- PUGL_CREATE_WINDOW_FAILED, /**< Window creation failed */
- PUGL_SET_FORMAT_FAILED, /**< Failed to set pixel format */
- PUGL_CREATE_CONTEXT_FAILED, /**< Failed to create drawing context */
- PUGL_UNSUPPORTED_TYPE, /**< Unsupported data type */
-} PuglStatus;
+PUGL_BEGIN_DECLS
/**
- Window hint.
+ @defgroup pugl_api Pugl
+ A minimal portable API for embeddable GUIs.
+ @{
*/
-typedef enum {
- PUGL_USE_COMPAT_PROFILE, /**< Use compatible (not core) OpenGL profile */
- PUGL_USE_DEBUG_CONTEXT, /**< True to use a debug OpenGL context */
- 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_SWAP_INTERVAL, /**< Number of frames between buffer swaps */
- PUGL_RESIZABLE, /**< True if window should be resizable */
- PUGL_IGNORE_KEY_REPEAT, /**< True if key repeat events are ignored */
-
- PUGL_NUM_WINDOW_HINTS
-} PuglViewHint;
-
-/**
- Special window hint value.
-*/
-typedef enum {
- PUGL_DONT_CARE = -1, /**< Use best available value */
- PUGL_FALSE = 0, /**< Explicitly false */
- PUGL_TRUE = 1 /**< Explicitly true */
-} PuglViewHintValue;
/**
A rectangle.
@@ -119,32 +82,50 @@ typedef struct {
} PuglRect;
/**
+ @defgroup events Events
+
+ Event definitions.
+
+ All updates to the view happen via events, which are dispatched to the
+ view's #PuglEventFunc by Pugl. Most events map directly to one from the
+ underlying window system, but some are constructed by Pugl itself so there
+ is not necessarily a direct correspondence.
+
+ @{
+*/
+
+/**
Keyboard modifier flags.
*/
typedef enum {
- PUGL_MOD_SHIFT = 1, /**< Shift key */
- PUGL_MOD_CTRL = 1 << 1, /**< Control key */
- PUGL_MOD_ALT = 1 << 2, /**< Alt/Option key */
- PUGL_MOD_SUPER = 1 << 3 /**< Mod4/Command/Windows key */
+ PUGL_MOD_SHIFT = 1, ///< Shift key
+ PUGL_MOD_CTRL = 1 << 1, ///< Control key
+ PUGL_MOD_ALT = 1 << 2, ///< Alt/Option key
+ PUGL_MOD_SUPER = 1 << 3 ///< Mod4/Command/Windows key
} PuglMod;
/**
- Special keyboard keys.
+ Bitwise OR of #PuglMod values.
+*/
+typedef uint32_t PuglMods;
+
+/**
+ Keyboard key codepoints.
- All keys, special or not, are expressed as a Unicode code point. This
+ All keys are identified by a Unicode code point in PuglEventKey::key. This
enumeration defines constants for special keys that do not have a standard
- code point, and some convenience constants for control characters.
+ code point, and some convenience constants for control characters. Note
+ that all keys are handled in the same way, this enumeration is just for
+ convenience when writing hard-coded key bindings.
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.
+ 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_TAB = 0x09,
- PUGL_KEY_RETURN = 0x0D,
PUGL_KEY_ESCAPE = 0x1B,
PUGL_KEY_DELETE = 0x7F,
@@ -194,248 +175,479 @@ typedef enum {
The type of a PuglEvent.
*/
typedef enum {
- PUGL_NOTHING, /**< No event */
- PUGL_BUTTON_PRESS, /**< Mouse button press */
- PUGL_BUTTON_RELEASE, /**< Mouse button release */
- PUGL_CONFIGURE, /**< View moved and/or resized */
- PUGL_EXPOSE, /**< View exposed, redraw required */
- 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 */
- PUGL_SCROLL, /**< Scroll */
- PUGL_FOCUS_IN, /**< Keyboard focus entered view */
- PUGL_FOCUS_OUT /**< Keyboard focus left view */
+ PUGL_NOTHING, ///< No event
+ PUGL_CREATE, ///< View created, a #PuglEventCreate
+ PUGL_DESTROY, ///< View destroyed, a #PuglEventDestroy
+ PUGL_CONFIGURE, ///< View moved/resized, a #PuglEventConfigure
+ PUGL_MAP, ///< View made visible, a #PuglEventMap
+ PUGL_UNMAP, ///< View made invisible, a #PuglEventUnmap
+ PUGL_UPDATE, ///< View ready to draw, a #PuglEventUpdate
+ PUGL_EXPOSE, ///< View must be drawn, a #PuglEventExpose
+ PUGL_CLOSE, ///< View will be closed, a #PuglEventClose
+ PUGL_FOCUS_IN, ///< Keyboard focus entered view, a #PuglEventFocus
+ PUGL_FOCUS_OUT, ///< Keyboard focus left view, a #PuglEventFocus
+ PUGL_KEY_PRESS, ///< Key pressed, a #PuglEventKey
+ PUGL_KEY_RELEASE, ///< Key released, a #PuglEventKey
+ PUGL_TEXT, ///< Character entered, a #PuglEventText
+ PUGL_POINTER_IN, ///< Pointer entered view, a #PuglEventCrossing
+ PUGL_POINTER_OUT, ///< Pointer left view, a #PuglEventCrossing
+ PUGL_BUTTON_PRESS, ///< Mouse button pressed, a #PuglEventButton
+ PUGL_BUTTON_RELEASE, ///< Mouse button released, a #PuglEventButton
+ PUGL_MOTION, ///< Pointer moved, a #PuglEventMotion
+ PUGL_SCROLL, ///< Scrolled, a #PuglEventScroll
+ PUGL_CLIENT, ///< Custom client message, a #PuglEventClient
+ PUGL_TIMER, ///< Timer triggered, a #PuglEventTimer
+
+#ifndef PUGL_DISABLE_DEPRECATED
+ PUGL_ENTER_NOTIFY PUGL_DEPRECATED_BY("PUGL_POINTER_IN") = PUGL_POINTER_IN,
+ PUGL_LEAVE_NOTIFY PUGL_DEPRECATED_BY("PUGL_POINTER_OUT") = PUGL_POINTER_OUT,
+ PUGL_MOTION_NOTIFY PUGL_DEPRECATED_BY("PUGL_MOTION") = PUGL_MOTION,
+#endif
+
} PuglEventType;
+/**
+ Common flags for all event types.
+*/
typedef enum {
- PUGL_IS_SEND_EVENT = 1
+ PUGL_IS_SEND_EVENT = 1 ///< Event is synthetic
} PuglEventFlag;
/**
+ Bitwise OR of #PuglEventFlag values.
+*/
+typedef uint32_t PuglEventFlags;
+
+/**
Reason for a PuglEventCrossing.
*/
typedef enum {
- PUGL_CROSSING_NORMAL, /**< Crossing due to pointer motion. */
- PUGL_CROSSING_GRAB, /**< Crossing due to a grab. */
- PUGL_CROSSING_UNGRAB /**< Crossing due to a grab release. */
+ PUGL_CROSSING_NORMAL, ///< Crossing due to pointer motion
+ PUGL_CROSSING_GRAB, ///< Crossing due to a grab
+ PUGL_CROSSING_UNGRAB ///< Crossing due to a grab release
} PuglCrossingMode;
/**
Common header for all event structs.
*/
typedef struct {
- PuglEventType type; /**< Event type. */
- uint32_t flags; /**< Bitwise OR of PuglEventFlag values. */
+ PuglEventType type; ///< Event type
+ PuglEventFlags flags; ///< Bitwise OR of #PuglEventFlag values
} PuglEventAny;
/**
- Button press or release event.
+ View create event.
+
+ This event is sent when a view is realized before it is first displayed,
+ with the graphics context entered. This is typically used for setting up
+ the graphics system, for example by loading OpenGL extensions.
- For event types PUGL_BUTTON_PRESS and PUGL_BUTTON_RELEASE.
+ This event type has no extra fields.
*/
-typedef struct {
- PuglEventType type; /**< PUGL_BUTTON_PRESS or PUGL_BUTTON_RELEASE. */
- 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 xRoot; /**< Root-relative X coordinate. */
- double yRoot; /**< Root-relative Y coordinate. */
- uint32_t state; /**< Bitwise OR of PuglMod flags. */
- uint32_t button; /**< 1-relative button number. */
-} PuglEventButton;
+typedef PuglEventAny PuglEventCreate;
/**
- Configure event for when window size or position has changed.
+ View destroy event.
+
+ This event is the counterpart to #PuglEventCreate, and it is sent when the
+ view is being destroyed. This is typically used for tearing down the
+ graphics system, or otherwise freeing any resources allocated when the
+ create event was handled.
+
+ This is the last event sent to any view, and immediately after it is
+ processed, the view is destroyed and may no longer be used.
+
+ This event type has no extra fields.
+*/
+typedef PuglEventAny PuglEventDestroy;
+
+/**
+ View resize or move event.
+
+ A configure event is sent whenever the view is resized or moved. When a
+ configure event is received, the graphics context is active but not set up
+ for drawing. For example, it is valid to adjust the OpenGL viewport or
+ otherwise configure the context, but not to draw anything.
*/
typedef struct {
- PuglEventType type; /**< PUGL_CONFIGURE. */
- uint32_t flags; /**< Bitwise OR of PuglEventFlag values. */
- double x; /**< New parent-relative X coordinate. */
- double y; /**< New parent-relative Y coordinate. */
- double width; /**< New width. */
- double height; /**< New height. */
+ PuglEventType type; ///< #PUGL_CONFIGURE
+ PuglEventFlags flags; ///< Bitwise OR of #PuglEventFlag values
+ double x; ///< New parent-relative X coordinate
+ double y; ///< New parent-relative Y coordinate
+ double width; ///< New width
+ double height; ///< New height
} PuglEventConfigure;
/**
+ View show event.
+
+ This event is sent when a view is mapped to the screen and made visible.
+
+ This event type has no extra fields.
+*/
+typedef PuglEventAny PuglEventMap;
+
+/**
+ View hide event.
+
+ This event is sent when a view is unmapped from the screen and made
+ invisible.
+
+ This event type has no extra fields.
+*/
+typedef PuglEventAny PuglEventUnmap;
+
+/**
+ View update event.
+
+ This event is sent to every view near the end of a main loop iteration when
+ any pending exposures are about to be redrawn. It is typically used to mark
+ regions to expose with puglPostRedisplay() or puglPostRedisplayRect(). For
+ example, to continuously animate, a view calls puglPostRedisplay() when an
+ update event is received, and it will then shortly receive an expose event.
+*/
+typedef PuglEventAny PuglEventUpdate;
+
+/**
Expose event for when a region must be redrawn.
+
+ When an expose event is received, the graphics context is active, and the
+ view must draw the entire specified region. The contents of the region are
+ undefined, there is no preservation of anything drawn previously.
*/
typedef struct {
- PuglEventType type; /**< PUGL_EXPOSE. */
- uint32_t flags; /**< Bitwise OR of PuglEventFlag values. */
- double x; /**< View-relative X coordinate. */
- double y; /**< View-relative Y coordinate. */
- double width; /**< Width of exposed region. */
- double height; /**< Height of exposed region. */
- int count; /**< Number of expose events to follow. */
+ PuglEventType type; ///< #PUGL_EXPOSE
+ PuglEventFlags flags; ///< Bitwise OR of #PuglEventFlag values
+ double x; ///< View-relative X coordinate
+ double y; ///< View-relative Y coordinate
+ double width; ///< Width of exposed region
+ double height; ///< Height of exposed region
+ int count; ///< Number of expose events to follow
} PuglEventExpose;
/**
- Window close event.
+ View close event.
+
+ This event is sent when the view is to be closed, for example when the user
+ clicks the close button.
+
+ This event type has no extra fields.
+*/
+typedef PuglEventAny PuglEventClose;
+
+/**
+ Keyboard focus event.
+
+ This event is sent whenever the view gains or loses the keyboard focus. The
+ view with the keyboard focus will receive any key press or release events.
*/
typedef struct {
- PuglEventType type; /**< PUGL_CLOSE. */
- uint32_t flags; /**< Bitwise OR of PuglEventFlag values. */
-} PuglEventClose;
+ PuglEventType type; ///< #PUGL_FOCUS_IN or #PUGL_FOCUS_OUT
+ PuglEventFlags flags; ///< Bitwise OR of #PuglEventFlag values
+ bool grab; ///< True iff this is a grab/ungrab event
+} PuglEventFocus;
/**
- Key press/release event.
+ Key press or 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.
+ This event represents low-level key presses and releases. This can be used
+ for "direct" keyboard handing like key bindings, but must not be interpreted
+ as text input.
- 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).
+ Keys are represented portably as Unicode code points, using the "natural"
+ code point for the key where possible (see #PuglKey for details). The #key
+ field is the code for the pressed key, without any modifiers applied. For
+ example, a press or release of the 'A' key will have #key 97 ('a')
+ regardless of whether shift or control are being held.
+
+ Alternatively, the raw #keycode can be used to work directly with physical
+ keys, but note that this value is not portable and differs between platforms
+ and hardware.
*/
typedef struct {
- PuglEventType type; /**< PUGL_KEY_PRESS or PUGL_KEY_RELEASE. */
- 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 xRoot; /**< Root-relative X coordinate. */
- double yRoot; /**< Root-relative Y coordinate. */
- uint32_t state; /**< Bitwise OR of PuglMod flags. */
- uint32_t keycode; /**< Raw key code. */
- uint32_t key; /**< Unshifted Unicode character code, or 0. */
+ PuglEventType type; ///< #PUGL_KEY_PRESS or #PUGL_KEY_RELEASE
+ PuglEventFlags flags; ///< Bitwise OR of #PuglEventFlag values
+ double time; ///< Time in seconds
+ double x; ///< View-relative X coordinate
+ double y; ///< View-relative Y coordinate
+ double xRoot; ///< Root-relative X coordinate
+ double yRoot; ///< Root-relative Y coordinate
+ PuglMods 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.
+ This event 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.
+
+ Note that this event is generated by the platform's input system, so there
+ is not necessarily a direct correspondence between text events and physical
+ key presses. For example, with some input methods a sequence of several key
+ presses will generate a single character.
*/
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 xRoot; /**< Root-relative X coordinate. */
- double yRoot; /**< 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. */
+ PuglEventType type; ///< #PUGL_TEXT
+ PuglEventFlags flags; ///< Bitwise OR of #PuglEventFlag values
+ double time; ///< Time in seconds
+ double x; ///< View-relative X coordinate
+ double y; ///< View-relative Y coordinate
+ double xRoot; ///< Root-relative X coordinate
+ double yRoot; ///< Root-relative Y coordinate
+ PuglMods 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).
+ Pointer enter or leave event.
+
+ This event is sent when the pointer enters or leaves the view. This can
+ happen for several reasons (not just the user dragging the pointer over the
+ window edge), as described by the #mode field.
*/
typedef struct {
- PuglEventType type; /**< PUGL_ENTER_NOTIFY or PUGL_LEAVE_NOTIFY. */
- 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 xRoot; /**< Root-relative X coordinate. */
- double yRoot; /**< Root-relative Y coordinate. */
- uint32_t state; /**< Bitwise OR of PuglMod flags. */
- PuglCrossingMode mode; /**< Reason for crossing. */
+ PuglEventType type; ///< #PUGL_POINTER_IN or #PUGL_POINTER_OUT
+ PuglEventFlags flags; ///< Bitwise OR of #PuglEventFlag values
+ double time; ///< Time in seconds
+ double x; ///< View-relative X coordinate
+ double y; ///< View-relative Y coordinate
+ double xRoot; ///< Root-relative X coordinate
+ double yRoot; ///< Root-relative Y coordinate
+ PuglMods state; ///< Bitwise OR of #PuglMod flags
+ PuglCrossingMode mode; ///< Reason for crossing
} PuglEventCrossing;
/**
+ Button press or release event.
+*/
+typedef struct {
+ PuglEventType type; ///< #PUGL_BUTTON_PRESS or #PUGL_BUTTON_RELEASE
+ PuglEventFlags flags; ///< Bitwise OR of #PuglEventFlag values
+ double time; ///< Time in seconds
+ double x; ///< View-relative X coordinate
+ double y; ///< View-relative Y coordinate
+ double xRoot; ///< Root-relative X coordinate
+ double yRoot; ///< Root-relative Y coordinate
+ PuglMods state; ///< Bitwise OR of #PuglMod flags
+ uint32_t button; ///< Button number starting from 1
+} PuglEventButton;
+
+/**
Pointer motion event.
*/
typedef struct {
- PuglEventType type; /**< PUGL_MOTION_NOTIFY. */
- 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 xRoot; /**< Root-relative X coordinate. */
- double yRoot; /**< Root-relative Y coordinate. */
- uint32_t state; /**< Bitwise OR of PuglMod flags. */
- bool isHint; /**< True iff this event is a motion hint. */
- bool focus; /**< True iff this is the focused window. */
+ PuglEventType type; ///< #PUGL_MOTION
+ PuglEventFlags flags; ///< Bitwise OR of #PuglEventFlag values
+ double time; ///< Time in seconds
+ double x; ///< View-relative X coordinate
+ double y; ///< View-relative Y coordinate
+ double xRoot; ///< Root-relative X coordinate
+ double yRoot; ///< Root-relative Y coordinate
+ PuglMods state; ///< Bitwise OR of #PuglMod flags
+ bool isHint; ///< True iff this event is a motion hint
+ bool focus; ///< True iff this is the focused view
} PuglEventMotion;
/**
Scroll event.
The scroll distance is expressed in "lines", an arbitrary unit that
- corresponds to a single tick of a detented mouse wheel. For example, `dy` =
+ corresponds to a single tick of a detented mouse wheel. For example, #dy =
1.0 scrolls 1 line up. Some systems and devices support finer resolution
and/or higher values for fast scrolls, so programs should handle any value
gracefully.
- */
+*/
typedef struct {
- PuglEventType type; /**< PUGL_SCROLL. */
- 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 xRoot; /**< Root-relative X coordinate. */
- double yRoot; /**< Root-relative Y coordinate. */
- uint32_t state; /**< Bitwise OR of PuglMod flags. */
- double dx; /**< Scroll X distance in lines. */
- double dy; /**< Scroll Y distance in lines. */
+ PuglEventType type; ///< #PUGL_SCROLL
+ PuglEventFlags flags; ///< Bitwise OR of #PuglEventFlag values
+ double time; ///< Time in seconds
+ double x; ///< View-relative X coordinate
+ double y; ///< View-relative Y coordinate
+ double xRoot; ///< Root-relative X coordinate
+ double yRoot; ///< Root-relative Y coordinate
+ PuglMods state; ///< Bitwise OR of #PuglMod flags
+ double dx; ///< Scroll X distance in lines
+ double dy; ///< Scroll Y distance in lines
} PuglEventScroll;
/**
- Keyboard focus event.
+ Custom client message event.
+
+ This can be used to send a custom message to a view, which is delivered via
+ the window system and processed in the event loop as usual. Among other
+ things, this makes it possible to wake up the event loop for any reason.
*/
typedef struct {
- PuglEventType type; /**< PUGL_FOCUS_IN or PUGL_FOCUS_OUT. */
- uint32_t flags; /**< Bitwise OR of PuglEventFlag values. */
- bool grab; /**< True iff this is a grab/ungrab event. */
-} PuglEventFocus;
+ PuglEventType type; ///< #PUGL_CLIENT
+ PuglEventFlags flags; ///< Bitwise OR of #PuglEventFlag values
+ uintptr_t data1; ///< Client-specific data
+ uintptr_t data2; ///< Client-specific data
+} PuglEventClient;
+
+/**
+ Timer event.
+
+ This event is sent at the regular interval specified in the call to
+ puglStartTimer() that activated it.
+
+ The #id is the application-specific ID given to puglStartTimer() which
+ distinguishes this timer from others. It should always be checked in the
+ event handler, even in applications that register only one timer.
+*/
+typedef struct {
+ PuglEventType type; ///< #PUGL_TIMER
+ PuglEventFlags flags; ///< Bitwise OR of #PuglEventFlag values
+ uintptr_t id; ///< Timer ID
+} PuglEventTimer;
/**
- Interface event.
+ View event.
+
+ This is a union of all event types. The #type must be checked to determine
+ which fields are safe to access. A pointer to PuglEvent can either be cast
+ to the appropriate type, or the union members used.
- This is a union of all event structs. The `type` must be checked to
- determine which fields are safe to access. A pointer to PuglEvent can
- either be cast to the appropriate type, or the union members used.
+ The graphics system may only be accessed when handling certain events. The
+ graphics context is active for #PUGL_CREATE, #PUGL_DESTROY, #PUGL_CONFIGURE,
+ and #PUGL_EXPOSE, but only enabled for drawing for #PUGL_EXPOSE.
*/
typedef union {
- PuglEventType type; /**< Event type. */
- PuglEventAny any; /**< Valid for all event types. */
- PuglEventButton button; /**< PUGL_BUTTON_PRESS, PUGL_BUTTON_RELEASE. */
- PuglEventConfigure configure; /**< PUGL_CONFIGURE. */
- 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. */
- PuglEventFocus focus; /**< PUGL_FOCUS_IN, PUGL_FOCUS_OUT. */
+ PuglEventAny any; ///< Valid for all event types
+ PuglEventType type; ///< Event type
+ PuglEventButton button; ///< #PUGL_BUTTON_PRESS, #PUGL_BUTTON_RELEASE
+ PuglEventConfigure configure; ///< #PUGL_CONFIGURE
+ PuglEventExpose expose; ///< #PUGL_EXPOSE
+ PuglEventKey key; ///< #PUGL_KEY_PRESS, #PUGL_KEY_RELEASE
+ PuglEventText text; ///< #PUGL_TEXT
+ PuglEventCrossing crossing; ///< #PUGL_POINTER_IN, #PUGL_POINTER_OUT
+ PuglEventMotion motion; ///< #PUGL_MOTION
+ PuglEventScroll scroll; ///< #PUGL_SCROLL
+ PuglEventFocus focus; ///< #PUGL_FOCUS_IN, #PUGL_FOCUS_OUT
+ PuglEventClient client; ///< #PUGL_CLIENT
+ PuglEventTimer timer; ///< #PUGL_TIMER
} PuglEvent;
/**
- @anchor world
- @name World
- The top level context of a Pugl application.
+ @}
+ @defgroup status Status
+
+ Status codes and error handling.
+
+ @{
+*/
+
+/**
+ Return status code.
+*/
+typedef enum {
+ PUGL_SUCCESS, ///< Success
+ PUGL_FAILURE, ///< Non-fatal failure
+ PUGL_UNKNOWN_ERROR, ///< Unknown system error
+ PUGL_BAD_BACKEND, ///< Invalid or missing backend
+ PUGL_BAD_PARAMETER, ///< Invalid parameter
+ PUGL_BACKEND_FAILED, ///< Backend initialisation failed
+ PUGL_REGISTRATION_FAILED, ///< Class registration failed
+ PUGL_REALIZE_FAILED, ///< System view realization failed
+ PUGL_SET_FORMAT_FAILED, ///< Failed to set pixel format
+ PUGL_CREATE_CONTEXT_FAILED, ///< Failed to create drawing context
+ PUGL_UNSUPPORTED_TYPE, ///< Unsupported data type
+} PuglStatus;
+
+/**
+ Return a string describing a status code.
+*/
+PUGL_API
+const char*
+puglStrerror(PuglStatus status);
+
+/**
+ @}
+ @defgroup world World
+
+ The top-level context of a Pugl application or plugin.
+
+ The world contains all library-wide state. There is no static data in Pugl,
+ so it is safe to use multiple worlds in a single process. This is to
+ facilitate plugins or other situations where it is not possible to share a
+ world, but a single world should be shared for all views where possible.
+
@{
*/
/**
The "world" of application state.
- The world represents things that are not associated with a particular view.
- Several worlds can be created in a process (which is the case when many
- plugins use Pugl, for example), but code using different worlds must be
- isolated so they are never mixed. Views are strongly associated with the
- world they were created for.
+ The world represents everything that is not associated with a particular
+ view. Several worlds can be created in a single process, but code using
+ different worlds must be isolated so they are never mixed. Views are
+ strongly associated with the world they were created in.
*/
typedef struct PuglWorldImpl PuglWorld;
/**
+ Handle for the world's opaque user data.
+*/
+typedef void* PuglWorldHandle;
+
+/**
+ The type of a PuglWorld.
+*/
+typedef enum {
+ PUGL_PROGRAM, ///< Top-level application
+ PUGL_MODULE ///< Plugin or module within a larger application
+} PuglWorldType;
+
+/**
+ World flags.
+*/
+typedef enum {
+ /**
+ Set up support for threads if necessary.
+
+ - X11: Calls XInitThreads() which is required for some drivers.
+ */
+ PUGL_WORLD_THREADS = 1 << 0
+} PuglWorldFlag;
+
+/**
+ Bitwise OR of #PuglWorldFlag values.
+*/
+typedef uint32_t PuglWorldFlags;
+
+/**
+ A log message level, compatible with syslog.
+*/
+typedef enum {
+ PUGL_LOG_LEVEL_ERR = 3, ///< Error
+ PUGL_LOG_LEVEL_WARNING = 4, ///< Warning
+ PUGL_LOG_LEVEL_INFO = 6, ///< Informational message
+ PUGL_LOG_LEVEL_DEBUG = 7 ///< Debug message
+} PuglLogLevel;
+
+/**
+ A function called to report log messages.
+
+ @param world The world that produced this log message.
+ @param level Log level.
+ @param msg Message string.
+*/
+typedef void (*PuglLogFunc)(PuglWorld* world,
+ PuglLogLevel level,
+ const char* msg);
+
+/**
Create a new world.
- @return A newly created world.
+ @param type The type, which dictates what this world is responsible for.
+ @param flags Flags to control world features.
+ @return A new world, which must be later freed with puglFreeWorld().
*/
PUGL_API PuglWorld*
-puglNewWorld(void);
+puglNewWorld(PuglWorldType type, PuglWorldFlags flags);
/**
Free a world allocated with puglNewWorld().
@@ -444,6 +656,51 @@ PUGL_API void
puglFreeWorld(PuglWorld* world);
/**
+ Set the user data for the world.
+
+ This is usually a pointer to a struct that contains all the state which must
+ be accessed by several views.
+
+ The handle is opaque to Pugl and is not interpreted in any way.
+*/
+PUGL_API void
+puglSetWorldHandle(PuglWorld* world, PuglWorldHandle handle);
+
+/**
+ Get the user data for the world.
+*/
+PUGL_API PuglWorldHandle
+puglGetWorldHandle(PuglWorld* world);
+
+/**
+ Return a pointer to the native handle of the world.
+
+ @return
+ - X11: A pointer to the `Display`.
+ - MacOS: `NULL`.
+ - Windows: The `HMODULE` of the calling process.
+*/
+PUGL_API void*
+puglGetNativeWorld(PuglWorld* world);
+
+/**
+ Set the function to call to log a message.
+
+ This will be called to report any log messages generated internally by Pugl
+ which are enabled according to the log level.
+*/
+PUGL_API PuglStatus
+puglSetLogFunc(PuglWorld* world, PuglLogFunc logFunc);
+
+/**
+ Set the level of log messages to emit.
+
+ Any log messages with a level less than or equal to `level` will be emitted.
+*/
+PUGL_API PuglStatus
+puglSetLogLevel(PuglWorld* world, PuglLogLevel level);
+
+/**
Set the class name of the application.
This is a stable identifier for the application, used as the window
@@ -466,49 +723,128 @@ PUGL_API double
puglGetTime(const PuglWorld* world);
/**
- Poll for events that are ready to be processed.
+ Update by processing events from the window system.
+
+ This function is a single iteration of the main loop, and should be called
+ repeatedly to update all views.
- This polls for events that are ready for any view in the application,
- potentially blocking depending on `timeout`.
+ If a positive timeout is given, then events will be processed for that
+ amount of time, starting from when this function was called. For purely
+ event-driven programs, a timeout of -1.0 can be used to block indefinitely
+ until something happens. For continuously animating programs, a timeout
+ that is a reasonable fraction of the ideal frame period should be used, to
+ minimise input latency by ensuring that as many input events are consumed as
+ possible before drawing. Plugins should always use a timeout of 0.0 to
+ avoid blocking the host.
+
+ @param world The world to update.
- @param world The world for all the views to poll.
@param timeout Maximum time to wait, in seconds. If zero, the call returns
immediately, if negative, the call blocks indefinitely.
- @return PUGL_SUCCESS if events are read, PUGL_FAILURE if not, or an error.
-*/
-PUGL_API PuglStatus
-puglPollEvents(PuglWorld* world, double timeout);
-
-/**
- Dispatch any pending events to views.
- This processes all pending events, dispatching them to the appropriate
- views. View event handlers will be called in the scope of this call. This
- function does not block, if no events are pending it will return
- immediately.
+ @return #PUGL_SUCCESS if events are read, #PUGL_FAILURE if not, or an error.
*/
PUGL_API PuglStatus
-puglDispatchEvents(PuglWorld* world);
+puglUpdate(PuglWorld* world, double timeout);
/**
@}
- @anchor view
- @name View
- A view is a drawing region that receives events, which may correspond to a
- top-level window or be embedded in some other window.
+
+ @defgroup view View
+
+ A drawable region that receives events.
+
+ A view can be thought of as a window, but does not necessarily correspond to
+ a top-level window in a desktop environment. For example, a view can be
+ embedded in some other window, or represent an embedded system where there
+ is no concept of multiple windows at all.
+
@{
*/
/**
- A Pugl view.
+ A drawable region that receives events.
*/
typedef struct PuglViewImpl PuglView;
/**
+ A graphics backend.
+
+ The backend dictates how graphics are set up for a view, and how drawing is
+ performed. A backend must be set by calling puglSetBackend() before
+ realising a view.
+
+ If you are using a local copy of Pugl, it is possible to implement a custom
+ backend. See the definition of `PuglBackendImpl` in the source code for
+ details.
+*/
+typedef struct PuglBackendImpl PuglBackend;
+
+/**
+ A native view handle.
+
+ X11: This is a `Window`.
+
+ MacOS: This is a pointer to an `NSView*`.
+
+ Windows: This is a `HWND`.
+*/
+typedef uintptr_t PuglNativeView;
+
+/**
+ Handle for a view's opaque user data.
+*/
+typedef void* PuglHandle;
+
+/**
+ A hint for configuring a view.
+*/
+typedef enum {
+ PUGL_USE_COMPAT_PROFILE, ///< Use compatible (not core) OpenGL profile
+ PUGL_USE_DEBUG_CONTEXT, ///< True to use a debug OpenGL context
+ 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_SWAP_INTERVAL, ///< Number of frames between buffer swaps
+ PUGL_RESIZABLE, ///< True if view should be resizable
+ PUGL_IGNORE_KEY_REPEAT, ///< True if key repeat events are ignored
+
+ PUGL_NUM_VIEW_HINTS
+} PuglViewHint;
+
+/**
+ A special view hint value.
+*/
+typedef enum {
+ PUGL_DONT_CARE = -1, ///< Use best available value
+ PUGL_FALSE = 0, ///< Explicitly false
+ PUGL_TRUE = 1 ///< Explicitly true
+} PuglViewHintValue;
+
+/**
+ A function called when an event occurs.
+*/
+typedef PuglStatus (*PuglEventFunc)(PuglView* view, const PuglEvent* event);
+
+/**
+ @name Setup
+ Functions for creating and destroying a view.
+ @{
+*/
+
+/**
Create a new view.
- A view represents a window, but a window will not be shown until configured
- with the various puglInit functions and shown with puglShowWindow().
+ A newly created view does not correspond to a real system view or window.
+ It must first be configured, then the system view can be created with
+ puglRealize().
*/
PUGL_API PuglView*
puglNewView(PuglWorld* world);
@@ -526,39 +862,55 @@ PUGL_API PuglWorld*
puglGetWorld(PuglView* view);
/**
- Set the handle to be passed to all callbacks.
+ Set the user data for a view.
+
+ This is usually a pointer to a struct that contains all the state which must
+ be accessed by a view. Everything needed to process events should be stored
+ here, not in static variables.
- This is generally a pointer to a struct which contains all necessary state.
- Everything needed in callbacks should be here, not in static variables.
+ The handle is opaque to Pugl and is not interpreted in any way.
*/
PUGL_API void
puglSetHandle(PuglView* view, PuglHandle handle);
/**
- Get the handle to be passed to all callbacks.
+ Get the user data for a view.
*/
PUGL_API PuglHandle
puglGetHandle(PuglView* view);
/**
- Set a hint to configure window properties.
+ Set the graphics backend to use for a view.
+
+ This must be called once to set the graphics backend before calling
+ puglRealize().
- This only has an effect when called before puglCreateWindow().
+ Pugl includes the following backends:
+
+ - puglGlBackend(), declared in pugl_gl.h
+ - puglCairoBackend(), declared in pugl_cairo.h
+
+ Note that backends are modular and not compiled into the main Pugl library
+ to avoid unnecessary dependencies. To use a particular backend,
+ applications must link against the appropriate backend library, or be sure
+ to compile in the appropriate code if using a local copy of Pugl.
*/
PUGL_API PuglStatus
-puglSetViewHint(PuglView* view, PuglViewHint hint, int value);
+puglSetBackend(PuglView* view, const PuglBackend* backend);
/**
- Return true iff the view is currently visible.
+ Set the function to call when an event occurs.
*/
-PUGL_API bool
-puglGetVisible(PuglView* view);
+PUGL_API PuglStatus
+puglSetEventFunc(PuglView* view, PuglEventFunc eventFunc);
/**
- Request a redisplay on the next call to puglDispatchEvents().
+ Set a hint to configure view properties.
+
+ This only has an effect when called before puglRealize().
*/
PUGL_API PuglStatus
-puglPostRedisplay(PuglView* view);
+puglSetViewHint(PuglView* view, PuglViewHint hint, int value);
/**
@}
@@ -570,12 +922,16 @@ puglPostRedisplay(PuglView* view);
/**
Get the current position and size of the view.
+
+ The position is in screen coordinates with an upper left origin.
*/
PUGL_API PuglRect
puglGetFrame(const PuglView* view);
/**
Set the current position and size of the view.
+
+ The position is in screen coordinates with an upper left origin.
*/
PUGL_API PuglStatus
puglSetFrame(PuglView* view, PuglRect frame);
@@ -583,13 +939,14 @@ puglSetFrame(PuglView* view, PuglRect frame);
/**
Set the minimum size of the view.
- To avoid stutter, this should be called before creating the window.
+ If an initial minimum size is known, this should be called before
+ puglRealize() to avoid stutter, though it can be called afterwards as well.
*/
PUGL_API PuglStatus
puglSetMinSize(PuglView* view, int width, int height);
/**
- Set the window aspect ratio range.
+ Set the view aspect ratio range.
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.
@@ -597,6 +954,9 @@ puglSetMinSize(PuglView* view, int width, int height);
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.
+
+ If an initial aspect ratio is known, this should be called before
+ puglRealize() to avoid stutter, though it can be called afterwards as well.
*/
PUGL_API PuglStatus
puglSetAspectRatio(PuglView* view, int minX, int minY, int maxX, int maxY);
@@ -604,20 +964,11 @@ puglSetAspectRatio(PuglView* view, int minX, int minY, int maxX, int maxY);
/**
@}
@name Windows
- Functions for working with top-level windows.
+ Functions for working with system views and the window hierarchy.
@{
*/
/**
- A native window handle.
-
- On X11, this is a Window.
- On OSX, this is an NSView*.
- On Windows, this is a HWND.
-*/
-typedef intptr_t PuglNativeWindow;
-
-/**
Set the title of the window.
This only makes sense for non-embedded views that will have a corresponding
@@ -628,33 +979,45 @@ PUGL_API PuglStatus
puglSetWindowTitle(PuglView* view, const char* title);
/**
- Set the parent window before creating a window (for embedding).
+ Set the parent window for embedding a view in an existing window.
- This only works when called before creating the window with
- puglCreateWindow(), reparenting is not supported.
+ This must be called before puglRealize(), reparenting is not supported.
*/
PUGL_API PuglStatus
-puglSetParentWindow(PuglView* view, PuglNativeWindow parent);
+puglSetParentWindow(PuglView* view, PuglNativeView parent);
/**
Set the transient parent of the window.
- This is used for things like dialogs, to have them associated with the
- window they are a transient child of properly.
+ Set this for transient children like dialogs, to have them properly
+ associated with their parent window. This should be called before
+ puglRealize().
*/
PUGL_API PuglStatus
-puglSetTransientFor(PuglView* view, PuglNativeWindow parent);
+puglSetTransientFor(PuglView* view, PuglNativeView parent);
/**
- Create a window with the settings given by the various puglInit functions.
+ Realise a view by creating a corresponding system view or window.
- @return 1 (pugl does not currently support multiple windows).
+ After this call, the (initially invisible) underlying system view exists and
+ can be accessed with puglGetNativeWindow(). There is currently no
+ corresponding unrealize function, the system view will be destroyed along
+ with the view when puglFreeView() is called.
+
+ The view should be fully configured using the above functions before this is
+ called. This function may only be called once per view.
*/
PUGL_API PuglStatus
-puglCreateWindow(PuglView* view, const char* title);
+puglRealize(PuglView* view);
/**
- Show the current window.
+ Show the view.
+
+ If the view has not yet been realized, the first call to this function will
+ do so automatically.
+
+ If the view is currently hidden, it will be shown and possibly raised to the
+ top depending on the platform.
*/
PUGL_API PuglStatus
puglShowWindow(PuglView* view);
@@ -666,115 +1029,101 @@ PUGL_API PuglStatus
puglHideWindow(PuglView* view);
/**
+ Return true iff the view is currently visible.
+*/
+PUGL_API bool
+puglGetVisible(const PuglView* view);
+
+/**
Return the native window handle.
*/
-PUGL_API PuglNativeWindow
+PUGL_API PuglNativeView
puglGetNativeWindow(PuglView* view);
/**
@}
- @name Graphics Context
- Functions for working with the drawing context.
+ @name Graphics
+ Functions for working with the graphics context and scheduling redisplays.
@{
*/
/**
- Graphics backend interface.
-*/
-typedef struct PuglBackendImpl PuglBackend;
+ Get the graphics context.
-/**
- OpenGL extension function.
-*/
-typedef void (*PuglGlFunc)(void);
-
-/**
- Set the graphics backend to use.
+ This is a backend-specific context used for drawing if the backend graphics
+ API requires one. It is only available during an expose.
- 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 PuglStatus
-puglSetBackend(PuglView* view, const PuglBackend* backend);
-
-/**
- Return the address of an OpenGL extension function.
-*/
-PUGL_API PuglGlFunc
-puglGetProcAddress(const char* name);
-
-/**
- Get the drawing context.
-
- 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`.
+ @return
+ - OpenGL: `NULL`.
+ - Cairo: A pointer to a
+ [`cairo_t`](http://www.cairographics.org/manual/cairo-cairo-t.html).
*/
PUGL_API void*
puglGetContext(PuglView* view);
/**
- Enter the drawing context.
+ Request a redisplay for the entire view.
- 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.
+ This will cause an expose event to be dispatched later. If called from
+ within the event handler, the expose should arrive at the end of the current
+ event loop iteration, though this is not strictly guaranteed on all
+ platforms. If called elsewhere, an expose will be enqueued to be processed
+ in the next event loop iteration.
*/
PUGL_API PuglStatus
-puglEnterContext(PuglView* view, bool drawing);
+puglPostRedisplay(PuglView* view);
/**
- Leave the drawing context.
-
- This must be called after puglEnterContext() with a matching `drawing`
- parameter.
+ Request a redisplay of the given rectangle within the view.
- @param view The view being left.
- @param drawing If true, finish drawing, for example by swapping buffers.
+ This has the same semantics as puglPostRedisplay(), but allows giving a
+ precise region for redrawing only a portion of the view.
*/
PUGL_API PuglStatus
-puglLeaveContext(PuglView* view, bool drawing);
+puglPostRedisplayRect(PuglView* view, PuglRect rect);
/**
@}
@anchor interaction
@name Interaction
- Interacting with the system and user with events.
+ Functions for interacting with the user and window system.
@{
*/
/**
- A function called when an event occurs.
-*/
-typedef PuglStatus (*PuglEventFunc)(PuglView* view, const PuglEvent* event);
-
-/**
- Set the function to call when an event occurs.
+ Grab the keyboard input focus.
*/
PUGL_API PuglStatus
-puglSetEventFunc(PuglView* view, PuglEventFunc eventFunc);
+puglGrabFocus(PuglView* view);
/**
- Return true iff `view` has the input focus.
+ Return whether `view` has the keyboard input focus.
*/
PUGL_API bool
puglHasFocus(const PuglView* view);
/**
- Grab the input focus.
+ Set the clipboard contents.
+
+ This sets the system clipboard contents, which can be retrieved with
+ puglGetClipboard() or pasted into other applications.
+
+ @param view The view.
+ @param type The MIME type of the data, "text/plain" is assumed if `NULL`.
+ @param data The data to copy to the clipboard.
+ @param len The length of data in bytes (including terminator if necessary).
*/
PUGL_API PuglStatus
-puglGrabFocus(PuglView* view);
+puglSetClipboard(PuglView* view,
+ const char* type,
+ const void* data,
+ size_t len);
/**
- Get clipboard contents.
+ Get the clipboard contents.
+
+ This gets the system clipboard contents, which may have been set with
+ puglSetClipboard() or copied from another application.
@param view The view.
@param[out] type Set to the MIME type of the data.
@@ -785,28 +1134,68 @@ PUGL_API const void*
puglGetClipboard(PuglView* view, const char** type, size_t* len);
/**
- Set clipboard contents.
+ Request user attention.
- @param view The view.
- @param type The MIME type of the data, "text/plain" is assumed if NULL.
- @param data The data to copy to the clipboard.
- @param len The length of data in bytes (including terminator if necessary).
+ 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 a flashing task bar entry or bouncing application icon.
*/
PUGL_API PuglStatus
-puglSetClipboard(PuglView* view,
- const char* type,
- const void* data,
- size_t len);
+puglRequestAttention(PuglView* view);
/**
- Request user attention.
+ Activate a repeating timer event.
- 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.
+ This starts a timer which will send a #PuglEventTimer to `view` every
+ `timeout` seconds. This can be used to perform some action in a view at a
+ regular interval with relatively low frequency. Note that the frequency of
+ timer events may be limited by how often puglUpdate() is called.
+
+ If the given timer already exists, it is replaced.
+
+ @param view The view to begin seding #PUGL_TIMER events to.
+
+ @param id The identifier for this timer. This is an application-specific ID
+ that should be a low number, typically the value of a constant or `enum`
+ that starts from 0. There is a platform-specific limit to the number of
+ supported timers, and overhead associated with each, so applications should
+ create only a few timers and perform several tasks in one if necessary.
+
+ @param timeout The period, in seconds, of this timer. This is not
+ guaranteed to have a resolution better than 10ms (the maximum timer
+ resolution on Windows) and may be rounded up if it is too short. On X11 and
+ MacOS, a resolution of about 1ms can usually be relied on.
+
+ @return #PUGL_SUCCESS, #PUGL_FAILURE if timers are not supported on this
+ system, or an error code.
*/
PUGL_API PuglStatus
-puglRequestAttention(PuglView* view);
+puglStartTimer(PuglView* view, uintptr_t id, double timeout);
+
+/**
+ Stop an active timer.
+
+ @param view The view that the timer is set for.
+ @param id The ID previously passed to puglStartTimer().
+ @return #PUGL_SUCCESS, or #PUGL_FAILURE if no such timer was found.
+*/
+PUGL_API PuglStatus
+puglStopTimer(PuglView* view, uintptr_t id);
+
+/**
+ Send an event to a view via the window system.
+
+ If supported, the event will be delivered to the view via the event loop
+ like other events. Note that this function only works for certain event
+ types, and will return PUGL_UNSUPPORTED_TYPE for events that are not
+ supported.
+*/
+PUGL_API PuglStatus
+puglSendEvent(PuglView* view, const PuglEvent* event);
+
+/**
+ @}
+*/
#ifndef PUGL_DISABLE_DEPRECATED
@@ -816,19 +1205,22 @@ puglRequestAttention(PuglView* view);
@{
*/
-#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
+/**
+ A native window handle.
+
+ X11: This is a `Window`.
+
+ MacOS: This is a pointer to an `NSView*`.
+
+ Windows: This is a `HWND`.
+*/
+typedef uintptr_t PuglNativeWindow;
/**
Create a Pugl application and view.
To create a window, call the various puglInit* functions as necessary, then
- call puglCreateWindow().
+ call puglRealize().
@deprecated Use puglNewApp() and puglNewView().
@@ -842,7 +1234,7 @@ puglInit(const int* pargc, char** argv)
(void)pargc;
(void)argv;
- return puglNewView(puglNewWorld());
+ return puglNewView(puglNewWorld(PUGL_MODULE, 0));
}
/**
@@ -928,7 +1320,7 @@ puglInitTransientFor(PuglView* view, uintptr_t parent)
/**
Enable or disable resizing before creating a window.
- @deprecated Use puglSetViewHint() with @ref PUGL_RESIZABLE.
+ @deprecated Use puglSetViewHint() with #PUGL_RESIZABLE.
*/
static inline PUGL_DEPRECATED_BY("puglSetViewHint") void
puglInitResizable(PuglView* view, bool resizable)
@@ -954,7 +1346,7 @@ puglGetSize(PuglView* view, int* width, int* height)
/**
Ignore synthetic repeated key events.
- @deprecated Use puglSetViewHint() with @ref PUGL_IGNORE_KEY_REPEAT.
+ @deprecated Use puglSetViewHint() with #PUGL_IGNORE_KEY_REPEAT.
*/
static inline PUGL_DEPRECATED_BY("puglSetViewHint") void
puglIgnoreKeyRepeat(PuglView* view, bool ignore)
@@ -992,7 +1384,22 @@ puglInitWindowParent(PuglView* view, PuglNativeWindow parent)
static inline PUGL_DEPRECATED_BY("puglSetBackend") int
puglInitBackend(PuglView* view, const PuglBackend* backend)
{
- return puglSetBackend(view, backend);
+ return (int)puglSetBackend(view, backend);
+}
+
+/**
+ Realise a view by creating a corresponding system view or window.
+
+ The view should be fully configured using the above functions before this is
+ called. This function may only be called once per view.
+
+ @deprecated Use puglRealize(), or just show the view.
+*/
+static inline PUGL_DEPRECATED_BY("puglRealize") PuglStatus
+puglCreateWindow(PuglView* view, const char* title)
+{
+ puglSetWindowTitle(view, title);
+ return puglRealize(view);
}
/**
@@ -1020,6 +1427,70 @@ puglWaitForEvent(PuglView* view);
PUGL_API PUGL_DEPRECATED_BY("puglDispatchEvents") PuglStatus
puglProcessEvents(PuglView* view);
+/**
+ Poll for events that are ready to be processed.
+
+ This polls for events that are ready for any view in the world, potentially
+ blocking depending on `timeout`.
+
+ @param world The world to poll for events.
+
+ @param timeout Maximum time to wait, in seconds. If zero, the call returns
+ immediately, if negative, the call blocks indefinitely.
+
+ @return #PUGL_SUCCESS if events are read, #PUGL_FAILURE if not, or an error.
+
+ @deprecated Use puglUpdate().
+*/
+PUGL_API PUGL_DEPRECATED_BY("puglUpdate") PuglStatus
+puglPollEvents(PuglWorld* world, double timeout);
+
+/**
+ Dispatch any pending events to views.
+
+ This processes all pending events, dispatching them to the appropriate
+ views. View event handlers will be called in the scope of this call. This
+ function does not block, if no events are pending then it will return
+ immediately.
+
+ @deprecated Use puglUpdate().
+*/
+PUGL_API PUGL_DEPRECATED_BY("puglUpdate") PuglStatus
+puglDispatchEvents(PuglWorld* world);
+
+/**
+ Enter the graphics context.
+
+ Note that, unlike some similar libraries, Pugl automatically enters and
+ leaves the graphics context when required and application should not
+ normally do this. Drawing in Pugl is only allowed during the processing of
+ an expose event.
+
+ However, this can be used to enter the graphics context elsewhere, for
+ example to call any GL functions during setup.
+
+ @param view The view being entered.
+ @param drawing If true, prepare for drawing.
+
+ @deprecated Set up graphics when a #PUGL_CREATE event is received.
+*/
+PUGL_API PUGL_DEPRECATED_BY("PUGL_CREATE") PuglStatus
+puglEnterContext(PuglView* view, bool drawing);
+
+/**
+ Leave the graphics context.
+
+ 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.
+
+ @deprecated Shut down graphics when a #PUGL_DESTROY event is received.
+*/
+PUGL_API PUGL_DEPRECATED_BY("PUGL_DESTROY") PuglStatus
+puglLeaveContext(PuglView* view, bool drawing);
+
#endif /* PUGL_DISABLE_DEPRECATED */
/**
@@ -1027,8 +1498,6 @@ puglProcessEvents(PuglView* view);
@}
*/
-#ifdef __cplusplus
-} /* extern "C" */
-#endif
+PUGL_END_DECLS
-#endif /* PUGL_H_INCLUDED */
+#endif /* PUGL_PUGL_H */
diff --git a/pugl/pugl/pugl.hpp b/pugl/pugl/pugl.hpp
index dee8c17..73cfe2a 100644
--- a/pugl/pugl/pugl.hpp
+++ b/pugl/pugl/pugl.hpp
@@ -18,19 +18,32 @@
@file pugl.hpp Pugl C++ API wrapper.
*/
-#ifndef PUGL_HPP_INCLUDED
-#define PUGL_HPP_INCLUDED
+#ifndef PUGL_PUGL_HPP
+#define PUGL_PUGL_HPP
#include "pugl/pugl.h"
/**
- @defgroup puglmm Puglmm
- Pugl C++ API wrapper.
+ @defgroup puglxx C++
+
+ C++ API wrapper.
+
+ @ingroup pugl_api
@{
*/
+/**
+ Pugl C++ API namespace.
+*/
namespace pugl {
+/**
+ A drawable region that receives events.
+
+ This is a thin wrapper for a PuglView that contains only a pointer.
+
+ @ingroup puglxx
+*/
class View {
public:
View(int* pargc, char** argv)
@@ -104,4 +117,4 @@ private:
@}
*/
-#endif /* PUGL_HPP_INCLUDED */
+#endif /* PUGL_PUGL_HPP */
diff --git a/pugl/pugl/pugl_cairo.h b/pugl/pugl/pugl_cairo.h
new file mode 100644
index 0000000..e71072e
--- /dev/null
+++ b/pugl/pugl/pugl_cairo.h
@@ -0,0 +1,49 @@
+/*
+ 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.h Declaration of Cairo backend accessor.
+*/
+
+#ifndef PUGL_PUGL_CAIRO_H
+#define PUGL_PUGL_CAIRO_H
+
+#include "pugl/pugl.h"
+
+PUGL_BEGIN_DECLS
+
+/**
+ @defgroup cairo Cairo
+ Cairo graphics support.
+ @ingroup pugl_api
+ @{
+*/
+
+/**
+ Cairo graphics backend accessor.
+
+ Pass the return value to puglInitBackend() to draw to a view with Cairo.
+*/
+PUGL_API const PuglBackend*
+puglCairoBackend(void);
+
+/**
+ @}
+*/
+
+PUGL_END_DECLS
+
+#endif // PUGL_PUGL_CAIRO_H
diff --git a/pugl/pugl/pugl_cairo_backend.h b/pugl/pugl/pugl_cairo_backend.h
index 3330c08..3f8cec3 100644
--- a/pugl/pugl/pugl_cairo_backend.h
+++ b/pugl/pugl/pugl_cairo_backend.h
@@ -14,29 +14,10 @@
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);
+#ifndef PUGL_PUGL_CAIRO_BACKEND_H
+#define PUGL_PUGL_CAIRO_BACKEND_H
-#ifdef __cplusplus
-} /* extern "C" */
-#endif
+#warning "This header is deprecated, use pugl/pugl_cairo.h instead."
+#include "pugl/pugl_cairo.h"
-#endif // PUGL_CAIRO_BACKEND_H
+#endif // PUGL_PUGL_CAIRO_BACKEND_H
diff --git a/pugl/pugl/pugl_gl.h b/pugl/pugl/pugl_gl.h
new file mode 100644
index 0000000..9c5fa94
--- /dev/null
+++ b/pugl/pugl/pugl_gl.h
@@ -0,0 +1,60 @@
+/*
+ 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.h OpenGL-specific API.
+*/
+
+#ifndef PUGL_PUGL_GL_H
+#define PUGL_PUGL_GL_H
+
+#include "pugl/pugl.h"
+
+PUGL_BEGIN_DECLS
+
+/**
+ @defgroup gl OpenGL
+ OpenGL graphics support.
+ @ingroup pugl_api
+ @{
+*/
+
+/**
+ OpenGL extension function.
+*/
+typedef void (*PuglGlFunc)(void);
+
+/**
+ Return the address of an OpenGL extension function.
+*/
+PUGL_API PuglGlFunc
+puglGetProcAddress(const char* name);
+
+/**
+ OpenGL graphics backend.
+
+ Pass the return value to puglSetBackend() to draw to a view with OpenGL.
+*/
+PUGL_API const PuglBackend*
+puglGlBackend(void);
+
+PUGL_END_DECLS
+
+/**
+ @}
+*/
+
+#endif // PUGL_PUGL_GL_H
diff --git a/pugl/pugl/pugl_gl_backend.h b/pugl/pugl/pugl_gl_backend.h
index 5913b95..e1b9a15 100644
--- a/pugl/pugl/pugl_gl_backend.h
+++ b/pugl/pugl/pugl_gl_backend.h
@@ -14,29 +14,10 @@
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);
+#ifndef PUGL_PUGL_GL_BACKEND_H
+#define PUGL_PUGL_GL_BACKEND_H
-#ifdef __cplusplus
-} /* extern "C" */
-#endif
+#warning "This header is deprecated, use pugl/pugl_gl.h instead."
+#include "pugl/pugl_gl.h"
-#endif // PUGL_GL_BACKEND_H
+#endif // PUGL_PUGL_GL_BACKEND_H
diff --git a/pugl/pugl/pugl_stub.h b/pugl/pugl/pugl_stub.h
new file mode 100644
index 0000000..da918aa
--- /dev/null
+++ b/pugl/pugl/pugl_stub.h
@@ -0,0 +1,103 @@
+/*
+ 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 pugl_stub.h Stub backend functions and accessor declaration.
+*/
+
+#ifndef PUGL_PUGL_STUB_H
+#define PUGL_PUGL_STUB_H
+
+#include "pugl/pugl.h"
+
+PUGL_BEGIN_DECLS
+
+/**
+ @defgroup stub Stub
+
+ Stub graphics backend.
+
+ The stub backend functions do nothing and always
+ return success. These do not make for a usable backend on their own since
+ the platform implementation would fail to create a window, but are useful
+ for other backends to reuse since not all need non-trivial implementations
+ of every backend function.
+
+ @ingroup pugl_api
+ @{
+*/
+
+/**
+ Stub graphics backend.
+
+ This backend just creates a simple native window without setting up any
+ portable graphics API.
+*/
+PUGL_API
+const PuglBackend*
+puglStubBackend(void);
+
+static inline PuglStatus
+puglStubConfigure(PuglView* view)
+{
+ (void)view;
+ return PUGL_SUCCESS;
+}
+
+static inline PuglStatus
+puglStubCreate(PuglView* view)
+{
+ (void)view;
+ return PUGL_SUCCESS;
+}
+
+static inline PuglStatus
+puglStubDestroy(PuglView* view)
+{
+ (void)view;
+ return PUGL_SUCCESS;
+}
+
+static inline PuglStatus
+puglStubEnter(PuglView* view, const PuglEventExpose* expose)
+{
+ (void)view;
+ (void)expose;
+ return PUGL_SUCCESS;
+}
+
+static inline PuglStatus
+puglStubLeave(PuglView* view, const PuglEventExpose* expose)
+{
+ (void)view;
+ (void)expose;
+ return PUGL_SUCCESS;
+}
+
+static inline void*
+puglStubGetContext(PuglView* view)
+{
+ (void)view;
+ return NULL;
+}
+
+/**
+ @}
+*/
+
+PUGL_END_DECLS
+
+#endif // PUGL_PUGL_STUB_H
diff --git a/pugl/pugl/glew.h b/pugl/pugl/pugl_stub_backend.h
index f26ff93..e5aa513 100644
--- a/pugl/pugl/glew.h
+++ b/pugl/pugl/pugl_stub_backend.h
@@ -1,5 +1,5 @@
/*
- Copyright 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
@@ -14,18 +14,10 @@
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
-/**
- @file glew.h Portable header wrapper for glew.h.
+#ifndef PUGL_PUGL_STUB_BACKEND_H
+#define PUGL_PUGL_STUB_BACKEND_H
- Unfortunately, GL includes vary across platforms so this header allows for
- pure portable programs.
-*/
+#warning "This header is deprecated, use pugl/pugl_stub.h instead."
+#include "pugl/pugl_stub.h"
-#ifdef __APPLE__
-# include "OpenGL/glew.h"
-#else
-# ifdef _WIN32
-# include <windows.h> /* Broken Windows GL headers require this */
-# endif
-# include "GL/glew.h"
-#endif
+#endif // PUGL_PUGL_STUB_BACKEND_H
diff --git a/pugl/shaders/rect.frag b/pugl/shaders/rect.frag
new file mode 100644
index 0000000..5e3af9d
--- /dev/null
+++ b/pugl/shaders/rect.frag
@@ -0,0 +1,35 @@
+#version 330 core
+
+/* The fragment shader uses the UV coordinates to calculate whether it is in
+ the T, R, B, or L border. These are then mixed with the border color, and
+ their inverse is mixed with the fill color, to calculate the fragment color.
+ For example, if we are in the top border, then T=1, so the border mix factor
+ TRBL=1, and the fill mix factor (1-TRBL) is 0.
+
+ The use of pixel units here is handy because the border width can be
+ specified precisely in pixels to draw sharp lines. The border width is just
+ hardcoded, but could be made a uniform or vertex attribute easily enough. */
+
+noperspective in vec2 f_uv;
+noperspective in vec2 f_size;
+noperspective in vec4 f_fillColor;
+
+layout(location = 0) out vec4 FragColor;
+
+void
+main()
+{
+ const float borderWidth = 2.0;
+
+ vec4 borderColor = f_fillColor + vec4(0.0, 0.4, 0.4, 0.0);
+ float t = step(borderWidth, f_uv[1]);
+ float r = step(borderWidth, f_size.x - f_uv[0]);
+ float b = step(borderWidth, f_size.y - f_uv[1]);
+ float l = step(borderWidth, f_uv[0]);
+ float fillMix = t * r * b * l;
+ float borderMix = 1.0 - fillMix;
+ vec4 fill = fillMix * f_fillColor;
+ vec4 border = borderMix * borderColor;
+
+ FragColor = fill + border;
+}
diff --git a/pugl/shaders/rect.vert b/pugl/shaders/rect.vert
new file mode 100644
index 0000000..bf2e951
--- /dev/null
+++ b/pugl/shaders/rect.vert
@@ -0,0 +1,34 @@
+#version 330 core
+
+/* The vertex shader is trivial, but forwards scaled UV coordinates (in pixels)
+ to the fragment shader for drawing the border. */
+
+uniform mat4 u_projection;
+
+layout(location = 0) in vec2 v_position;
+layout(location = 1) in vec2 v_origin;
+layout(location = 2) in vec2 v_size;
+layout(location = 3) in vec4 v_fillColor;
+
+noperspective out vec2 f_uv;
+noperspective out vec2 f_size;
+noperspective out vec4 f_fillColor;
+
+void
+main()
+{
+ // clang-format off
+ mat4 m = mat4(v_size[0], 0.0, 0.0, 0.0,
+ 0.0, v_size[1], 0.0, 0.0,
+ 0.0, 0.0, 1.0, 0.0,
+ v_origin[0], v_origin[1], 0.0, 1.0);
+ // clang-format on
+
+ mat4 MVP = u_projection * m;
+
+ f_uv = v_position * v_size;
+ f_size = v_size;
+ f_fillColor = v_fillColor;
+
+ gl_Position = MVP * vec4(v_position, 0.0, 1.0);
+}
diff --git a/pugl/test/pugl_gl3_test.c b/pugl/test/pugl_gl3_test.c
deleted file mode 100644
index c939930..0000000
--- a/pugl/test/pugl_gl3_test.c
+++ /dev/null
@@ -1,395 +0,0 @@
-/*
- 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_gl3_test.c A simple test of OpenGL 3 with Pugl.
-
- This is an example of using OpenGL for pixel-perfect 2D drawing. It uses
- pixel coordinates for positions and sizes so that things work roughly like a
- typical 2D graphics API.
-
- The program draws a bunch of rectangles with borders, with one draw call per
- rectangle (the shader draws the borders). Rectangle attributes are
- controlled via uniform variables. This is certainly not the fastest way to
- do this: it is probably CPU and/or I/O bound, but serves as a decent very
- rough benchmark for how many draw calls you can get away with.
-
- A better (if slightly more GPU memory intensive) way to do this would be to
- put everything in vertex attributes, jam all the rectangle data into a
- single buffer, and draw the whole thing with a single draw call. That way
- would probably be GPU bound instead, and show a difference between alpha
- blending and depth testing for many overlapped rectangles.
-*/
-
-#define GL_SILENCE_DEPRECATION 1
-
-#include "shader_utils.h"
-#include "test_utils.h"
-
-#include "glad/glad.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 <stdlib.h>
-#include <string.h>
-
-static const int defaultWidth = 512;
-static const int defaultHeight = 512;
-
-typedef struct
-{
- float pos[2];
- float size[2];
- float fillColor[4];
- float borderColor[4];
-} Rect;
-
-// clang-format off
-static const GLfloat rectVertices[] = {
- 0.0f, 0.0f, // TL
- 1.0f, 0.0f, // TR
- 0.0f, 1.0f, // BL
- 1.0f, 1.0f, // BR
-};
-// clang-format on
-
-static const GLuint rectIndices[4] = {0, 1, 2, 3};
-
-/* The vertex shader is trivial, but forwards scaled UV coordinates (in pixels)
- to the fragment shader for drawing the border. */
-static const char* vertexSource = //
- "#version 330\n"
- "uniform mat4 MVP;\n"
- "uniform vec2 u_size;\n"
- "in vec2 v_position;\n"
- "noperspective out vec2 f_uv;\n"
- "void main() {\n"
- " f_uv = v_position * u_size;\n"
- " gl_Position = MVP * vec4(v_position, 0.0, 1.0);\n"
- "}\n";
-
-/* The fragment shader uses the UV coordinates to calculate whether it is in
- the T, R, B, or L border. These are then mixed with the border color, and
- their inverse is mixed with the fill color, to calculate the fragment color.
- For example, if we are in the top border, then T=1, so the border mix factor
- TRBL=1, and the fill mix factor (1-TRBL) is 0.
-
- The use of pixel units here is handy because the border width can be
- specified precisely in pixels to draw sharp lines. The border width is just
- hardcoded, but could be made a uniform or vertex attribute easily enough. */
-static const char* fragmentSource = //
- "#version 330\n"
- "uniform vec2 u_size;\n"
- "uniform vec4 u_borderColor;\n"
- "uniform vec4 u_fillColor;\n"
- "noperspective in vec2 f_uv;\n"
- "layout(location = 0) out vec4 FragColor;\n"
- "void main() {\n"
- " const float border_width = 2.0;\n"
- "\n"
- " float t = step(border_width, f_uv[1]);\n"
- " float r = step(border_width, u_size.x - f_uv[0]);\n"
- " float b = step(border_width, u_size.y - f_uv[1]);\n"
- " float l = step(border_width, f_uv[0]);\n"
- " float fill_mix = t * r * b * l;\n"
- " float border_mix = 1.0 - fill_mix;\n"
- " vec4 fill = fill_mix * u_fillColor;\n"
- " vec4 border = border_mix * u_borderColor;\n"
- " FragColor = fill + border;\n"
- "}\n";
-
-typedef struct
-{
- PuglTestOptions opts;
- PuglWorld* world;
- PuglView* view;
- size_t numRects;
- Rect* rects;
- Program drawRect;
- GLuint vao;
- GLuint vbo;
- GLuint ibo;
- GLint u_MVP;
- GLint u_size;
- GLint u_fillColor;
- GLint u_borderColor;
- unsigned framesDrawn;
- int quit;
-} PuglTestApp;
-
-static void
-onConfigure(PuglView* view, double width, double height)
-{
- (void)view;
-
- glEnable(GL_BLEND);
- glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
- glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
- glViewport(0, 0, (int)width, (int)height);
-}
-
-static void
-drawRect(const PuglTestApp* app, const Rect* rect, mat4 projection)
-{
- /* The vertex data is always the same: a normalized rectangle from (0, 0)
- to (1, 1). We use the MVP matrix to scale and translate this to the
- desired screen coordinates. */
-
- // Construct model matrix to scale/translate to screen coordinates
- mat4 m;
- mat4Identity(m);
- mat4Translate(m, rect->pos[0], rect->pos[1], 0);
- m[0][0] = rect->size[0];
- m[1][1] = rect->size[1];
-
- // Combine them into the final MVP matrix and set uniform
- mat4 mvp;
- mat4Mul(mvp, projection, m);
- glUniformMatrix4fv(app->u_MVP, 1, GL_FALSE, (const GLfloat*)&mvp);
-
- // Set uniforms for the various rectangle attributes
- glUniform2fv(app->u_size, 1, rect->size);
- glUniform4fv(app->u_fillColor, 1, rect->fillColor);
- glUniform4fv(app->u_borderColor, 1, rect->borderColor);
-
- // Draw
- glDrawElements(GL_TRIANGLE_STRIP, 4, GL_UNSIGNED_INT, 0);
-}
-
-static void
-onExpose(PuglView* view)
-{
- PuglTestApp* app = (PuglTestApp*)puglGetHandle(view);
- const PuglRect frame = puglGetFrame(view);
- const double time = puglGetTime(puglGetWorld(view));
-
- // Construct projection matrix for 2D window surface (in pixels)
- mat4 proj;
- mat4Ortho(proj,
- 0.0f,
- (float)frame.width,
- 0.0f,
- (float)frame.height,
- -1.0f,
- 1.0f);
-
- // Clear and bind everything that is the same for every rect
- glClear(GL_COLOR_BUFFER_BIT);
- glUseProgram(app->drawRect.program);
- glBindVertexArray(app->vao);
- glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, app->ibo);
-
- for (size_t i = 0; i < app->numRects; ++i) {
- Rect* rect = &app->rects[i];
-
- // Move rect around in an arbitrary way that looks cool
- rect->pos[0] = (float)(frame.width - rect->size[0]) *
- (sinf((float)time * rect->size[0] / 100.0f) + 1.0f) /
- 2.0f;
- rect->pos[1] = (float)(frame.height - rect->size[1]) *
- (cosf((float)time * rect->size[1] / 100.0f) + 1.0f) /
- 2.0f;
-
- drawRect(app, rect, proj);
- }
-
- ++app->framesDrawn;
-}
-
-static PuglStatus
-onEvent(PuglView* view, const PuglEvent* event)
-{
- PuglTestApp* app = (PuglTestApp*)puglGetHandle(view);
-
- printEvent(event, "Event: ");
-
- switch (event->type) {
- case PUGL_CONFIGURE:
- onConfigure(view, event->configure.width, event->configure.height);
- break;
- case PUGL_EXPOSE: onExpose(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;
- default: break;
- }
-
- return PUGL_SUCCESS;
-}
-
-static Rect*
-makeRects(const size_t numRects, const int width, const int height)
-{
- const int minSize = width / 32;
- const int maxSize = width / 4;
- const float boxAlpha = 0.6f;
-
- Rect* rects = (Rect*)calloc(numRects, sizeof(Rect));
- for (size_t i = 0; i < numRects; ++i) {
- rects[i].pos[0] = (float)(rand() % width);
- rects[i].pos[1] = (float)(rand() % height);
- rects[i].size[0] = (float)minSize + rand() % (maxSize - minSize);
- rects[i].size[1] = (float)minSize + rand() % (maxSize - minSize);
-
- rects[i].fillColor[1] = (rand() % numRects) / ((float)numRects - 0.4f);
- rects[i].fillColor[2] = (rand() % numRects) / ((float)numRects - 0.4f);
- rects[i].fillColor[3] = boxAlpha;
-
- rects[i].borderColor[1] = rects[i].fillColor[1] + 0.4f;
- rects[i].borderColor[2] = rects[i].fillColor[1] + 0.4f;
- rects[i].borderColor[3] = boxAlpha;
- }
-
- return rects;
-}
-
-int
-main(int argc, char** argv)
-{
- PuglTestApp app;
- memset(&app, 0, sizeof(app));
-
- const PuglRect frame = {0, 0, defaultWidth, defaultHeight};
-
- // Parse command line options
- app.numRects = 1024;
- app.opts = puglParseTestOptions(&argc, &argv);
- if (app.opts.help) {
- puglPrintTestUsage("pugl_gl3_test", "[NUM_RECTS]");
- return 1;
- }
-
- // Parse number of rectangles argument, if given
- if (argc == 1) {
- char* endptr = NULL;
- app.numRects = (size_t)strtol(argv[0], &endptr, 10);
- if (endptr != argv[0] + strlen(argv[0])) {
- puglPrintTestUsage("pugl_gl3_test", "[NUM_RECTS]");
- return 1;
- }
- }
-
- // Create world, view, and rect data
- app.world = puglNewWorld();
- app.view = puglNewView(app.world);
- app.rects = makeRects(app.numRects, defaultWidth, defaultHeight);
-
- // Set up world and view
- puglSetClassName(app.world, "PuglGL3Test");
- puglSetFrame(app.view, frame);
- puglSetMinSize(app.view, defaultWidth / 4, defaultHeight / 4);
- puglSetAspectRatio(app.view, 1, 1, 16, 9);
- puglSetBackend(app.view, puglGlBackend());
- puglSetViewHint(app.view, PUGL_USE_COMPAT_PROFILE, PUGL_TRUE);
- puglSetViewHint(app.view, PUGL_CONTEXT_VERSION_MAJOR, 3);
- puglSetViewHint(app.view, PUGL_CONTEXT_VERSION_MINOR, 3);
- puglSetViewHint(app.view, PUGL_RESIZABLE, app.opts.resizable);
- puglSetViewHint(app.view, PUGL_SAMPLES, app.opts.samples);
- puglSetViewHint(app.view, PUGL_DOUBLE_BUFFER, app.opts.doubleBuffer);
- puglSetViewHint(app.view, PUGL_SWAP_INTERVAL, app.opts.doubleBuffer);
- puglSetViewHint(app.view, PUGL_IGNORE_KEY_REPEAT, PUGL_TRUE);
- puglSetHandle(app.view, &app);
- puglSetEventFunc(app.view, onEvent);
-
- if (puglCreateWindow(app.view, "Pugl OpenGL 3")) {
- fprintf(stderr, "error: Failed to create window window\n");
- return 1;
- }
-
- // Enter context to set up GL stuff
- puglEnterContext(app.view, false);
-
- // Load GL functions via GLAD
- if (!gladLoadGLLoader((GLADloadproc)&puglGetProcAddress)) {
- fprintf(stderr, "error: Failed to load GL\n");
- puglFreeView(app.view);
- puglFreeWorld(app.world);
- return 1;
- }
-
- // Compile rectangle shaders and program
- app.drawRect = compileProgram(vertexSource, fragmentSource);
- if (!app.drawRect.program) {
- puglFreeView(app.view);
- puglFreeWorld(app.world);
- return 1;
- }
-
- // Get location of rectangle shader uniforms
- app.u_MVP = glGetUniformLocation(app.drawRect.program, "MVP");
- app.u_size = glGetUniformLocation(app.drawRect.program, "u_size");
- app.u_fillColor = glGetUniformLocation(app.drawRect.program, "u_fillColor");
- app.u_borderColor =
- glGetUniformLocation(app.drawRect.program, "u_borderColor");
-
- // Generate/bind a VAO to track state
- glGenVertexArrays(1, &app.vao);
- glBindVertexArray(app.vao);
-
- // Generate/bind a VBO to store vertex position data
- glGenBuffers(1, &app.vbo);
- glBindBuffer(GL_ARRAY_BUFFER, app.vbo);
- glBufferData(GL_ARRAY_BUFFER,
- sizeof(rectVertices),
- rectVertices,
- GL_STATIC_DRAW);
-
- // Set up the first/only attribute, position, as 2 floats from the VBO
- glEnableVertexAttribArray(0);
- glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 2 * sizeof(GLfloat), NULL);
-
- // Set up the IBO to index into the VBO
- glGenBuffers(1, &app.ibo);
- glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, app.ibo);
- glBufferData(GL_ELEMENT_ARRAY_BUFFER,
- sizeof(rectIndices),
- rectIndices,
- GL_STATIC_DRAW);
-
- // Finally ready to go, leave GL context and show the window
- puglLeaveContext(app.view, false);
- puglShowWindow(app.view);
-
- // Grind away, drawing continuously
- PuglFpsPrinter fpsPrinter = {puglGetTime(app.world)};
- while (!app.quit) {
- puglPostRedisplay(app.view);
- puglDispatchEvents(app.world);
- puglPrintFps(app.world, &fpsPrinter, &app.framesDrawn);
- }
-
- // Delete GL stuff
- puglEnterContext(app.view, false);
- glDeleteBuffers(1, &app.ibo);
- glDeleteBuffers(1, &app.vbo);
- glDeleteVertexArrays(1, &app.vao);
- deleteProgram(app.drawRect);
- puglLeaveContext(app.view, false);
-
- // Tear down view and world
- puglFreeView(app.view);
- puglFreeWorld(app.world);
- return 0;
-}
diff --git a/pugl/test/test_redisplay.c b/pugl/test/test_redisplay.c
new file mode 100644
index 0000000..75006cb
--- /dev/null
+++ b/pugl/test/test_redisplay.c
@@ -0,0 +1,132 @@
+/*
+ Copyright 2020 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.
+*/
+
+/*
+ Tests that redisplays posted in the event handler are dispatched at the end
+ of the same event loop iteration.
+*/
+
+#undef NDEBUG
+
+#include "test_utils.h"
+
+#include "pugl/pugl.h"
+#include "pugl/pugl_stub.h"
+
+#include <assert.h>
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdint.h>
+
+#ifdef __APPLE__
+static const double timeout = 1 / 60.0;
+#else
+static const double timeout = -1.0;
+#endif
+
+typedef enum {
+ START,
+ EXPOSED,
+ SHOULD_REDISPLAY,
+ POSTED_REDISPLAY,
+ REDISPLAYED,
+} State;
+
+typedef struct
+{
+ PuglTestOptions opts;
+ PuglWorld* world;
+ PuglView* view;
+ State state;
+} PuglTest;
+
+static const PuglRect redisplayRect = {1, 2, 3, 4};
+static const uintptr_t postRedisplayId = 42;
+
+static PuglStatus
+onEvent(PuglView* view, const PuglEvent* event)
+{
+ PuglTest* test = (PuglTest*)puglGetHandle(view);
+
+ if (test->opts.verbose) {
+ printEvent(event, "Event: ", true);
+ }
+
+ switch (event->type) {
+ case PUGL_EXPOSE:
+ if (test->state == START) {
+ test->state = EXPOSED;
+ } else if (test->state == POSTED_REDISPLAY &&
+ event->expose.x == redisplayRect.x &&
+ event->expose.y == redisplayRect.y &&
+ event->expose.width == redisplayRect.width &&
+ event->expose.height == redisplayRect.height) {
+ test->state = REDISPLAYED;
+ }
+ break;
+
+ case PUGL_CLIENT:
+ if (event->client.data1 == postRedisplayId) {
+ puglPostRedisplayRect(view, redisplayRect);
+ test->state = POSTED_REDISPLAY;
+ }
+ break;
+
+ default: break;
+ }
+
+ return PUGL_SUCCESS;
+}
+
+int
+main(int argc, char** argv)
+{
+ PuglTest app = {puglParseTestOptions(&argc, &argv),
+ puglNewWorld(PUGL_PROGRAM, 0),
+ NULL,
+ START};
+
+ // Set up view
+ app.view = puglNewView(app.world);
+ puglSetClassName(app.world, "Pugl Test");
+ puglSetBackend(app.view, puglStubBackend());
+ puglSetHandle(app.view, &app);
+ puglSetEventFunc(app.view, onEvent);
+
+ // Create and show window
+ assert(!puglRealize(app.view));
+ assert(!puglShowWindow(app.view));
+ while (app.state != EXPOSED) {
+ assert(!puglUpdate(app.world, timeout));
+ }
+
+ // Send a custom event to trigger a redisplay in the event loop
+ const PuglEventClient client = { PUGL_CLIENT, 0, postRedisplayId, 0 };
+ assert(!puglSendEvent(app.view, (const PuglEvent*)&client));
+
+ // Loop until an expose happens in the same iteration as the redisplay
+ app.state = SHOULD_REDISPLAY;
+ while (app.state != REDISPLAYED) {
+ assert(!puglUpdate(app.world, timeout));
+ assert(app.state != POSTED_REDISPLAY);
+ }
+
+ // Tear down
+ puglFreeView(app.view);
+ puglFreeWorld(app.world);
+
+ return 0;
+}
diff --git a/pugl/test/test_show_hide.c b/pugl/test/test_show_hide.c
new file mode 100644
index 0000000..cc2c972
--- /dev/null
+++ b/pugl/test/test_show_hide.c
@@ -0,0 +1,147 @@
+/*
+ Copyright 2020 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.
+*/
+
+/*
+ Tests the basic sanity of view/window create, configure, map, expose, unmap,
+ and destroy events.
+*/
+
+#undef NDEBUG
+
+#include "test_utils.h"
+
+#include "pugl/pugl.h"
+#include "pugl/pugl_stub.h"
+
+#include <assert.h>
+#include <stdbool.h>
+#include <stddef.h>
+
+typedef enum {
+ START,
+ CREATED,
+ CONFIGURED,
+ MAPPED,
+ EXPOSED,
+ UNMAPPED,
+ DESTROYED,
+} State;
+
+typedef struct {
+ PuglTestOptions opts;
+ PuglWorld* world;
+ PuglView* view;
+ State state;
+} PuglTest;
+
+static PuglStatus
+onEvent(PuglView* view, const PuglEvent* event)
+{
+ PuglTest* test = (PuglTest*)puglGetHandle(view);
+
+ if (test->opts.verbose) {
+ printEvent(event, "Event: ", true);
+ }
+
+ switch (event->type) {
+ case PUGL_CREATE:
+ assert(test->state == START);
+ test->state = CREATED;
+ break;
+ case PUGL_CONFIGURE:
+ if (test->state == CREATED) {
+ test->state = CONFIGURED;
+ }
+ break;
+ case PUGL_MAP:
+ assert(test->state == CONFIGURED || test->state == UNMAPPED);
+ test->state = MAPPED;
+ break;
+ case PUGL_EXPOSE:
+ assert(test->state == MAPPED);
+ test->state = EXPOSED;
+ break;
+ case PUGL_UNMAP:
+ assert(test->state == EXPOSED);
+ test->state = UNMAPPED;
+ break;
+ case PUGL_DESTROY:
+ assert(test->state == UNMAPPED);
+ test->state = DESTROYED;
+ break;
+ default:
+ break;
+ }
+
+ return PUGL_SUCCESS;
+}
+
+static void
+tick(PuglWorld* world)
+{
+#ifdef __APPLE__
+ // FIXME: Expose events are not events on MacOS, so we can't block
+ // indefinitely here since it will block forever
+ assert(!puglUpdate(world, 1 / 30.0));
+#else
+ assert(!puglUpdate(world, -1));
+#endif
+}
+
+int
+main(int argc, char** argv)
+{
+ PuglTest test = {puglParseTestOptions(&argc, &argv),
+ puglNewWorld(PUGL_PROGRAM, 0),
+ NULL,
+ START};
+
+ // Set up view
+ test.view = puglNewView(test.world);
+ puglSetClassName(test.world, "Pugl Test");
+ puglSetBackend(test.view, puglStubBackend());
+ puglSetHandle(test.view, &test);
+ puglSetEventFunc(test.view, onEvent);
+
+ // Create initially invisible window
+ assert(!puglRealize(test.view));
+ assert(!puglGetVisible(test.view));
+ while (test.state < CREATED) {
+ tick(test.world);
+ }
+
+ // Show and hide window a couple of times
+ for (unsigned i = 0u; i < 2u; ++i) {
+ assert(!puglShowWindow(test.view));
+ while (test.state != EXPOSED) {
+ tick(test.world);
+ }
+
+ assert(puglGetVisible(test.view));
+ assert(!puglHideWindow(test.view));
+ while (test.state != UNMAPPED) {
+ tick(test.world);
+ }
+ }
+
+ // Tear down
+ assert(!puglGetVisible(test.view));
+ puglFreeView(test.view);
+ assert(test.state == DESTROYED);
+ puglFreeWorld(test.world);
+
+ return 0;
+}
diff --git a/pugl/test/test_timer.c b/pugl/test/test_timer.c
new file mode 100644
index 0000000..58d0652
--- /dev/null
+++ b/pugl/test/test_timer.c
@@ -0,0 +1,160 @@
+/*
+ Copyright 2020 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.
+*/
+
+/*
+ Tests that update events are received and than redisplays they trigger happen
+ immediately in the same event loop iteration.
+*/
+
+#undef NDEBUG
+
+#include "test_utils.h"
+
+#include "pugl/pugl.h"
+#include "pugl/pugl_stub.h"
+
+#include <assert.h>
+#include <math.h>
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <stdio.h>
+
+#ifdef __APPLE__
+static const double timeout = 1 / 60.0;
+#else
+static const double timeout = -1.0;
+#endif
+
+#ifdef _WIN32
+// Windows SetTimer has a maximum resolution of 10ms
+static const double tolerance = 0.011;
+#else
+static const double tolerance = 0.002;
+#endif
+
+static const uintptr_t timerId = 1u;
+static const double timerPeriod = 1 / 60.0;
+
+typedef enum {
+ START,
+ EXPOSED,
+} State;
+
+typedef struct {
+ PuglTestOptions opts;
+ PuglWorld* world;
+ PuglView* view;
+ size_t numAlarms;
+ State state;
+} PuglTest;
+
+static PuglStatus
+onEvent(PuglView* view, const PuglEvent* event)
+{
+ PuglTest* test = (PuglTest*)puglGetHandle(view);
+
+ if (test->opts.verbose) {
+ printEvent(event, "Event: ", true);
+ }
+
+ switch (event->type) {
+ case PUGL_EXPOSE:
+ test->state = EXPOSED;
+ break;
+
+ case PUGL_TIMER:
+ assert(event->timer.id == timerId);
+ ++test->numAlarms;
+ break;
+
+ default:
+ break;
+ }
+
+ return PUGL_SUCCESS;
+}
+
+static double
+roundPeriod(const double period)
+{
+ return floor(period * 1000.0) / 1000.0; // Round down to milliseconds
+}
+
+int
+main(int argc, char** argv)
+{
+ PuglTest app = {puglParseTestOptions(&argc, &argv),
+ puglNewWorld(PUGL_PROGRAM, 0),
+ NULL,
+ 0,
+ START};
+
+ // Set up view
+ app.view = puglNewView(app.world);
+ puglSetClassName(app.world, "Pugl Test");
+ puglSetBackend(app.view, puglStubBackend());
+ puglSetHandle(app.view, &app);
+ puglSetEventFunc(app.view, onEvent);
+
+ // Create and show window
+ assert(!puglRealize(app.view));
+ assert(!puglShowWindow(app.view));
+ while (app.state != EXPOSED) {
+ assert(!puglUpdate(app.world, timeout));
+ }
+
+ // Register a timer with a longer period first
+ assert(!puglStartTimer(app.view, timerId, timerPeriod * 2.0));
+
+ // Replace it with the one we want (to ensure timers are replaced)
+ assert(!puglStartTimer(app.view, timerId, timerPeriod));
+
+ const double startTime = puglGetTime(app.world);
+
+ puglUpdate(app.world, 1.0);
+
+ // Calculate the actual period of the timer
+ const double endTime = puglGetTime(app.world);
+ const double duration = endTime - startTime;
+ const double expectedPeriod = roundPeriod(timerPeriod);
+ const double actualPeriod = roundPeriod(duration / (double)app.numAlarms);
+ const double difference = fabs(actualPeriod - expectedPeriod);
+
+ if (difference > tolerance) {
+ fprintf(stderr,
+ "error: Period not within %f of %f\n",
+ tolerance,
+ expectedPeriod);
+ fprintf(stderr, "note: Actual period %f\n", actualPeriod);
+ }
+
+ assert(difference <= tolerance);
+
+ // Deregister timer and tick once to synchronize
+ assert(!puglStopTimer(app.view, timerId));
+ puglUpdate(app.world, 0.0);
+
+ // Update for a half second and check that we receive no more alarms
+ app.numAlarms = 0;
+ puglUpdate(app.world, 0.5);
+ assert(app.numAlarms == 0);
+
+ puglFreeView(app.view);
+ puglFreeWorld(app.world);
+
+ return 0;
+}
diff --git a/pugl/test/test_update.c b/pugl/test/test_update.c
new file mode 100644
index 0000000..081fb9b
--- /dev/null
+++ b/pugl/test/test_update.c
@@ -0,0 +1,124 @@
+/*
+ Copyright 2020 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.
+*/
+
+/*
+ Tests that update events are received and that the redisplays they trigger
+ happen immediately in the same event loop iteration.
+*/
+
+#undef NDEBUG
+
+#include "test_utils.h"
+
+#include "pugl/pugl.h"
+#include "pugl/pugl_stub.h"
+
+#include <assert.h>
+#include <stdbool.h>
+#include <stddef.h>
+
+#ifdef __APPLE__
+static const double timeout = 1 / 60.0;
+#else
+static const double timeout = -1.0;
+#endif
+
+typedef enum {
+ START,
+ EXPOSED1,
+ UPDATED,
+ EXPOSED2,
+} State;
+
+typedef struct {
+ PuglTestOptions opts;
+ PuglWorld* world;
+ PuglView* view;
+ State state;
+} PuglTest;
+
+static PuglStatus
+onEvent(PuglView* view, const PuglEvent* event)
+{
+ PuglTest* test = (PuglTest*)puglGetHandle(view);
+
+ if (test->opts.verbose) {
+ printEvent(event, "Event: ", true);
+ }
+
+ switch (event->type) {
+ case PUGL_EXPOSE:
+ switch (test->state) {
+ case START:
+ test->state = EXPOSED1;
+ break;
+ case UPDATED:
+ test->state = EXPOSED2;
+ break;
+ default:
+ break;
+ }
+ break;
+
+ case PUGL_UPDATE:
+ if (test->state == EXPOSED1) {
+ puglPostRedisplay(view);
+ test->state = UPDATED;
+ }
+ break;
+
+ default:
+ break;
+ }
+
+ return PUGL_SUCCESS;
+}
+
+int
+main(int argc, char** argv)
+{
+ PuglTest app = {puglParseTestOptions(&argc, &argv),
+ puglNewWorld(PUGL_PROGRAM, 0),
+ NULL,
+ START};
+
+ // Set up view
+ app.view = puglNewView(app.world);
+ puglSetClassName(app.world, "Pugl Test");
+ puglSetBackend(app.view, puglStubBackend());
+ puglSetHandle(app.view, &app);
+ puglSetEventFunc(app.view, onEvent);
+
+ // Create and show window
+ assert(!puglRealize(app.view));
+ assert(!puglShowWindow(app.view));
+
+ // Tick until an expose happens
+ while (app.state < EXPOSED1) {
+ assert(!puglUpdate(app.world, timeout));
+ assert(app.state != UPDATED);
+ }
+
+ // Tick once and ensure the update and the expose it posted both happened
+ assert(!puglUpdate(app.world, 0.0));
+ assert(app.state == EXPOSED2);
+
+ // Tear down
+ puglFreeView(app.view);
+ puglFreeWorld(app.world);
+
+ return 0;
+}
diff --git a/pugl/test/test_utils.h b/pugl/test/test_utils.h
index 8e9c7ff..7d33601 100644
--- a/pugl/test/test_utils.h
+++ b/pugl/test/test_utils.h
@@ -1,5 +1,5 @@
/*
- Copyright 2012-2019 David Robillard <http://drobilla.net>
+ Copyright 2012-2020 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
@@ -14,143 +14,47 @@
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
+#define __STDC_FORMAT_MACROS 1
+
#include "pugl/pugl.h"
-#include <math.h>
+#include <inttypes.h>
+#include <stdarg.h>
+#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>
-typedef struct {
- double lastReportTime;
-} PuglFpsPrinter;
+#ifdef __GNUC__
+# define PUGL_LOG_FUNC(fmt, arg1) __attribute__((format(printf, fmt, arg1)))
+#else
+# define PUGL_LOG_FUNC(fmt, arg1)
+#endif
typedef struct {
int samples;
int doubleBuffer;
+ int sync;
bool continuous;
bool help;
bool ignoreKeyRepeat;
bool resizable;
+ bool verbose;
+ bool errorChecking;
} PuglTestOptions;
-typedef float vec4[4];
-typedef vec4 mat4[4];
-
-static const float cubeStripVertices[] = {
- -1.0f, 1.0f, 1.0f, // Front top left
- 1.0f, 1.0f, 1.0f, // Front top right
- -1.0f, -1.0f, 1.0f, // Front bottom left
- 1.0f, -1.0f, 1.0f, // Front bottom right
- 1.0f, -1.0f, -1.0f, // Back bottom right
- 1.0f, 1.0f, 1.0f, // Front top right
- 1.0f, 1.0f, -1.0f, // Back top right
- -1.0f, 1.0f, 1.0f, // Front top left
- -1.0f, 1.0f, -1.0f, // Back top left
- -1.0f, -1.0f, 1.0f, // Front bottom left
- -1.0f, -1.0f, -1.0f, // Back bottom left
- 1.0f, -1.0f, -1.0f, // Back bottom right
- -1.0f, 1.0f, -1.0f, // Back top left
- 1.0f, 1.0f, -1.0f // Back top right
-};
-
-static const float cubeFrontLineLoop[] = {
- -1.0f, 1.0f, 1.0f, // Front top left
- 1.0f, 1.0f, 1.0f, // Front top right
- 1.0f, -1.0f, 1.0f, // Front bottom right
- -1.0f, -1.0f, 1.0f, // Front bottom left
-};
-
-static const float cubeBackLineLoop[] = {
- -1.0f, 1.0f, -1.0f, // Back top left
- 1.0f, 1.0f, -1.0f, // Back top right
- 1.0f, -1.0f, -1.0f, // Back bottom right
- -1.0f, -1.0f, -1.0f, // Back bottom left
-};
-
-static const float cubeSideLines[] = {
- -1.0f, 1.0f, 1.0f, // Front top left
- -1.0f, 1.0f, -1.0f, // Back top left
-
- -1.0f, -1.0f, 1.0f, // Front bottom left
- -1.0f, -1.0f, -1.0f, // Back bottom left
-
- 1.0f, 1.0f, 1.0f, // Front top right
- 1.0f, 1.0f, -1.0f, // Back top right
-
- 1.0f, -1.0f, 1.0f, // Front bottom right
- 1.0f, -1.0f, -1.0f, // Back bottom right
-};
-
-static inline void
-mat4Identity(mat4 m)
+PUGL_LOG_FUNC(1, 2)
+static int
+logError(const char* fmt, ...)
{
- for (int c = 0; c < 4; ++c) {
- for (int r = 0; r < 4; ++r) {
- m[c][r] = c == r ? 1.0f : 0.0f;
- }
- }
-}
+ fprintf(stderr, "error: ");
-static inline void
-mat4Translate(mat4 m, const float x, const float y, const float z)
-{
- m[3][0] = x;
- m[3][1] = y;
- m[3][2] = z;
-}
+ va_list args;
+ va_start(args, fmt);
+ vfprintf(stderr, fmt, args);
+ va_end(args);
-static inline void
-mat4Mul(mat4 m, mat4 a, mat4 b)
-{
- for (int c = 0; c < 4; ++c) {
- for (int r = 0; r < 4; ++r) {
- m[c][r] = 0.0f;
- for (int k = 0; k < 4; ++k) {
- m[c][r] += a[k][r] * b[c][k];
- }
- }
- }
-}
-
-static inline void
-mat4Ortho(mat4 m,
- const float l,
- const float r,
- const float b,
- const float t,
- const float n,
- const float f)
-{
- m[0][0] = 2.0f / (r - l);
- m[0][1] = m[0][2] = m[0][3] = 0.0f;
-
- m[1][1] = 2.0f / (t - b);
- m[1][0] = m[1][2] = m[1][3] = 0.0f;
-
- m[2][2] = -2.0f / (f - n);
- m[2][0] = m[2][1] = m[2][3] = 0.0f;
-
- m[3][0] = -(r + l) / (r - l);
- m[3][1] = -(t + b) / (t - b);
- m[3][2] = -(f + n) / (f - n);
- m[3][3] = 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;
+ return 1;
}
static inline int
@@ -164,49 +68,120 @@ printModifiers(const uint32_t mods)
}
static inline int
-printEvent(const PuglEvent* event, const char* prefix)
+printEvent(const PuglEvent* event, const char* prefix, const bool verbose)
{
+#define FFMT "%6.1f"
+#define PFMT FFMT " " FFMT
+#define PRINT(fmt, ...) fprintf(stderr, fmt, __VA_ARGS__)
+
switch (event->type) {
+ case PUGL_NOTHING:
+ return 0;
case PUGL_KEY_PRESS:
- return fprintf(stderr, "%sKey press code %3u key U+%04X\n",
- prefix, event->key.keycode, event->key.key);
+ return PRINT("%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);
+ return PRINT("%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);
+ return PRINT("%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) +
+ return (PRINT("%sMouse %u %s at " PFMT " ",
+ 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) +
+ return (PRINT("%sScroll %5.1f %5.1f at " PFMT " ",
+ prefix,
+ event->scroll.dx,
+ event->scroll.dy,
+ event->scroll.x,
+ event->scroll.y) +
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_POINTER_IN:
+ return PRINT("%sMouse enter at " PFMT "\n",
+ prefix,
+ event->crossing.x,
+ event->crossing.y);
+ case PUGL_POINTER_OUT:
+ return PRINT("%sMouse leave at " PFMT "\n",
+ prefix,
+ event->crossing.x,
+ event->crossing.y);
case PUGL_FOCUS_IN:
- return fprintf(stderr, "%sFocus in%s\n",
- prefix, event->focus.grab ? " (grab)" : "");
+ return PRINT("%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 PRINT("%sFocus out%s\n",
+ prefix,
+ event->focus.grab ? " (ungrab)" : "");
+ case PUGL_CLIENT:
+ return PRINT("%sClient %" PRIXPTR " %" PRIXPTR "\n",
+ prefix,
+ event->client.data1,
+ event->client.data2);
+ case PUGL_TIMER:
+ return PRINT("%sTimer %" PRIuPTR "\n", prefix, event->timer.id);
+ default:
+ break;
}
+ if (verbose) {
+ switch (event->type) {
+ case PUGL_CREATE:
+ return fprintf(stderr, "%sCreate\n", prefix);
+ case PUGL_DESTROY:
+ return fprintf(stderr, "%sDestroy\n", prefix);
+ case PUGL_MAP:
+ return fprintf(stderr, "%sMap\n", prefix);
+ case PUGL_UNMAP:
+ return fprintf(stderr, "%sUnmap\n", prefix);
+ case PUGL_UPDATE:
+ return fprintf(stderr, "%sUpdate\n", prefix);
+ case PUGL_CONFIGURE:
+ return PRINT("%sConfigure " PFMT " " PFMT "\n",
+ prefix,
+ event->configure.x,
+ event->configure.y,
+ event->configure.width,
+ event->configure.height);
+ case PUGL_EXPOSE:
+ return PRINT("%sExpose " PFMT " " PFMT "\n",
+ prefix,
+ event->expose.x,
+ event->expose.y,
+ event->expose.width,
+ event->expose.height);
+ case PUGL_CLOSE:
+ return PRINT("%sClose\n", prefix);
+ case PUGL_MOTION:
+ return PRINT("%sMouse motion at " PFMT "\n",
+ prefix,
+ event->motion.x,
+ event->motion.y);
+ default:
+ fprintf(stderr, "%sUnknown event type %u\n", prefix, event->type);
+ break;
+ }
+ }
+
+#undef PRINT
+#undef PFMT
+#undef FFMT
+
return 0;
}
@@ -216,17 +191,31 @@ puglPrintTestUsage(const char* prog, const char* posHelp)
printf("Usage: %s [OPTION]... %s\n\n"
" -a Enable anti-aliasing\n"
" -c Continuously animate and draw\n"
- " -d Enable double-buffering\n"
+ " -d Directly draw to window (no double-buffering)\n"
+ " -e Enable platform error-checking\n"
+ " -f Fast drawing, explicitly disable vertical sync\n"
" -h Display this help\n"
" -i Ignore key repeat\n"
- " -r Resizable window\n",
+ " -v Print verbose output\n"
+ " -r Resizable window\n"
+ " -s Explicitly enable vertical sync\n",
prog, posHelp);
}
static inline PuglTestOptions
puglParseTestOptions(int* pargc, char*** pargv)
{
- PuglTestOptions opts = { 0, 0, false, false, false, false };
+ PuglTestOptions opts = {
+ 0,
+ PUGL_TRUE,
+ PUGL_DONT_CARE,
+ false,
+ false,
+ false,
+ false,
+ false,
+ false,
+ };
char** const argv = *pargv;
int i = 1;
@@ -236,7 +225,11 @@ puglParseTestOptions(int* pargc, char*** pargv)
} else if (!strcmp(argv[i], "-c")) {
opts.continuous = true;
} else if (!strcmp(argv[i], "-d")) {
- opts.doubleBuffer = PUGL_TRUE;
+ opts.doubleBuffer = PUGL_FALSE;
+ } else if (!strcmp(argv[i], "-e")) {
+ opts.errorChecking = PUGL_TRUE;
+ } else if (!strcmp(argv[i], "-f")) {
+ opts.sync = PUGL_FALSE;
} else if (!strcmp(argv[i], "-h")) {
opts.help = true;
return opts;
@@ -244,11 +237,15 @@ puglParseTestOptions(int* pargc, char*** pargv)
opts.ignoreKeyRepeat = true;
} else if (!strcmp(argv[i], "-r")) {
opts.resizable = true;
+ } else if (!strcmp(argv[i], "-s")) {
+ opts.sync = PUGL_TRUE;
+ } else if (!strcmp(argv[i], "-v")) {
+ opts.verbose = true;
} else if (argv[i][0] != '-') {
break;
} else {
opts.help = true;
- fprintf(stderr, "error: Unknown option: %s\n", argv[i]);
+ logError("Unknown option: %s\n", argv[i]);
}
}
@@ -257,22 +254,3 @@ puglParseTestOptions(int* pargc, char*** pargv)
return opts;
}
-
-static inline void
-puglPrintFps(const PuglWorld* world,
- PuglFpsPrinter* printer,
- unsigned* const framesDrawn)
-{
- const double thisTime = puglGetTime(world);
- 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 e22930a..58d14c3 100755
--- a/pugl/waf
+++ b/pugl/waf
@@ -2,15 +2,26 @@
# Minimal waf script for projects that include waflib directly
-from waflib import Context, Scripting
-
+import sys
import inspect
import os
+try:
+ from waflib import Context, Scripting
+except Exception as e:
+ sys.stderr.write('error: Failed to import waf (%s)\n' % e)
+ if os.path.exists('.git'):
+ sys.stderr.write("Are submodules up to date? "
+ "Try 'git submodule update --init --recursive'\n")
+
+ sys.exit(1)
+
+
def main():
script_path = os.path.abspath(inspect.getfile(inspect.getmodule(main)))
- project_path = os.path.dirname(script_path)
+ project_path = os.path.dirname(os.path.realpath(script_path))
Scripting.waf_entry_point(os.getcwd(), Context.WAFVERSION, project_path)
+
if __name__ == '__main__':
main()
diff --git a/pugl/waflib/.gitignore b/pugl/waflib/.gitignore
deleted file mode 100644
index 8d35cb3..0000000
--- a/pugl/waflib/.gitignore
+++ /dev/null
@@ -1,2 +0,0 @@
-__pycache__
-*.pyc
diff --git a/pugl/waflib/Build.py b/pugl/waflib/Build.py
deleted file mode 100644
index 8143dbc..0000000
--- a/pugl/waflib/Build.py
+++ /dev/null
@@ -1,1496 +0,0 @@
-#!/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
deleted file mode 100644
index a4147d2..0000000
--- a/pugl/waflib/COPYING
+++ /dev/null
@@ -1,25 +0,0 @@
-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
deleted file mode 100644
index 901fba6..0000000
--- a/pugl/waflib/ConfigSet.py
+++ /dev/null
@@ -1,361 +0,0 @@
-#!/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
deleted file mode 100644
index db09c0e..0000000
--- a/pugl/waflib/Configure.py
+++ /dev/null
@@ -1,639 +0,0 @@
-#!/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
deleted file mode 100644
index 876ea46..0000000
--- a/pugl/waflib/Context.py
+++ /dev/null
@@ -1,737 +0,0 @@
-#!/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
deleted file mode 100644
index bf75c1b..0000000
--- a/pugl/waflib/Errors.py
+++ /dev/null
@@ -1,68 +0,0 @@
-#!/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
deleted file mode 100644
index 11dc34f..0000000
--- a/pugl/waflib/Logs.py
+++ /dev/null
@@ -1,379 +0,0 @@
-#!/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
deleted file mode 100644
index 4ac1ea8..0000000
--- a/pugl/waflib/Node.py
+++ /dev/null
@@ -1,970 +0,0 @@
-#!/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
deleted file mode 100644
index ad802d4..0000000
--- a/pugl/waflib/Options.py
+++ /dev/null
@@ -1,342 +0,0 @@
-#!/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
deleted file mode 100644
index c5361b9..0000000
--- a/pugl/waflib/README.md
+++ /dev/null
@@ -1,24 +0,0 @@
-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
deleted file mode 100644
index 5d27669..0000000
--- a/pugl/waflib/Runner.py
+++ /dev/null
@@ -1,617 +0,0 @@
-#!/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::
-
- d