init
Some checks failed
Docker. / Ubuntu (push) Has been cancelled
User-agent updater. / User-agent (push) Failing after 15s
Lock Threads / lock (push) Failing after 10s
Waiting for answer. / waiting-for-answer (push) Failing after 22s
Needs user action. / needs-user-action (push) Failing after 8s
Can't reproduce. / cant-reproduce (push) Failing after 8s
Close stale issues and PRs / stale (push) Has been cancelled
Some checks failed
Docker. / Ubuntu (push) Has been cancelled
User-agent updater. / User-agent (push) Failing after 15s
Lock Threads / lock (push) Failing after 10s
Waiting for answer. / waiting-for-answer (push) Failing after 22s
Needs user action. / needs-user-action (push) Failing after 8s
Can't reproduce. / cant-reproduce (push) Failing after 8s
Close stale issues and PRs / stale (push) Has been cancelled
This commit is contained in:
159
cmake/external/glib/cppgir/examples/co-async.hpp
vendored
Normal file
159
cmake/external/glib/cppgir/examples/co-async.hpp
vendored
Normal file
@@ -0,0 +1,159 @@
|
||||
#pragma once
|
||||
|
||||
#include <gi/gi.hpp>
|
||||
|
||||
#include <coroutine>
|
||||
#include <future>
|
||||
|
||||
#ifdef CO_DEBUG
|
||||
#include <iostream>
|
||||
static auto &dout = std::cerr;
|
||||
#else
|
||||
#include <sstream>
|
||||
static std::ostringstream dout;
|
||||
#endif
|
||||
|
||||
template<typename T, typename SELF>
|
||||
struct holder
|
||||
{
|
||||
void return_value(T &&v)
|
||||
{
|
||||
dout << "return value " << std::endl;
|
||||
auto self = (SELF *)this;
|
||||
self->set_value(std::move(v));
|
||||
}
|
||||
};
|
||||
|
||||
template<typename SELF>
|
||||
struct holder<void, SELF>
|
||||
{
|
||||
void return_void()
|
||||
{
|
||||
auto self = (SELF *)this;
|
||||
self->set_value();
|
||||
}
|
||||
};
|
||||
|
||||
template<typename RESULT>
|
||||
class promise_type_t : public holder<RESULT, promise_type_t<RESULT>>
|
||||
{
|
||||
protected:
|
||||
std::promise<RESULT> result_;
|
||||
std::coroutine_handle<> waiter_;
|
||||
|
||||
public:
|
||||
struct init
|
||||
{
|
||||
std::coroutine_handle<> handle;
|
||||
std::future<RESULT> f;
|
||||
};
|
||||
|
||||
~promise_type_t() { dout << "promise destruction" << std::endl; }
|
||||
|
||||
auto get_return_object(bool refresh = false)
|
||||
{
|
||||
dout << "return obj " << std::endl;
|
||||
if (refresh)
|
||||
result_ = decltype(result_)();
|
||||
return init{std::coroutine_handle<promise_type_t>::from_promise(*this),
|
||||
result_.get_future()};
|
||||
}
|
||||
std::suspend_never initial_suspend() noexcept { return {}; }
|
||||
std::suspend_never final_suspend() noexcept { return {}; }
|
||||
|
||||
bool resume()
|
||||
{
|
||||
auto w = waiter_;
|
||||
if (w) {
|
||||
// waiter takes care of itself again
|
||||
waiter_ = nullptr;
|
||||
w.resume();
|
||||
}
|
||||
return bool(w);
|
||||
}
|
||||
|
||||
// use any dummy type to avoid reference to void below
|
||||
using arg_type = typename std::conditional<std::is_same<RESULT, void>::value,
|
||||
std::nullptr_t, RESULT>::type;
|
||||
|
||||
void set_value(arg_type &&v)
|
||||
{
|
||||
result_.set_value(std::move(v));
|
||||
resume();
|
||||
}
|
||||
|
||||
void set_value()
|
||||
{
|
||||
result_.set_value();
|
||||
resume();
|
||||
}
|
||||
|
||||
void set_waiter(std::coroutine_handle<> handle)
|
||||
{
|
||||
// a task/promise represent a coroutine function (frame)
|
||||
// it should only be waited upon by one other task
|
||||
// (rather than handed around and waited in multiple locations)
|
||||
if (waiter_)
|
||||
gi::detail::try_throw(std::logic_error("already waited upon"));
|
||||
waiter_ = handle;
|
||||
}
|
||||
|
||||
void unhandled_exception()
|
||||
{
|
||||
#if GI_CONFIG_EXCEPTIONS
|
||||
result_.set_exception(std::current_exception());
|
||||
// if no-one waiting, deliver to caller
|
||||
// the latter likely is the original caller
|
||||
// (to which we have not yet returned, so it can yet await)
|
||||
// otherwise it might end up totally lost
|
||||
if (!resume())
|
||||
throw;
|
||||
#endif
|
||||
}
|
||||
};
|
||||
|
||||
template<typename RESULT, typename P = promise_type_t<RESULT>>
|
||||
class task
|
||||
{
|
||||
public:
|
||||
using promise_type = P;
|
||||
|
||||
// only 1 actually active
|
||||
std::coroutine_handle<promise_type> coro_;
|
||||
std::unique_ptr<promise_type> p_;
|
||||
// but always this
|
||||
std::future<RESULT> result_;
|
||||
|
||||
public:
|
||||
// NOTE if coroutine exits by co_return, then handle is not useful
|
||||
// but the future should have a value
|
||||
task(typename P::init i)
|
||||
: coro_(decltype(coro_)::from_address(i.handle.address())),
|
||||
result_(std::move(i.f))
|
||||
{
|
||||
dout << "init task" << std::endl;
|
||||
}
|
||||
|
||||
task() : p_(new promise_type()) { result_ = p_->get_return_object().f; }
|
||||
|
||||
// move-only
|
||||
task(task &&other) = default;
|
||||
task &operator=(task &&other) = delete;
|
||||
|
||||
bool await_ready()
|
||||
{
|
||||
return result_.wait_for(std::chrono::seconds(0)) ==
|
||||
std::future_status::ready;
|
||||
}
|
||||
|
||||
promise_type &promise() const { return coro_ ? coro_.promise() : *p_; }
|
||||
|
||||
void await_suspend(std::coroutine_handle<> handle)
|
||||
{
|
||||
if (!coro_ && !p_)
|
||||
gi::detail::try_throw(std::logic_error("no routine to wait on"));
|
||||
promise().set_waiter(handle);
|
||||
}
|
||||
|
||||
RESULT await_resume() { return result_.get(); }
|
||||
};
|
||||
74
cmake/external/glib/cppgir/examples/external/CMakeLists.txt
vendored
Normal file
74
cmake/external/glib/cppgir/examples/external/CMakeLists.txt
vendored
Normal file
@@ -0,0 +1,74 @@
|
||||
cmake_minimum_required(VERSION 3.16.0 FATAL_ERROR)
|
||||
project(cppgir_example VERSION 2.0.0)
|
||||
|
||||
# following example uses the Gio-2.0 gir
|
||||
# will generate code for that
|
||||
|
||||
# locate cppgir
|
||||
|
||||
# NOTE recent cmake version have more options and automagic find_package integration
|
||||
# (see https://cmake.org/cmake/help/latest/guide/using-dependencies/index.html
|
||||
# or https://cmake.org/cmake/help/latest/module/FetchContent.html)
|
||||
# but a slightly older approach is used here
|
||||
find_package(cppgir ${CMAKE_PROJECT_VERSION} QUIET)
|
||||
if (cppgir_FOUND)
|
||||
# ok
|
||||
message(STATUS "using system cppgir")
|
||||
elseif (CMAKE_VERSION GREATER_EQUAL 3.22)
|
||||
# older version fail processing the subdirectory below
|
||||
message(STATUS "cppgir not found, fetching instead")
|
||||
# of course, alternatively,
|
||||
# use any other method to include cppgir (e.g. git submodule)
|
||||
# which can also be done unconditionally (without trying find_package first)
|
||||
include(FetchContent)
|
||||
FetchContent_Declare(
|
||||
cppgir
|
||||
GIT_REPOSITORY https://gitlab.com/mnauw/cppgir.git
|
||||
GIT_TAG master
|
||||
GIT_SHALLOW TRUE
|
||||
GIT_SUBMODULES_RECURSE TRUE
|
||||
# disable auto-invoke of add_subdirectory() below
|
||||
SOURCE_SUBDIR data
|
||||
)
|
||||
# no CMakeLists in specified subdir, so we handle it explicitly
|
||||
FetchContent_MakeAvailable(cppgir)
|
||||
set(CPPGIR_DIR "${cppgir_SOURCE_DIR}")
|
||||
else ()
|
||||
# obviously, actual mileage/location may vary (e.g. using git submodule)
|
||||
set(CPPGIR_DIR "../..")
|
||||
endif ()
|
||||
|
||||
if (CPPGIR_DIR)
|
||||
# adjust options
|
||||
set(BUILD_TESTING OFF)
|
||||
set(BUILD_EXAMPLES OFF)
|
||||
set(BUILD_EMBED_IGNORE ON)
|
||||
add_subdirectory(${CPPGIR_DIR} cppgir EXCLUDE_FROM_ALL)
|
||||
endif ()
|
||||
|
||||
set(EXAMPLE_NS "Gio-2.0")
|
||||
# of course, can be anywhere, e.g. inside/outside the build directory
|
||||
# set(GENERATED_DIR "/tmp/gi.example")
|
||||
set(GENERATED_DIR "${CMAKE_CURRENT_BINARY_DIR}/generated")
|
||||
add_custom_command(OUTPUT ${GENERATED_DIR}
|
||||
COMMENT "Generating wrapper code for: ${EXAMPLE_NS}"
|
||||
DEPENDS CppGir::cppgir
|
||||
COMMAND CppGir::cppgir --output ${GENERATED_DIR} ${EXAMPLE_NS}
|
||||
COMMAND cmake -E touch_nocreate ${GENERATED_DIR}
|
||||
)
|
||||
add_custom_target(generate DEPENDS ${GENERATED_DIR})
|
||||
|
||||
# need the code and libs and especially GIRs
|
||||
include(FindPkgConfig)
|
||||
pkg_check_modules(GOBJECT REQUIRED IMPORTED_TARGET gobject-2.0)
|
||||
pkg_check_modules(GIO REQUIRED IMPORTED_TARGET gio-2.0 gio-unix-2.0)
|
||||
|
||||
# (actually) needs no generated code, but it needs basic libs
|
||||
add_executable(ext-gobject src/ext-gobject.cpp)
|
||||
target_link_libraries(ext-gobject PRIVATE CppGir::gi PkgConfig::GOBJECT)
|
||||
|
||||
# (really) needs generated code
|
||||
add_executable(ext-gio src/ext-gio.cpp)
|
||||
target_link_libraries(ext-gio PRIVATE CppGir::gi PkgConfig::GIO)
|
||||
target_include_directories(ext-gio PRIVATE ${GENERATED_DIR})
|
||||
add_dependencies(ext-gio generate)
|
||||
6
cmake/external/glib/cppgir/examples/external/README.md
vendored
Normal file
6
cmake/external/glib/cppgir/examples/external/README.md
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
# cppgir_example
|
||||
|
||||
This example project shows how `cppgir` can be used with `CMake` or `meson`.
|
||||
In either case, the required `cppgir` parts can either be found "in the system"
|
||||
(as previously installed by some package or build), or built as part of the
|
||||
project itself (by suitable form of inclusion).
|
||||
49
cmake/external/glib/cppgir/examples/external/conanfile.py
vendored
Normal file
49
cmake/external/glib/cppgir/examples/external/conanfile.py
vendored
Normal file
@@ -0,0 +1,49 @@
|
||||
import os
|
||||
|
||||
from conan import ConanFile
|
||||
from conan.tools.cmake import CMake, CMakeToolchain, cmake_layout
|
||||
from conan.tools.build import can_run
|
||||
|
||||
|
||||
class CppGirExampleConan(ConanFile):
|
||||
version = "2.0.0"
|
||||
name = "cppgir-example"
|
||||
description = "cppgir example"
|
||||
license = "MIT Software License"
|
||||
url = "https://gitlab.com/mnauw/cppgir.git"
|
||||
exports_sources = "src/*", "CMakeLists.txt"
|
||||
settings = "os", "compiler", "build_type", "arch"
|
||||
build_policy = "missing"
|
||||
author = "Mark Nauwelaerts"
|
||||
|
||||
def requirements(self):
|
||||
req = self.tested_reference_str
|
||||
if req is None:
|
||||
req = "cppgir/" + self.version
|
||||
self.requires(req)
|
||||
# also requires GLib
|
||||
# however, no GIRs in conan binary package
|
||||
# which nowadays requires a bootstrap dance with gobject-introspection
|
||||
# so rely on a distro package instead
|
||||
# self.requires("glib/2.78.3")
|
||||
|
||||
def build_requirements(self):
|
||||
self.tool_requires("cppgir/<host_version>",
|
||||
options={'header_only': False})
|
||||
|
||||
def layout(self):
|
||||
cmake_layout(self, build_folder="build.conan")
|
||||
|
||||
def generate(self):
|
||||
tc = CMakeToolchain(self)
|
||||
tc.generate()
|
||||
|
||||
def build(self):
|
||||
cmake = CMake(self)
|
||||
cmake.configure()
|
||||
cmake.build()
|
||||
|
||||
def test(self):
|
||||
if can_run(self):
|
||||
cmd = os.path.join(self.cpp.build.bindir, "ext-gobject")
|
||||
self.run(cmd, env="conanrun")
|
||||
57
cmake/external/glib/cppgir/examples/external/meson.build
vendored
Normal file
57
cmake/external/glib/cppgir/examples/external/meson.build
vendored
Normal file
@@ -0,0 +1,57 @@
|
||||
project(
|
||||
'cppgir_example',
|
||||
['c', 'cpp'],
|
||||
version: '2.0.0',
|
||||
meson_version: '>= 0.61',
|
||||
default_options: ['warning_level=2', 'cpp_std=c++17'],
|
||||
)
|
||||
|
||||
# following example uses the Gio-2.0 gir
|
||||
# will generate code for that
|
||||
|
||||
# locate cppgir, either in system or subproject
|
||||
# if subproject, arrange to embed ignore data
|
||||
# (since it will run un-installed, so data can not be found in installed location)
|
||||
gi = dependency(
|
||||
'cppgir',
|
||||
required: true,
|
||||
version: '>=2.0.0',
|
||||
fallback: 'cppgir',
|
||||
default_options: ['build_embed_ignore=true'],
|
||||
)
|
||||
cppgir = find_program('cppgir', required: true)
|
||||
|
||||
generated_src = custom_target(
|
||||
'src',
|
||||
# actually it generates all this (and more)
|
||||
# output : ['gio.cpp', 'gio.hpp', 'glib.cpp', 'glib.hpp', 'gobject.cpp', 'gobject.hpp'],
|
||||
# but the example below will inline all of the above, so no source files are used
|
||||
# (though it is recommended for a real case, rather than a full inline)
|
||||
output: ['gio.hpp'],
|
||||
console: true,
|
||||
# output-top creates extra files in top-level (as oppposed to subdir)
|
||||
# since "output" paths above "must not contain a path segment" (says meson, sigh)
|
||||
command: [cppgir, '--output-top', '--output', '@OUTDIR@', 'Gio-2.0'],
|
||||
)
|
||||
|
||||
# need the code and libs and especially GIRs
|
||||
gobject = dependency('gobject-2.0', required: true)
|
||||
gio = dependency('gio-2.0', required: true)
|
||||
# the Gio GIR data also covers this, so generated code references it
|
||||
gio_unix = dependency('gio-unix-2.0', required: true)
|
||||
|
||||
deps = [gi, gobject, gio, gio_unix]
|
||||
|
||||
# (actually) needs no generated code, but it needs basic libs
|
||||
ex_gobject = executable(
|
||||
'ext-gobject',
|
||||
['src/ext-gobject.cpp'],
|
||||
dependencies: deps,
|
||||
)
|
||||
|
||||
# (really) needs generated code
|
||||
ex_gio = executable(
|
||||
'ext-gio',
|
||||
['src/ext-gio.cpp', generated_src],
|
||||
dependencies: deps,
|
||||
)
|
||||
1
cmake/external/glib/cppgir/examples/external/src/ext-gio.cpp
vendored
Normal file
1
cmake/external/glib/cppgir/examples/external/src/ext-gio.cpp
vendored
Normal file
@@ -0,0 +1 @@
|
||||
#include "../../gio.cpp"
|
||||
1
cmake/external/glib/cppgir/examples/external/src/ext-gobject.cpp
vendored
Normal file
1
cmake/external/glib/cppgir/examples/external/src/ext-gobject.cpp
vendored
Normal file
@@ -0,0 +1 @@
|
||||
#include "../../gobject.cpp"
|
||||
5
cmake/external/glib/cppgir/examples/external/subprojects/cppgir.wrap
vendored
Normal file
5
cmake/external/glib/cppgir/examples/external/subprojects/cppgir.wrap
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
[wrap-git]
|
||||
url = https://gitlab.com/mnauw/cppgir.git
|
||||
revision = master
|
||||
clone-recursive = true
|
||||
depth = 1
|
||||
254
cmake/external/glib/cppgir/examples/gio-async-co.cpp
vendored
Normal file
254
cmake/external/glib/cppgir/examples/gio-async-co.cpp
vendored
Normal file
@@ -0,0 +1,254 @@
|
||||
#define GI_INLINE 1
|
||||
#include <gio/gio.hpp>
|
||||
|
||||
#include "co-async.hpp"
|
||||
|
||||
namespace GLib = gi::repository::GLib;
|
||||
namespace GObject_ = gi::repository::GObject;
|
||||
namespace Gio = gi::repository::Gio;
|
||||
|
||||
template<typename R>
|
||||
class async_result_promise_type_t : public promise_type_t<R>
|
||||
{
|
||||
public:
|
||||
Gio::Cancellable cancel_;
|
||||
};
|
||||
|
||||
template<typename RESULT = Gio::AsyncResult>
|
||||
class async_result : public task<RESULT, async_result_promise_type_t<RESULT>>
|
||||
{
|
||||
public:
|
||||
using super_type = task<RESULT, async_result_promise_type_t<RESULT>>;
|
||||
|
||||
using super_type::super_type;
|
||||
|
||||
using super_type::await_suspend;
|
||||
|
||||
// NOTE; in general, if await_ready == false, then there is no result yet,
|
||||
// so the handle's frame should not have co_return'ed yet
|
||||
// so the handle/promise should still be valid
|
||||
|
||||
template<typename OR>
|
||||
void await_suspend(
|
||||
std::coroutine_handle<async_result_promise_type_t<OR>> handle)
|
||||
{
|
||||
// propagate cancellable
|
||||
// the handle should be valid
|
||||
// (as await_ready == false, otherwise no suspend should happen)
|
||||
handle.promise().cancel_ = this->promise().cancel_;
|
||||
// usual suspend
|
||||
super_type::await_suspend(handle);
|
||||
}
|
||||
|
||||
operator Gio::AsyncReadyCallback()
|
||||
{
|
||||
// only makes sense in default case
|
||||
static_assert(std::is_same<RESULT, Gio::AsyncResult>::value, "");
|
||||
if (this->p_) {
|
||||
// setup for new gio call
|
||||
this->result_ = this->p_->get_return_object(true).f;
|
||||
// also arrange for new cancellable below
|
||||
this->p_->cancel_ = nullptr;
|
||||
return [this](GObject_::Object, Gio::AsyncResult result) {
|
||||
this->promise().return_value(std::move(result));
|
||||
};
|
||||
} else {
|
||||
// so this task is the result of a coroutine frame
|
||||
// then it should be completed by the latter, not a Gio callback
|
||||
g_warning("no callback to complete coroutine");
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
Gio::Cancellable cancellable()
|
||||
{
|
||||
Gio::Cancellable ret;
|
||||
if (this->p_) {
|
||||
if (!this->p_->cancel_)
|
||||
this->p_->cancel_ = Gio::Cancellable::new_();
|
||||
ret = this->p_->cancel_;
|
||||
} else if (!this->await_ready()) {
|
||||
// no create here, only propagate
|
||||
ret = this->promise().cancel_;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
static Gio::Cancellable timeout(
|
||||
const std::chrono::milliseconds &to, Gio::Cancellable cancel)
|
||||
{
|
||||
if (to.count() > 0 && cancel) {
|
||||
GLib::timeout_add_once(
|
||||
to.count(), [cancel]() mutable { cancel.cancel(); });
|
||||
}
|
||||
return cancel;
|
||||
}
|
||||
};
|
||||
|
||||
async_result<void>
|
||||
sleep_for(std::chrono::milliseconds to)
|
||||
{
|
||||
async_result<void> w;
|
||||
GLib::timeout_add_once(to.count(), [&w] { w.promise().return_void(); });
|
||||
co_await w;
|
||||
co_return;
|
||||
}
|
||||
|
||||
static task<void>
|
||||
async_client(int port, int id, int &count)
|
||||
{
|
||||
async_result w;
|
||||
|
||||
auto dest = Gio::NetworkAddress::new_loopback(port);
|
||||
|
||||
std::string sid = "client ";
|
||||
sid += std::to_string(id);
|
||||
|
||||
// connect a client
|
||||
std::cout << sid << ": connect" << std::endl;
|
||||
auto client = Gio::SocketClient::new_();
|
||||
client.connect_async(dest, w);
|
||||
auto conn = gi::expect(client.connect_finish(co_await w));
|
||||
|
||||
// say something
|
||||
auto os = conn.get_output_stream();
|
||||
std::cout << sid << ": send: " << sid << std::endl;
|
||||
os.write_all_async(
|
||||
(guint8 *)sid.data(), sid.size(), GLib::PRIORITY_DEFAULT_, w);
|
||||
os.write_all_finish(co_await w, (gsize *)nullptr);
|
||||
|
||||
// now hear what the other side has to say
|
||||
std::cout << sid << ": receive" << std::endl;
|
||||
auto is = conn.get_input_stream();
|
||||
while (1) {
|
||||
guint8 data[1024];
|
||||
is.read_async(data, sizeof(data), GLib::PRIORITY_DEFAULT_, w);
|
||||
auto size = gi::expect(is.read_finish(co_await w));
|
||||
if (!size)
|
||||
break;
|
||||
std::string msg(data, data + size);
|
||||
std::cout << sid << ": got data: " << msg << std::endl;
|
||||
}
|
||||
|
||||
std::cout << sid << ": closing down" << std::endl;
|
||||
--count;
|
||||
}
|
||||
|
||||
static task<void>
|
||||
async_handle_client(Gio::SocketConnection conn)
|
||||
{
|
||||
async_result w;
|
||||
|
||||
// say hello
|
||||
auto os = conn.get_output_stream();
|
||||
std::string msg = "hello ";
|
||||
os.write_all_async(
|
||||
(guint8 *)msg.data(), msg.size(), GLib::PRIORITY_DEFAULT_, w);
|
||||
os.write_all_finish(co_await w, (gsize *)nullptr);
|
||||
|
||||
// now echo what the other side has to say
|
||||
auto is = conn.get_input_stream();
|
||||
while (1) {
|
||||
guint8 data[1024];
|
||||
// give up if timeout
|
||||
GLib::Error error;
|
||||
is.read_async(data, sizeof(data), GLib::PRIORITY_DEFAULT_,
|
||||
w.timeout(std::chrono::milliseconds(200), w.cancellable()), w);
|
||||
auto size = gi::expect(is.read_finish(co_await w, &error));
|
||||
if (error) {
|
||||
if (error.matches(G_IO_ERROR, G_IO_ERROR_CANCELLED)) {
|
||||
break;
|
||||
} else {
|
||||
gi::detail::try_throw(std::move(error));
|
||||
}
|
||||
}
|
||||
std::string msg(data, data + size);
|
||||
std::cout << "server: got data: " << msg << std::endl;
|
||||
os.write_all_async(data, size, GLib::PRIORITY_DEFAULT_, w);
|
||||
os.write_all_finish(co_await w, (gsize *)nullptr);
|
||||
}
|
||||
|
||||
std::cout << "server: closing down client" << std::endl;
|
||||
}
|
||||
|
||||
static task<void>
|
||||
async_server(int clients, int &port)
|
||||
{
|
||||
async_result w;
|
||||
|
||||
auto listener = Gio::SocketListener::new_();
|
||||
port = gi::expect(listener.add_any_inet_port());
|
||||
|
||||
int count = 0;
|
||||
while (count < clients) {
|
||||
// accept clients
|
||||
std::cout << "server: accepting" << std::endl;
|
||||
listener.accept_async(w);
|
||||
auto conn = gi::expect(
|
||||
listener.accept_finish(co_await w, (GObject_::Object *)nullptr));
|
||||
|
||||
// spawn client handler
|
||||
std::cout << "server: new connection" << std::endl;
|
||||
// task will run itself to completion, no need to wait/watch it here
|
||||
async_handle_client(conn);
|
||||
++count;
|
||||
}
|
||||
|
||||
// wait a bit more and shutdown, because we can
|
||||
co_await sleep_for(std::chrono::milliseconds(1000));
|
||||
}
|
||||
|
||||
void
|
||||
async_demo(GLib::MainLoop loop, int clients)
|
||||
{
|
||||
// run server
|
||||
// dispatch at once to obtain port
|
||||
int port = 0;
|
||||
auto server = async_server(clients, port);
|
||||
|
||||
// make clients
|
||||
int count = 0;
|
||||
for (int i = 0; i < clients; ++i) {
|
||||
++count;
|
||||
// client task runs to completion, no need to wait/watch
|
||||
// NOTE this frame will stay alive, so count ref is valid
|
||||
async_client(port, i, count);
|
||||
}
|
||||
|
||||
// plain-and-simple; poll regularly and quit when all clients done
|
||||
task<void> cd;
|
||||
auto check = [&]() -> gboolean {
|
||||
if (!count) {
|
||||
cd.promise().return_void();
|
||||
return G_SOURCE_REMOVE;
|
||||
}
|
||||
return G_SOURCE_CONTINUE;
|
||||
};
|
||||
GLib::timeout_add(100, check);
|
||||
|
||||
auto wait = [&]() -> task<void> {
|
||||
// wait clients
|
||||
co_await cd;
|
||||
// server should also have completed
|
||||
co_await server;
|
||||
std::cout << "server down" << std::endl;
|
||||
loop.quit();
|
||||
};
|
||||
|
||||
wait();
|
||||
|
||||
std::cout << "running loop" << std::endl;
|
||||
loop.run();
|
||||
std::cout << "ending loop" << std::endl;
|
||||
}
|
||||
|
||||
int
|
||||
main(int argc, char **argv)
|
||||
{
|
||||
auto loop = GLib::MainLoop::new_();
|
||||
|
||||
int clients = argc > 1 ? std::stoi(argv[1]) : 0;
|
||||
std::cout << clients << " clients" << std::endl;
|
||||
if (clients > 0)
|
||||
async_demo(loop, clients);
|
||||
}
|
||||
351
cmake/external/glib/cppgir/examples/gio-async.cpp
vendored
Normal file
351
cmake/external/glib/cppgir/examples/gio-async.cpp
vendored
Normal file
@@ -0,0 +1,351 @@
|
||||
#define GI_INLINE 1
|
||||
#include <gio/gio.hpp>
|
||||
|
||||
#include <iostream>
|
||||
|
||||
#if GI_CONFIG_EXCEPTIONS
|
||||
#include <boost/fiber/all.hpp>
|
||||
|
||||
namespace GLib = gi::repository::GLib;
|
||||
namespace GObject_ = gi::repository::GObject;
|
||||
namespace Gio = gi::repository::Gio;
|
||||
|
||||
class context_scheduler : public boost::fibers::algo::round_robin
|
||||
{
|
||||
typedef context_scheduler self;
|
||||
typedef boost::fibers::algo::round_robin super;
|
||||
|
||||
struct src : GSource
|
||||
{
|
||||
self *scheduler;
|
||||
};
|
||||
|
||||
GSourceFuncs funcs{};
|
||||
GLib::MainContext ctx_;
|
||||
src *source_;
|
||||
boost::fibers::condition_variable cond_;
|
||||
boost::fibers::mutex mtx_;
|
||||
using clock_type = std::chrono::steady_clock;
|
||||
bool dispatching_ = false;
|
||||
|
||||
static gboolean src_dispatch(
|
||||
GSource *source, GSourceFunc /*callback*/, gpointer /*user_data*/)
|
||||
{
|
||||
auto s = (src *)(source);
|
||||
auto sched = s->scheduler;
|
||||
// wait here to give (other) fibers a chance
|
||||
sched->dispatching_ = true;
|
||||
std::unique_lock<boost::fibers::mutex> lk(sched->mtx_);
|
||||
sched->cond_.wait(lk);
|
||||
sched->dispatching_ = false;
|
||||
// all available work has been done while we were waiting above
|
||||
// no need to dispatch again until new work
|
||||
// which we accept as of now (due to mainloop activity)
|
||||
return G_SOURCE_CONTINUE;
|
||||
}
|
||||
|
||||
public:
|
||||
context_scheduler(GLib::MainContext ctx) : ctx_(ctx)
|
||||
{
|
||||
// this is a bit too much for bindings, so handle the raw C way
|
||||
funcs.dispatch = src_dispatch;
|
||||
auto s = g_source_new(&funcs, sizeof(src));
|
||||
source_ = (src *)(s);
|
||||
source_->scheduler = this;
|
||||
g_source_attach(s, ctx.gobj_());
|
||||
}
|
||||
|
||||
~context_scheduler()
|
||||
{
|
||||
g_source_destroy(source_);
|
||||
g_source_unref(source_);
|
||||
}
|
||||
|
||||
void awakened(boost::fibers::context *t) noexcept override
|
||||
{
|
||||
// delegate first
|
||||
super::awakened(t);
|
||||
// arrange for dispatch of work
|
||||
// discard awake of source dispatch
|
||||
if (!dispatching_)
|
||||
g_source_set_ready_time(source_, 0);
|
||||
}
|
||||
|
||||
void suspend_until(
|
||||
std::chrono::steady_clock::time_point const &abs_time) noexcept override
|
||||
{
|
||||
// release dispatch
|
||||
// should only end up here while dispatching in source
|
||||
// (rather than inadvertently trying to block main loop,
|
||||
// which would then lead to busy loop)
|
||||
if (dispatching_) {
|
||||
// derive time of subsequent dispatch
|
||||
if (clock_type::time_point::max() != abs_time) {
|
||||
auto to = abs_time - std::chrono::steady_clock::now();
|
||||
int ms =
|
||||
std::chrono::duration_cast<std::chrono::milliseconds>(to).count();
|
||||
ms = std::max(ms, 0);
|
||||
g_source_set_ready_time(source_, g_get_monotonic_time() + ms);
|
||||
} else {
|
||||
g_source_set_ready_time(source_, -1);
|
||||
}
|
||||
// release source dispatching
|
||||
cond_.notify_one();
|
||||
} else {
|
||||
// suspend is requested, so there is nothing to do for a while
|
||||
// so in particular the main fiber is then also blocked (e.g. some sleep)
|
||||
// such main loop blocking is also/still not allowed
|
||||
// (if such is active)
|
||||
g_assert(g_main_depth() == 0);
|
||||
// no running loop (so also no dispatch)
|
||||
// so delegate to the usual scheduling
|
||||
// (which will really block the hard way, rather than poll)
|
||||
super::suspend_until(abs_time);
|
||||
}
|
||||
}
|
||||
|
||||
// might be called from a different thread
|
||||
void notify() noexcept override
|
||||
{
|
||||
// discard our own notify above to resume source dispatch
|
||||
if (dispatching_)
|
||||
return;
|
||||
ctx_.wakeup();
|
||||
}
|
||||
};
|
||||
|
||||
class async_future
|
||||
{
|
||||
boost::fibers::promise<Gio::AsyncResult> p_;
|
||||
boost::fibers::future<Gio::AsyncResult> f_;
|
||||
Gio::Cancellable cancel_;
|
||||
GLib::Source timeout_;
|
||||
|
||||
public:
|
||||
~async_future()
|
||||
{
|
||||
if (timeout_)
|
||||
timeout_.destroy();
|
||||
}
|
||||
|
||||
operator Gio::AsyncReadyCallback()
|
||||
{
|
||||
// prepare a new promise
|
||||
p_ = decltype(p_)();
|
||||
f_ = p_.get_future();
|
||||
cancel_ = nullptr;
|
||||
return [&](GObject_::Object, Gio::AsyncResult result) {
|
||||
p_.set_value(result);
|
||||
};
|
||||
}
|
||||
|
||||
Gio::AsyncResult get() { return f_.get(); }
|
||||
|
||||
Gio::Cancellable cancellable()
|
||||
{
|
||||
if (!cancel_)
|
||||
cancel_ = Gio::Cancellable::new_();
|
||||
return cancel_;
|
||||
}
|
||||
|
||||
Gio::Cancellable timeout(const std::chrono::milliseconds &to)
|
||||
{
|
||||
auto cancel = cancellable();
|
||||
if (to.count() > 0) {
|
||||
timeout_ = GLib::timeout_source_new(to.count());
|
||||
auto do_timeout = [cancel]() mutable {
|
||||
cancel.cancel();
|
||||
return GLib::SOURCE_REMOVE_;
|
||||
};
|
||||
timeout_.set_callback<GLib::SourceFunc>(do_timeout);
|
||||
timeout_.attach(GLib::MainContext::get_thread_default());
|
||||
}
|
||||
return cancel_;
|
||||
}
|
||||
};
|
||||
|
||||
static void
|
||||
async_client(int port, int id, int &count)
|
||||
{
|
||||
async_future w;
|
||||
|
||||
auto dest = Gio::NetworkAddress::new_loopback(port);
|
||||
|
||||
std::string sid = "client ";
|
||||
sid += std::to_string(id);
|
||||
|
||||
// connect a client
|
||||
std::cout << sid << ": connect" << std::endl;
|
||||
auto client = Gio::SocketClient::new_();
|
||||
client.connect_async(dest, w);
|
||||
auto conn = gi::expect(client.connect_finish(w.get()));
|
||||
|
||||
// say something
|
||||
auto os = conn.get_output_stream();
|
||||
std::cout << sid << ": send: " << sid << std::endl;
|
||||
os.write_all_async(
|
||||
(guint8 *)sid.data(), sid.size(), GLib::PRIORITY_DEFAULT_, w);
|
||||
os.write_all_finish(w.get(), (gsize *)nullptr);
|
||||
|
||||
// now hear what the other side has to say
|
||||
std::cout << sid << ": receive" << std::endl;
|
||||
auto is = conn.get_input_stream();
|
||||
while (1) {
|
||||
guint8 data[1024];
|
||||
is.read_async(data, sizeof(data), GLib::PRIORITY_DEFAULT_, w);
|
||||
auto size = gi::expect(is.read_finish(w.get()));
|
||||
if (!size)
|
||||
break;
|
||||
std::string msg(data, data + size);
|
||||
std::cout << sid << ": got data: " << msg << std::endl;
|
||||
}
|
||||
|
||||
std::cout << sid << ": closing down" << std::endl;
|
||||
--count;
|
||||
}
|
||||
|
||||
static void
|
||||
async_handle_client(Gio::SocketConnection conn)
|
||||
{
|
||||
async_future w;
|
||||
|
||||
// say hello
|
||||
auto os = conn.get_output_stream();
|
||||
std::string msg = "hello ";
|
||||
os.write_all_async(
|
||||
(guint8 *)msg.data(), msg.size(), GLib::PRIORITY_DEFAULT_, w);
|
||||
os.write_all_finish(w.get(), (gsize *)nullptr);
|
||||
|
||||
// now echo what the other side has to say
|
||||
auto is = conn.get_input_stream();
|
||||
while (1) {
|
||||
guint8 data[1024];
|
||||
// give up if timeout
|
||||
GLib::Error error;
|
||||
is.read_async(data, sizeof(data), GLib::PRIORITY_DEFAULT_,
|
||||
w.timeout(std::chrono::milliseconds(200)), w);
|
||||
auto size = gi::expect(is.read_finish(w.get(), &error));
|
||||
if (error) {
|
||||
if (error.matches(G_IO_ERROR, G_IO_ERROR_CANCELLED)) {
|
||||
break;
|
||||
} else {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
std::string msg(data, data + size);
|
||||
std::cout << "server: got data: " << msg << std::endl;
|
||||
os.write_all_async(data, size, GLib::PRIORITY_DEFAULT_, w);
|
||||
os.write_all_finish(w.get(), (gsize *)nullptr);
|
||||
}
|
||||
|
||||
std::cout << "server: closing down client" << std::endl;
|
||||
}
|
||||
|
||||
static void
|
||||
async_server(int clients, int &port)
|
||||
{
|
||||
async_future w;
|
||||
|
||||
auto listener = Gio::SocketListener::new_();
|
||||
port = gi::expect(listener.add_any_inet_port());
|
||||
|
||||
int count = 0;
|
||||
while (count < clients) {
|
||||
// accept clients
|
||||
std::cout << "server: accepting" << std::endl;
|
||||
listener.accept_async(w);
|
||||
auto conn = gi::expect(
|
||||
listener.accept_finish(w.get(), (GObject_::Object *)nullptr));
|
||||
|
||||
// spawn client handler
|
||||
std::cout << "server: new connection" << std::endl;
|
||||
boost::fibers::fiber c(async_handle_client, conn);
|
||||
c.detach();
|
||||
++count;
|
||||
}
|
||||
|
||||
// wait a bit and shutdown
|
||||
// wait long enough to test the out-of-loop join below
|
||||
boost::this_fiber::sleep_for(std::chrono::milliseconds(1000));
|
||||
std::cout << "server: shutdown" << std::endl;
|
||||
}
|
||||
|
||||
static void
|
||||
async_demo(GLib::MainLoop loop, int clients)
|
||||
{
|
||||
// run server
|
||||
// dispatch at once to obtain port
|
||||
int port = 0;
|
||||
boost::fibers::fiber server(
|
||||
boost::fibers::launch::dispatch, async_server, clients, std::ref(port));
|
||||
|
||||
// make clients
|
||||
int count = 0;
|
||||
for (int i = 0; i < clients; ++i) {
|
||||
++count;
|
||||
auto c = boost::fibers::fiber(async_client, port, i, std::ref(count));
|
||||
c.detach();
|
||||
}
|
||||
|
||||
// plain-and-simple; poll regularly and quit when all clients done
|
||||
auto check = [&]() {
|
||||
if (!count)
|
||||
loop.quit();
|
||||
return G_SOURCE_CONTINUE;
|
||||
};
|
||||
GLib::timeout_add(100, check);
|
||||
|
||||
std::cout << "running loop" << std::endl;
|
||||
loop.run();
|
||||
std::cout << "ending loop" << std::endl;
|
||||
|
||||
server.join();
|
||||
}
|
||||
|
||||
int
|
||||
main(int argc, char **argv)
|
||||
{
|
||||
GLib::MainLoop loop = GLib::MainLoop::new_();
|
||||
auto ctx = GLib::MainContext::default_();
|
||||
boost::fibers::use_scheduling_algorithm<context_scheduler>(ctx);
|
||||
|
||||
{ // basic fiber demo
|
||||
int count = 0;
|
||||
auto work = [&](const std::string &msg) {
|
||||
std::cout << msg << std::endl;
|
||||
++count;
|
||||
};
|
||||
auto quit = [&](int limit, const std::chrono::milliseconds &d) {
|
||||
while (count < limit)
|
||||
boost::this_fiber::sleep_for(d);
|
||||
loop.quit();
|
||||
};
|
||||
boost::fibers::fiber f1(work, "fiber 1");
|
||||
boost::fibers::fiber f2(work, "fiber 2");
|
||||
boost::fibers::fiber q(quit, 2, std::chrono::milliseconds(100));
|
||||
|
||||
loop.run();
|
||||
|
||||
f1.join();
|
||||
f2.join();
|
||||
q.join();
|
||||
}
|
||||
|
||||
// now an optional async GIO demo
|
||||
int clients = argc > 1 ? std::stoi(argv[1]) : 0;
|
||||
std::cout << clients << " clients" << std::endl;
|
||||
if (clients > 0)
|
||||
async_demo(loop, clients);
|
||||
}
|
||||
|
||||
#else
|
||||
int
|
||||
main(int argc, char **argv)
|
||||
{
|
||||
(void)argc;
|
||||
(void)argv;
|
||||
|
||||
std::cerr << "boost::fibers needs exceptions";
|
||||
}
|
||||
|
||||
#endif
|
||||
63
cmake/external/glib/cppgir/examples/gio-dbus-client.cpp
vendored
Normal file
63
cmake/external/glib/cppgir/examples/gio-dbus-client.cpp
vendored
Normal file
@@ -0,0 +1,63 @@
|
||||
#define GI_INLINE 1
|
||||
#include <gio/gio.hpp>
|
||||
|
||||
#include <iostream>
|
||||
|
||||
namespace GObject_ = gi::repository::GObject;
|
||||
namespace GLib = gi::repository::GLib;
|
||||
namespace Gio = gi::repository::Gio;
|
||||
|
||||
static GLib::MainLoop loop;
|
||||
|
||||
// many calls here support GError
|
||||
// so typically will throw here instead
|
||||
// (unless GError output is explicitly requested in call signature)
|
||||
|
||||
// NOTE abundancy of gi::expect not generally needed;
|
||||
// only needed when using --dl and --expected
|
||||
|
||||
static void
|
||||
on_reply(GObject_::Object ob, Gio::AsyncResult result)
|
||||
{
|
||||
// if not caught here, it will be caught before returning to plain C
|
||||
#if GI_CONFIG_EXCEPTIONS
|
||||
try {
|
||||
#endif
|
||||
auto connection = gi::object_cast<Gio::DBusConnection>(ob);
|
||||
|
||||
auto call_result = gi::expect(connection.call_finish(result));
|
||||
// get single array child
|
||||
auto names = gi::expect(call_result.get_child_value(0));
|
||||
int count = gi::expect(names.n_children());
|
||||
|
||||
std::cout << count << " message bus names: " << std::endl;
|
||||
for (int i = 0; i < count; ++i)
|
||||
std::cout << gi::expect(
|
||||
gi::expect(names.get_child_value(i)).get_string(nullptr))
|
||||
<< std::endl;
|
||||
#if GI_CONFIG_EXCEPTIONS
|
||||
} catch (const GLib::Error &error) {
|
||||
std::cerr << "error: '" << error.what() << "'." << std::endl;
|
||||
}
|
||||
#endif
|
||||
|
||||
// quit when idle
|
||||
GLib::idle_add([]() {
|
||||
loop.quit();
|
||||
return GLib::SOURCE_REMOVE_;
|
||||
});
|
||||
}
|
||||
|
||||
int
|
||||
main(int argc, char ** /*argv*/)
|
||||
{
|
||||
auto bustype = argc <= 1 ? Gio::BusType::SESSION_ : Gio::BusType::SYSTEM_;
|
||||
auto connection = gi::expect(Gio::bus_get_sync(bustype));
|
||||
|
||||
connection.call("org.freedesktop.DBus", "/org/freedesktop/DBus",
|
||||
"org.freedesktop.DBus", "ListNames", nullptr, nullptr,
|
||||
Gio::DBusCallFlags::NONE_, -1, nullptr, on_reply);
|
||||
|
||||
loop = gi::expect(GLib::MainLoop::new_());
|
||||
loop.run();
|
||||
}
|
||||
225
cmake/external/glib/cppgir/examples/gio-qt-async.cpp
vendored
Normal file
225
cmake/external/glib/cppgir/examples/gio-qt-async.cpp
vendored
Normal file
@@ -0,0 +1,225 @@
|
||||
#define GI_INLINE 1
|
||||
#include <gio/gio.hpp>
|
||||
|
||||
#include <functional>
|
||||
#include <iostream>
|
||||
#include <memory>
|
||||
|
||||
#include <QCoreApplication>
|
||||
|
||||
#include <QFuture>
|
||||
#include <QFutureWatcher>
|
||||
#include <QObject>
|
||||
|
||||
namespace GLib = gi::repository::GLib;
|
||||
namespace GObject_ = gi::repository::GObject;
|
||||
namespace Gio = gi::repository::Gio;
|
||||
|
||||
template<typename T>
|
||||
using ResultExtractor = std::function<void(
|
||||
GObject_::Object, Gio::AsyncResult result, QFutureInterface<T> &fut)>;
|
||||
|
||||
template<typename T>
|
||||
class qt_future
|
||||
{
|
||||
struct CallbackData
|
||||
{
|
||||
QFutureInterface<T> promise;
|
||||
QFutureWatcher<T> watcher;
|
||||
Gio::Cancellable cancel;
|
||||
ResultExtractor<T> handler;
|
||||
};
|
||||
|
||||
std::shared_ptr<CallbackData> data_;
|
||||
|
||||
public:
|
||||
qt_future(ResultExtractor<T> h)
|
||||
{
|
||||
data_ = std::make_shared<CallbackData>();
|
||||
data_->handler = h;
|
||||
}
|
||||
|
||||
QFuture<T> future() { return data_->promise.future(); }
|
||||
|
||||
QFutureInterface<T> promise() { return data_->promise; }
|
||||
|
||||
operator Gio::AsyncReadyCallback()
|
||||
{
|
||||
auto d = data_;
|
||||
return [d](GObject_::Object obj, Gio::AsyncResult result) {
|
||||
assert(d->handler);
|
||||
d->handler(obj, result, d->promise);
|
||||
};
|
||||
}
|
||||
|
||||
Gio::Cancellable cancellable()
|
||||
{
|
||||
if (!data_->cancel) {
|
||||
auto cancel = data_->cancel = Gio::Cancellable::new_();
|
||||
data_->watcher.setFuture(future());
|
||||
auto h = [cancel]() mutable { cancel.cancel(); };
|
||||
data_->watcher.connect(
|
||||
&data_->watcher, &decltype(data_->watcher)::finished, std::move(h));
|
||||
}
|
||||
return data_->cancel;
|
||||
}
|
||||
|
||||
// why not ... ??
|
||||
operator Gio::Cancellable() { return cancellable(); }
|
||||
};
|
||||
|
||||
#if GI_CONST_METHOD
|
||||
#define CONST_METHOD const
|
||||
#else
|
||||
#define CONST_METHOD
|
||||
#endif
|
||||
|
||||
template<typename Result, typename Object>
|
||||
qt_future<Result>
|
||||
make_future(Result (Object::*mf)(Gio::AsyncResult, GLib::Error *) CONST_METHOD)
|
||||
{
|
||||
ResultExtractor<Result> f;
|
||||
f = [mf](GObject_::Object cbobj, Gio::AsyncResult result,
|
||||
QFutureInterface<Result> &fut) mutable {
|
||||
auto obj = gi::object_cast<Object>(cbobj);
|
||||
GLib::Error error{};
|
||||
auto res = (obj.*mf)(result, &error);
|
||||
if (error.gobj_()) {
|
||||
// FIXME use std::expected<Result> as a result type ??
|
||||
// reportException might be an option, if enabled, but leads to throw
|
||||
fut.reportFinished();
|
||||
qWarning() << "error code " << error.code_();
|
||||
qWarning() << "error message "
|
||||
<< QString::fromUtf8(error.message_().c_str());
|
||||
} else {
|
||||
fut.reportFinished(&res);
|
||||
}
|
||||
};
|
||||
return {f};
|
||||
}
|
||||
|
||||
// ====
|
||||
// some small future-centric API wrappers using above helper class
|
||||
|
||||
QFuture<Gio::FileInfo>
|
||||
file_query_file_system_info(
|
||||
Gio::File f, const std::string &attributes, gint io_priority)
|
||||
{
|
||||
auto fut = make_future(&Gio::File::query_filesystem_info_finish);
|
||||
f.query_filesystem_info_async(attributes, io_priority, fut, fut);
|
||||
return fut.future();
|
||||
}
|
||||
|
||||
QFuture<bool>
|
||||
file_copy(Gio::File src, Gio::File destination, Gio::FileCopyFlags flags,
|
||||
gint io_priority)
|
||||
{
|
||||
auto fut = make_future(&Gio::File::copy_finish);
|
||||
auto promise = fut.promise();
|
||||
auto p = [promise](
|
||||
goffset current_num_bytes, goffset total_num_bytes) mutable {
|
||||
promise.setProgressRange(0, total_num_bytes);
|
||||
promise.setProgressValue(current_num_bytes);
|
||||
};
|
||||
src.copy_async(destination, flags, io_priority, fut, p, fut);
|
||||
return fut.future();
|
||||
}
|
||||
|
||||
// ====
|
||||
|
||||
// save on typing elsewhere
|
||||
// also makes it movable in a way
|
||||
template<typename T>
|
||||
std::shared_ptr<QFutureWatcher<T>>
|
||||
make_watcher(QFuture<T> fut)
|
||||
{
|
||||
auto watcher = std::make_shared<QFutureWatcher<T>>();
|
||||
watcher->setFuture(fut);
|
||||
return watcher;
|
||||
}
|
||||
|
||||
// type-erased owner/cleanup type
|
||||
using Retainer = std::shared_ptr<std::nullptr_t>;
|
||||
|
||||
Retainer
|
||||
do_query(QCoreApplication &app, const std::string &fpath)
|
||||
{
|
||||
auto file = Gio::File::new_for_commandline_arg(fpath);
|
||||
auto fut = file_query_file_system_info(file, "*", GLib::PRIORITY_DEFAULT_);
|
||||
auto watcher = make_watcher(fut);
|
||||
auto h = [fut, &app]() {
|
||||
Q_ASSERT(fut.isFinished());
|
||||
std::cout << "query finished" << std::endl;
|
||||
if (fut.resultCount()) {
|
||||
auto finfo = fut.result();
|
||||
for (auto attr : {Gio::FILE_ATTRIBUTE_FILESYSTEM_TYPE_,
|
||||
Gio::FILE_ATTRIBUTE_FILESYSTEM_FREE_,
|
||||
Gio::FILE_ATTRIBUTE_FILESYSTEM_SIZE_}) {
|
||||
std::cout << attr << ": " << finfo.get_attribute_as_string(attr)
|
||||
<< std::endl;
|
||||
}
|
||||
} else {
|
||||
std::cout << "... but no results" << std::endl;
|
||||
}
|
||||
app.quit();
|
||||
};
|
||||
watcher->connect(
|
||||
watcher.get(), &decltype(watcher)::element_type::finished, &app, h);
|
||||
return {watcher, nullptr};
|
||||
}
|
||||
|
||||
Retainer
|
||||
do_copy(QCoreApplication &app, const std::string &src, const std::string &dest)
|
||||
{
|
||||
auto fsrc = Gio::File::new_for_commandline_arg(src);
|
||||
auto fdest = Gio::File::new_for_commandline_arg(dest);
|
||||
auto fut = file_copy(
|
||||
fsrc, fdest, Gio::FileCopyFlags::ALL_METADATA_, GLib::PRIORITY_DEFAULT_);
|
||||
auto watcher = make_watcher(fut);
|
||||
auto h = [fut, &app]() {
|
||||
Q_ASSERT(fut.isFinished());
|
||||
std::cout << "copy finished" << std::endl;
|
||||
if (fut.resultCount()) {
|
||||
auto ok = fut.result();
|
||||
if (ok) {
|
||||
std::cout << "copy ok" << std::endl;
|
||||
} else {
|
||||
std::cout << "copy failed" << std::endl;
|
||||
}
|
||||
} else {
|
||||
std::cout << "... but no results" << std::endl;
|
||||
}
|
||||
app.quit();
|
||||
};
|
||||
watcher->connect(
|
||||
watcher.get(), &decltype(watcher)::element_type::finished, &app, h);
|
||||
// also monitor progress
|
||||
auto progress = [fut](int) {
|
||||
auto max = fut.progressMaximum();
|
||||
auto min = fut.progressMinimum();
|
||||
auto current = fut.progressValue();
|
||||
std::cout << "copy progress " << current << " (" << min << " -> " << max
|
||||
<< ")" << std::endl;
|
||||
};
|
||||
watcher->connect(watcher.get(),
|
||||
&decltype(watcher)::element_type::progressValueChanged, &app, progress);
|
||||
return {watcher, nullptr};
|
||||
}
|
||||
|
||||
int
|
||||
main(int argc, char **argv)
|
||||
{
|
||||
QCoreApplication app(argc, argv);
|
||||
|
||||
if (argc < 2)
|
||||
return -1;
|
||||
|
||||
Retainer r;
|
||||
if (argc == 2) {
|
||||
r = do_query(app, argv[1]);
|
||||
} else if (argc == 3) {
|
||||
r = do_copy(app, argv[1], argv[2]);
|
||||
}
|
||||
|
||||
return app.exec();
|
||||
}
|
||||
98
cmake/external/glib/cppgir/examples/gio.cpp
vendored
Normal file
98
cmake/external/glib/cppgir/examples/gio.cpp
vendored
Normal file
@@ -0,0 +1,98 @@
|
||||
#define GI_INLINE 1
|
||||
#include <gio/gio.hpp>
|
||||
|
||||
#include <iostream>
|
||||
|
||||
namespace GLib = gi::repository::GLib;
|
||||
namespace Gio = gi::repository::Gio;
|
||||
|
||||
const std::string localhost("127.0.0.1");
|
||||
static GLib::MainLoop loop;
|
||||
|
||||
// many calls here support GError
|
||||
// so typically will throw here instead
|
||||
// (unless GError output is explicitly requested in call signature)
|
||||
|
||||
// NOTE abundancy of gi::expect not generally needed;
|
||||
// only needed when using --dl and --expected
|
||||
|
||||
static bool
|
||||
receive(Gio::Socket s, GLib::IOCondition /*cond*/)
|
||||
{
|
||||
guint8 buffer[1024] = {
|
||||
0,
|
||||
};
|
||||
Gio::SocketAddress a;
|
||||
int count = gi::expect(s.receive_from(&a, buffer, sizeof(buffer)));
|
||||
if (count > 0) {
|
||||
// let's see where it came from
|
||||
std::string origin("someone");
|
||||
auto ia = gi::object_cast<Gio::InetSocketAddress>(a);
|
||||
if (ia) {
|
||||
origin = gi::expect(gi::expect(ia.get_address()).to_string());
|
||||
origin += ":";
|
||||
origin += std::to_string(gi::expect(ia.get_port()));
|
||||
}
|
||||
std::cout << origin << " said " << (char *)buffer << std::endl;
|
||||
// quit when idle
|
||||
GLib::idle_add([]() {
|
||||
loop.quit();
|
||||
return GLib::SOURCE_REMOVE_;
|
||||
});
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
Gio::Socket
|
||||
open(bool listen)
|
||||
{
|
||||
auto socket = gi::expect(Gio::Socket::new_(Gio::SocketFamily::IPV4_,
|
||||
Gio::SocketType::DATAGRAM_, Gio::SocketProtocol::DEFAULT_));
|
||||
|
||||
auto address =
|
||||
gi::expect(Gio::InetSocketAddress::new_from_string(localhost, 0));
|
||||
socket.bind(address, false);
|
||||
socket.set_blocking(false);
|
||||
|
||||
if (listen) {
|
||||
// runtime introspection has a hard time here,
|
||||
// but with a bit of extra information, we can keep going
|
||||
#if defined(GI_CALL_ARGS) && CALL_ARGS <= 1
|
||||
// so we should have this signature for a function with 1 non-required
|
||||
GLib::Source source =
|
||||
gi::expect(socket.create_source({.condition = GLib::IOCondition::IN_}));
|
||||
#else
|
||||
GLib::Source source =
|
||||
gi::expect(socket.create_source(GLib::IOCondition::IN_, nullptr));
|
||||
#endif
|
||||
source.set_callback<Gio::SocketSourceFunc>(receive);
|
||||
source.attach();
|
||||
}
|
||||
|
||||
return socket;
|
||||
}
|
||||
|
||||
static void
|
||||
die(const std::string &why)
|
||||
{
|
||||
std::cerr << why << std::endl;
|
||||
exit(2);
|
||||
}
|
||||
|
||||
int
|
||||
main(int argc, char **argv)
|
||||
{
|
||||
if (argc < 2)
|
||||
die("missing argument");
|
||||
|
||||
std::string msg = argv[1];
|
||||
std::cout << "will send message " << msg << std::endl;
|
||||
|
||||
auto recv = open(true);
|
||||
auto local = gi::expect(recv.get_local_address());
|
||||
auto send = open(false);
|
||||
send.send_to(local, (guint8 *)msg.data(), msg.size(), nullptr);
|
||||
|
||||
loop = gi::expect(GLib::MainLoop::new_());
|
||||
loop.run();
|
||||
}
|
||||
178
cmake/external/glib/cppgir/examples/gobject.cpp
vendored
Normal file
178
cmake/external/glib/cppgir/examples/gobject.cpp
vendored
Normal file
@@ -0,0 +1,178 @@
|
||||
// no code generation is needed
|
||||
// all required functionality is required by the basic code
|
||||
#include <gi/gi.hpp>
|
||||
|
||||
#include <iostream>
|
||||
|
||||
namespace GObject_ = gi::repository::GObject;
|
||||
|
||||
static const int DEFAULT_AGE = 10;
|
||||
static const char *PERSON_TYPE = "GIPerson";
|
||||
|
||||
// class must be a ObjectImpl to support properties and signals
|
||||
class Person : public GObject_::impl::ObjectImpl
|
||||
{
|
||||
using self_type = Person;
|
||||
|
||||
private:
|
||||
// the implementation/definition part of the properties
|
||||
// the setup parameters shadow the corresponding g_param_spec_xxx
|
||||
// so in practice define the property (name, nick, description)
|
||||
// along with min, max, default and so (where applicable)
|
||||
// (could also be in a constructor initializer list,
|
||||
// but this way it applies to any constructor)
|
||||
// this provides interface to set/get the actual value
|
||||
gi::property<int> prop_age_{this, "age", "age", "age", 0, 100, DEFAULT_AGE};
|
||||
gi::property<std::string> prop_firstname_{
|
||||
this, "firstname", "firstname", "firstname", ""};
|
||||
gi::property<std::string> prop_lastname_{
|
||||
this, "lastname", "lastname", "lastname", ""};
|
||||
|
||||
public:
|
||||
// likewise for signal
|
||||
// public because there is no extra interface for owning class
|
||||
// (both owner and outside can connect and/or emit)
|
||||
// btw, using Person in this signature would not be the way to go,
|
||||
// should stick to a plain wrapped type
|
||||
gi::signal<void(GObject_::Object, int)> signal_trigger{this, "trigger"};
|
||||
// std::string also works here,
|
||||
// but that would cost an extra allocation during invocation
|
||||
gi::signal<void(GObject_::Object, gi::cstring_v)> signal_example{
|
||||
this, "example"};
|
||||
|
||||
public:
|
||||
// old-style C++ first
|
||||
Person() : ObjectImpl(this) {}
|
||||
|
||||
// new-style C-first;
|
||||
// must use proper signature for constructor and superclass
|
||||
// this also registers a "public" GType, whose name must be specified
|
||||
Person(const InitData &id) : ObjectImpl(this, id, PERSON_TYPE) {}
|
||||
|
||||
// usually the above constructor is also used to during GType registration,
|
||||
// as such "dummy instantiation" auto-magically collects property info, etc)
|
||||
// however, it does involve an "incomplete" instantiation (with no .gobj_())
|
||||
// if such is considered too inelegant or too costly, then alternatively
|
||||
// a ::get_type_() can be defined that is then used to register type instead
|
||||
// however, as the example below demonstrates, this may be much less ergonomic
|
||||
// so it is likely only useful if really so desired or in advanced cases)
|
||||
// (e.g. subclass-ing a subclass)
|
||||
static GType get_type_disabled() // not really used due to extra suffix
|
||||
{
|
||||
// if this is used, the member initializers only need to mention name
|
||||
// no longer the full spec(ification)
|
||||
// (as the name then only serves to link the propertyspec to actual storage)
|
||||
return register_type_<Person>(PERSON_TYPE, 0, {},
|
||||
{{&self_type::prop_age_, "age", "age", "age", 0, 100, DEFAULT_AGE},
|
||||
{&self_type::prop_firstname_, "firstname", "firstname", "firstname",
|
||||
""},
|
||||
{&self_type::prop_lastname_, "lastname", "lastname", "lastname",
|
||||
""}},
|
||||
{{&self_type::signal_trigger, "trigger"},
|
||||
{&self_type::signal_example, "example"}});
|
||||
}
|
||||
|
||||
// the public counterpart providing the same interface
|
||||
// as with any wrapped object's predefined properties
|
||||
gi::property_proxy<int> prop_age() { return prop_age_.get_proxy(); }
|
||||
|
||||
gi::property_proxy<std::string> prop_firstname()
|
||||
{
|
||||
return prop_firstname_.get_proxy();
|
||||
}
|
||||
|
||||
gi::property_proxy<std::string> prop_lastname()
|
||||
{
|
||||
return prop_lastname_.get_proxy();
|
||||
}
|
||||
|
||||
void action(int id)
|
||||
{
|
||||
std::cout << "Changing the properties of 'p'" << std::endl;
|
||||
prop_firstname_ = "John";
|
||||
prop_lastname_ = "Doe";
|
||||
prop_age_ = 43;
|
||||
std::cout << "Done changing the properties of 'p'" << std::endl;
|
||||
// we were triggered after all
|
||||
signal_trigger.emit(id);
|
||||
}
|
||||
};
|
||||
|
||||
void
|
||||
on_firstname_changed(GObject_::Object, GObject_::ParamSpec)
|
||||
{
|
||||
std::cout << "- firstname changed!" << std::endl;
|
||||
}
|
||||
|
||||
void
|
||||
on_lastname_changed(GObject_::Object, GObject_::ParamSpec)
|
||||
{
|
||||
std::cout << "- lastname changed!" << std::endl;
|
||||
}
|
||||
|
||||
void
|
||||
on_age_changed(GObject_::Object, GObject_::ParamSpec)
|
||||
{
|
||||
std::cout << "- age changed!" << std::endl;
|
||||
}
|
||||
|
||||
int
|
||||
main(int /*argc*/, char ** /*argv*/)
|
||||
{
|
||||
Person p;
|
||||
// should have default age
|
||||
assert(p.prop_age().get() == DEFAULT_AGE);
|
||||
// register some handlers that will be called when the values of the
|
||||
// specified parameters are changed
|
||||
p.prop_firstname().signal_notify().connect(&on_firstname_changed);
|
||||
p.prop_lastname().signal_notify().connect(on_lastname_changed);
|
||||
p.prop_age().signal_notify().connect(&on_age_changed);
|
||||
|
||||
// now change the properties and see that the handlers get called
|
||||
p.action(0);
|
||||
|
||||
// (derived) object can be constructed on stack for simple cases
|
||||
// but in other (real) cases it is recommended that it is heap based.
|
||||
// so as not to have a naked ptr, it can be managed by a (special) shared
|
||||
// ptr that uses the GObject refcount for (shared) ownership tracking
|
||||
auto dp = gi::make_ref<Person, gi::construct_cpp_t>();
|
||||
// however, the GObject world has no knowledge of the subclass
|
||||
// (each instance is 1-to-1 with Person instance though).
|
||||
// so when we get something from that world, we can (sort-of dynamic)
|
||||
// cast to the subclass
|
||||
auto l = [](GObject_::Object ob, int id) {
|
||||
std::cout << " - triggered id " << id << std::endl;
|
||||
// obtain Person
|
||||
auto lp = gi::ref_ptr_cast<Person>(ob);
|
||||
if (lp)
|
||||
std::cout << " - it was a person!" << std::endl;
|
||||
// it really should be ...
|
||||
assert(lp);
|
||||
};
|
||||
dp->signal_trigger.connect(l);
|
||||
dp->action(1);
|
||||
|
||||
// all of the above constructed Person in C++ centric style
|
||||
// that is, as part of the constructor (super class) execution,
|
||||
// a (derived) GObject is created and associated/extended with C++ side Person
|
||||
|
||||
// alternatively, another derived GObject type can be registered,
|
||||
// which will trigger construction of a Person (as part of g_object_new()),
|
||||
// and then again associate those
|
||||
// the following essentially runs g_object_new()
|
||||
static int CUSTOM_AGE = 5;
|
||||
auto dpc = gi::make_ref<Person, gi::construct_c_t>("age", CUSTOM_AGE);
|
||||
// this will have a different GType
|
||||
assert(dpc->gobj_type_() != dp->gobj_type_());
|
||||
// as registered using specified name
|
||||
assert(g_type_from_name(PERSON_TYPE) == dpc->gobj_type_());
|
||||
// and the right age as specified in (gobject style) construct params
|
||||
assert(dpc->prop_age().get() == CUSTOM_AGE);
|
||||
|
||||
// if the class supports C-style, it is selected automatically
|
||||
auto dpa = gi::make_ref<Person>();
|
||||
// should have ended up with C-style GType
|
||||
assert(dpa->gobj_type_() == dpc->gobj_type_());
|
||||
|
||||
return 0;
|
||||
}
|
||||
419
cmake/external/glib/cppgir/examples/gst.cpp
vendored
Normal file
419
cmake/external/glib/cppgir/examples/gst.cpp
vendored
Normal file
@@ -0,0 +1,419 @@
|
||||
#include <functional>
|
||||
#include <iostream>
|
||||
#include <memory>
|
||||
#include <ostream>
|
||||
#include <sstream>
|
||||
#include <vector>
|
||||
|
||||
#include "assert.h"
|
||||
|
||||
#ifndef USE_GI_MODULE
|
||||
// no inline as the implementation is provided by other TUs
|
||||
// #define GI_INLINE 1
|
||||
#include <gst/gst.hpp>
|
||||
#else
|
||||
// optional, but recommended if gi macros are used
|
||||
#include <gi/gi_inc.hpp>
|
||||
// we also use some gst macros and api
|
||||
#include <gst/gst.h>
|
||||
// import used modules
|
||||
import gi.repo.gobject;
|
||||
import gi.repo.gst;
|
||||
#endif
|
||||
|
||||
namespace GLib = gi::repository::GLib;
|
||||
namespace Gst = gi::repository::Gst;
|
||||
namespace GObject_ = gi::repository::GObject;
|
||||
|
||||
void
|
||||
say(const std::string &msg)
|
||||
{
|
||||
std::cout << msg << std::endl;
|
||||
}
|
||||
|
||||
void
|
||||
die(const std::string &why)
|
||||
{
|
||||
std::cerr << why << std::endl;
|
||||
exit(2);
|
||||
}
|
||||
|
||||
class Player
|
||||
{
|
||||
typedef Player self;
|
||||
|
||||
GLib::MainLoop loop_;
|
||||
std::string url_;
|
||||
|
||||
Gst::Element playbin_;
|
||||
bool live_ = false;
|
||||
Gst::Element vfilter_;
|
||||
Gst::Element afilter_;
|
||||
std::map<std::string, Gst::Element> filter_;
|
||||
GLib::SourceScopedConnection monitor_;
|
||||
GObject_::SignalConnection busconn_;
|
||||
|
||||
public:
|
||||
Player(GLib::MainLoop loop, const std::string &url) : loop_(loop), url_(url)
|
||||
{
|
||||
playbin_ = Gst::ElementFactory::make("playbin");
|
||||
assert(playbin_);
|
||||
// get a property
|
||||
// this one is known at introspection time
|
||||
// (so type is known and suitable checked)
|
||||
auto name = playbin_.property_name().get();
|
||||
std::cout << "created playbin " << name << std::endl;
|
||||
}
|
||||
|
||||
void start()
|
||||
{
|
||||
// set some property
|
||||
playbin_.set_property("uri", url_);
|
||||
// ... or several ones
|
||||
playbin_.set_properties("volume", 0.5, "mute", false);
|
||||
// ... non-primitive also possible
|
||||
vfilter_ = Gst::ElementFactory::make("identity", "vfilter");
|
||||
afilter_ = Gst::ElementFactory::make("identity", "afilter");
|
||||
playbin_.set_properties("video-filter", vfilter_, "audio-filter", afilter_);
|
||||
|
||||
// connect some; find out what the source element is
|
||||
// signal not known to introspection, so have to provide signature here
|
||||
playbin_.connect<void(Gst::Element, Gst::Element)>(
|
||||
"source-setup", gi::mem_fun(&self::on_source_setup, this));
|
||||
// for a known signal, it is a bit easier to connect
|
||||
auto bus = playbin_.get_bus();
|
||||
bus.add_signal_watch();
|
||||
// signal connect; using introspected (hence checked) signal definition
|
||||
// could do without the slot step here and directly pass the lambda,
|
||||
// but wrapping it this way allows combining this with the returned
|
||||
// (plain) id into a scoped connection guard
|
||||
auto slot = bus.signal_message().slot(gi::mem_fun(&self::on_message, this));
|
||||
busconn_ =
|
||||
gi::make_connection(bus.signal_message().connect(slot), slot, bus);
|
||||
// again, if the guard is not desired; the following simply suffices
|
||||
if (false)
|
||||
bus.signal_message().connect(gi::mem_fun(&self::on_message, this));
|
||||
// if the signal signature is somehow not supported
|
||||
// then the following is a fallback manual method
|
||||
// which still allows to use lambda (and such)
|
||||
// and also provides some ownership management
|
||||
if (false) {
|
||||
auto h = [](GstBus *, GstMessage *) {
|
||||
// dummy no-op
|
||||
};
|
||||
bus.connect_unchecked<void(GstBus *, GstMessage *)>("message", h);
|
||||
}
|
||||
// likewise, if a callback is to be used whose arguments are not supported
|
||||
// or in a function that is not supported, then the following is a fallback
|
||||
// which still allows to use lambda (and such)
|
||||
// and also provides some ownership management
|
||||
if (false) {
|
||||
auto h = []() { return false; };
|
||||
auto cb = new gi::callback_wrapper<gboolean(), false>(h);
|
||||
// now the above pointer is managed as user data
|
||||
// and will be suitable destroyed when needed
|
||||
g_idle_add_full(0, &cb->wrapper, cb, &cb->destroy);
|
||||
// in case of a typical single-use async callback (with no GDestroyNotify)
|
||||
// use true as AUTODESTROY template parameter
|
||||
// (then it will auto-clean up after invoking callback)
|
||||
}
|
||||
// alternatively, maybe there is some API that accepts a Closure
|
||||
// the following are some convenient ways to get a closure
|
||||
if (false) {
|
||||
GLib::LogFunc h = [](gi::cstring_v log_domain,
|
||||
GLib::LogLevelFlags log_level,
|
||||
gi::cstring_v message) {
|
||||
(void)log_domain;
|
||||
(void)log_level;
|
||||
(void)message;
|
||||
};
|
||||
GObject_::Closure::from_callback(h);
|
||||
}
|
||||
// likewise, but with a bit more manual (C++) signature specification
|
||||
if (false) {
|
||||
auto h = [](GObject_::Object, int) {};
|
||||
GObject_::Closure::from_functor<void(GObject_::Object, int)>(h);
|
||||
}
|
||||
|
||||
say("Setting pipeline to PAUSED ...");
|
||||
auto ret = playbin_.set_state(Gst::State::PAUSED_);
|
||||
if (ret == Gst::StateChangeReturn::FAILURE_) {
|
||||
die("Pipeline does not want to pause");
|
||||
} else if (ret == Gst::StateChangeReturn::NO_PREROLL_) {
|
||||
say("Pipeline is live and does not need PREROLL ...");
|
||||
live_ = true;
|
||||
} else if (ret == Gst::StateChangeReturn::ASYNC_) {
|
||||
say("Pipeline is PREROLLING ...");
|
||||
}
|
||||
|
||||
// inspect after a while
|
||||
GLib::timeout_add_seconds(2, [this]() {
|
||||
inspect();
|
||||
return GLib::SOURCE_REMOVE_;
|
||||
});
|
||||
|
||||
// some regular progress reporting ...
|
||||
GLib::SourceFunc func = [this]() {
|
||||
progress();
|
||||
return GLib::SOURCE_CONTINUE_;
|
||||
};
|
||||
// the introspected function returns a plain id,
|
||||
// which can be used in the usual way to disconnect
|
||||
// e.g. at destructor time of owning object
|
||||
// alternatively, a helper scoped object can take care of that
|
||||
monitor_ = gi::make_connection(GLib::timeout_add_seconds(1, func), func);
|
||||
// other such make_connection variations exist;
|
||||
// e.g. for a signal connection, a probe callback
|
||||
// (and can easily be custom added)
|
||||
|
||||
// add a pad probe; use a casual lambda
|
||||
// but not too casual, mind (dangling) references/pointers though
|
||||
filter_["video"] = vfilter_;
|
||||
filter_["audio"] = afilter_;
|
||||
for (auto &&p : filter_) {
|
||||
if (p.second) {
|
||||
Gst::Pad pad = p.second.get_static_pad("sink");
|
||||
auto name = p.first;
|
||||
auto handler = [name](Gst::Pad p, Gst::PadProbeInfo_Ref info) {
|
||||
auto s = p.get_path_string();
|
||||
s += "received " + name + " buffer";
|
||||
auto buffer = info.get_buffer();
|
||||
if (buffer) {
|
||||
s += " of size ";
|
||||
s += std::to_string(buffer.get_size());
|
||||
}
|
||||
return Gst::PadProbeReturn::REMOVE_;
|
||||
};
|
||||
pad.add_probe(Gst::PadProbeType::BUFFER_, handler);
|
||||
}
|
||||
}
|
||||
|
||||
// shamelessly demo some helpers that aid in caps/value handling
|
||||
auto caps = Gst::Caps::new_empty_simple("video/x-raw");
|
||||
caps.set_value("width", GObject_::Value(Gst::IntRange(240, 320)));
|
||||
caps.set_value("pixel-aspect-ratio", GObject_::Value(Gst::Fraction(4, 3)));
|
||||
caps.set_value(
|
||||
"framerate", GObject_::Value(Gst::FractionRange({25, 1}, 30)));
|
||||
// retrieving pretty much the same way
|
||||
auto s = caps.get_structure(0);
|
||||
auto par = s.get_value("pixel-aspect-ratio").get_value<Gst::Fraction>();
|
||||
// helpers also stream to string properly
|
||||
std::ostringstream oss;
|
||||
oss << par;
|
||||
oss << (Gst::FlagSet(1, 1) == Gst::FlagSet(2, 1));
|
||||
// silly test code to exercise some operators
|
||||
// Rank override allows for succinct numeric conversion
|
||||
if (+Gst::Rank::PRIMARY_ + 0) {
|
||||
// flags support various typical operations
|
||||
(void)(Gst::PadProbeType::BUFFER_ | Gst::PadProbeType::BUFFER_LIST_);
|
||||
}
|
||||
}
|
||||
|
||||
void stop()
|
||||
{
|
||||
if (playbin_)
|
||||
playbin_.set_state(Gst::State::NULL_);
|
||||
loop_.quit();
|
||||
}
|
||||
|
||||
void on_message(Gst::Bus /*bus*/, Gst::Message_Ref msg)
|
||||
{
|
||||
auto &&src = msg.src_();
|
||||
switch (msg.type_()) {
|
||||
case Gst::MessageType::EOS_:
|
||||
say("Got EOS from " + src.get_path_string());
|
||||
stop();
|
||||
break;
|
||||
case Gst::MessageType::ERROR_: {
|
||||
GLib::Error err;
|
||||
gi::cstring debug;
|
||||
msg.parse_error(&err, &debug);
|
||||
say("Got error from " + src.get_path_string());
|
||||
if (debug.size())
|
||||
say("debug info:\n" + debug);
|
||||
break;
|
||||
}
|
||||
case Gst::MessageType::STATE_CHANGED_:
|
||||
/* only handle top-level case */
|
||||
if (src != playbin_)
|
||||
break;
|
||||
Gst::State old, new_, pending;
|
||||
msg.parse_state_changed(&old, &new_, &pending);
|
||||
if (new_ == Gst::State::PAUSED_ && old == Gst::State::READY_)
|
||||
playbin_.set_state(Gst::State::PLAYING_);
|
||||
break;
|
||||
default:
|
||||
// never mind
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void on_source_setup(Gst::Element /*pb*/, Gst::Element src)
|
||||
{
|
||||
say("source is " + src.get_path_string());
|
||||
}
|
||||
|
||||
void inspect()
|
||||
{
|
||||
std::ostringstream oss;
|
||||
// this should work
|
||||
auto bin = gi::object_cast<Gst::Bin>(playbin_);
|
||||
assert(bin);
|
||||
// a dynamically loaded element may implement a number of interfaces
|
||||
// which is not known at compile-time
|
||||
// the cast above can also cast to interface, which can be obtained as
|
||||
// follows if known at introspection/compile time
|
||||
auto cp = bin.interface_(gi::interface_tag<Gst::ChildProxy>());
|
||||
oss << "player bin has " << cp.get_children_count() << " children"
|
||||
<< std::endl;
|
||||
// get some properties (dynamically, i.e. not known at introspection
|
||||
// time) type will have to match (i.e. transformable) at runtime
|
||||
auto n_v = playbin_.get_property<int>("n-video");
|
||||
auto n_a = playbin_.get_property<int>("n-audio");
|
||||
// minimal stuff
|
||||
oss << "sample streams: video=" << n_v << ", audio=" << n_a << std::endl;
|
||||
// show some tag info
|
||||
for (auto &&p :
|
||||
std::map<std::string, int>{{"video", n_v}, {"audio", n_a}}) {
|
||||
for (int i = 0; i < p.second; ++i) {
|
||||
// the argument's type should match the signal definition
|
||||
// (cast if needed to make it so)
|
||||
auto action = std::string("get-");
|
||||
action += p.first + "-tags";
|
||||
auto taglist = playbin_.emit<Gst::TagList>(action, i);
|
||||
if (!taglist)
|
||||
continue;
|
||||
auto ntags = taglist.n_tags();
|
||||
oss << p.first << " stream " << i << std::endl;
|
||||
for (int j = 0; j < ntags; ++j) {
|
||||
auto tname = taglist.nth_tag_name(j);
|
||||
auto value = taglist.get_value_index(tname, 0);
|
||||
#if GI_CONFIG_EXCEPTIONS
|
||||
try {
|
||||
#endif
|
||||
auto sval = value.transform_value<std::string>();
|
||||
oss << " " << tname << ": " << sval << std::endl;
|
||||
#if GI_CONFIG_EXCEPTIONS
|
||||
} catch (...) {
|
||||
// could be object or otherwise, never mind
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// should also have caps here by now
|
||||
// there are other ways to obtain this, but let's go this way here
|
||||
for (auto &&p : filter_) {
|
||||
if (p.second) {
|
||||
Gst::Pad pad = p.second.get_static_pad("sink");
|
||||
auto caps = pad.get_current_caps();
|
||||
oss << p.first << " caps: " << caps.to_string() << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
say(oss.str());
|
||||
|
||||
say("Playbin elements:");
|
||||
// Gst::Iterator could be used with native interface
|
||||
// but a helper wrapper has been provided that supports ease-of-use as
|
||||
// in ...
|
||||
for (auto &&el : Gst::IteratorAdapter<Gst::Element>(bin.iterate_recurse()))
|
||||
say(el.get_path_string());
|
||||
say("");
|
||||
}
|
||||
|
||||
static std::string time_to_str(Gst::ClockTime time)
|
||||
{
|
||||
// could be done otherwise
|
||||
// but we have native C access at hand, so let's use that
|
||||
auto s = g_strdup_printf("%" GST_TIME_FORMAT, GST_TIME_ARGS(time));
|
||||
std::string ret(s);
|
||||
g_free(s);
|
||||
return ret;
|
||||
}
|
||||
|
||||
void progress()
|
||||
{
|
||||
bool ok = true;
|
||||
gint64 duration = -1, position = -1;
|
||||
ok &= playbin_.query_duration(Gst::Format::TIME_, &duration);
|
||||
ok &= playbin_.query_position(Gst::Format::TIME_, &position);
|
||||
std::ostringstream oss;
|
||||
if (ok) {
|
||||
oss << "Duration: " << time_to_str(duration)
|
||||
<< ", Position: " << time_to_str(position);
|
||||
} else {
|
||||
oss << "No progress info available";
|
||||
}
|
||||
say(oss.str());
|
||||
}
|
||||
};
|
||||
|
||||
#ifdef GI_CLASS_IMPL
|
||||
// not used in the above, but serves as a subclass example
|
||||
class ChattyBin : public Gst::impl::BinImpl
|
||||
{
|
||||
public:
|
||||
#if 0
|
||||
// this part is only needed if there is some conflict
|
||||
// (among members of class and/or interfaces)
|
||||
// otherwise it should be auto-detected
|
||||
struct DefinitionData
|
||||
{
|
||||
GI_DEFINES_MEMBER(BinClassDef, add_element, true)
|
||||
};
|
||||
#endif
|
||||
|
||||
ChattyBin() : Gst::impl::BinImpl(this) {}
|
||||
|
||||
bool add_element_(Gst::Element element) noexcept override
|
||||
{
|
||||
say("adding element " + element.name_() + '\n');
|
||||
return Gst::impl::BinImpl::add_element_(element);
|
||||
}
|
||||
};
|
||||
#endif
|
||||
|
||||
int
|
||||
main(int argc, char **argv)
|
||||
{
|
||||
if (argc < 2)
|
||||
die("missing argument");
|
||||
|
||||
// C signature fits C main best anyway
|
||||
gst_init(&argc, &argv);
|
||||
|
||||
std::string url = argv[1];
|
||||
// make it URL if not so
|
||||
if (!Gst::Uri::is_valid(url)) {
|
||||
#if GI_CONFIG_EXCEPTIONS
|
||||
try {
|
||||
#endif
|
||||
url = gi::expect(Gst::filename_to_uri(url));
|
||||
#if GI_CONFIG_EXCEPTIONS
|
||||
} catch (const GLib::Error &ex) {
|
||||
die(ex.what());
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
say("Playing " + url);
|
||||
|
||||
// simply local var will do here
|
||||
auto loop = GLib::MainLoop::new_();
|
||||
Player player(loop, url);
|
||||
|
||||
// schedule start
|
||||
GLib::idle_add([&] {
|
||||
player.start();
|
||||
return GLib::SOURCE_REMOVE_;
|
||||
});
|
||||
// ... and auto end after a while
|
||||
GLib::timeout_add_seconds(10, [&] {
|
||||
player.stop();
|
||||
return GLib::SOURCE_REMOVE_;
|
||||
});
|
||||
|
||||
loop.run();
|
||||
}
|
||||
95
cmake/external/glib/cppgir/examples/gtk-builder.ui
vendored
Normal file
95
cmake/external/glib/cppgir/examples/gtk-builder.ui
vendored
Normal file
@@ -0,0 +1,95 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<interface>
|
||||
<object class="GtkListStore" id="liststore1">
|
||||
<columns>
|
||||
<column type="gchararray"/>
|
||||
<column type="gchararray"/>
|
||||
<column type="gint"/>
|
||||
<column type="gchararray"/>
|
||||
</columns>
|
||||
<data>
|
||||
<row>
|
||||
<col id="0" translatable="yes">John</col>
|
||||
<col id="1" translatable="yes">Doe</col>
|
||||
<col id="2">25</col>
|
||||
<col id="3" translatable="yes">This is the John Doe row</col>
|
||||
</row>
|
||||
<row>
|
||||
<col id="0" translatable="yes">Mary</col>
|
||||
<col id="1" translatable="yes">Unknown</col>
|
||||
<col id="2">50</col>
|
||||
<col id="3" translatable="yes">This is the Mary Unknown row</col>
|
||||
</row>
|
||||
</data>
|
||||
</object>
|
||||
<object class="GtkWindow" id="window1">
|
||||
<property name="default-height">250</property>
|
||||
<property name="default-width">440</property>
|
||||
<property name="title" translatable="yes">Gtk::Builder demo</property>
|
||||
<child>
|
||||
<object class="GtkBox" id="vbox1">
|
||||
<property name="orientation">vertical</property>
|
||||
<child>
|
||||
<object class="GtkBox" id="toolbar1">
|
||||
<child>
|
||||
<object class="GtkButton">
|
||||
<property name="label" translatable="yes">Help</property>
|
||||
<property name="tooltip-text" translatable="yes">Help</property>
|
||||
<property name="action-name">win.help</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkScrolledWindow" id="scrolledwindow1">
|
||||
<property name="hexpand">True</property>
|
||||
<property name="vexpand">True</property>
|
||||
<child>
|
||||
<object class="GtkTreeView" id="treeview1">
|
||||
<property name="model">liststore1</property>
|
||||
<property name="tooltip-column">3</property>
|
||||
<child>
|
||||
<object class="GtkTreeViewColumn" id="column1">
|
||||
<property name="title">Name</property>
|
||||
<child>
|
||||
<object class="GtkCellRendererText" id="renderer1"/>
|
||||
<attributes>
|
||||
<attribute name="text">0</attribute>
|
||||
</attributes>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkTreeViewColumn" id="column2">
|
||||
<property name="title">Surname</property>
|
||||
<child>
|
||||
<object class="GtkCellRendererText" id="renderer2"/>
|
||||
<attributes>
|
||||
<attribute name="text">1</attribute>
|
||||
</attributes>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkTreeViewColumn" id="column3">
|
||||
<property name="title">Age</property>
|
||||
<child>
|
||||
<object class="GtkCellRendererText" id="renderer3"/>
|
||||
<attributes>
|
||||
<attribute name="text">2</attribute>
|
||||
</attributes>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<!-- INSERT -->
|
||||
<child>
|
||||
<object class="GtkStatusbar" id="statusbar1"/>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</interface>
|
||||
2
cmake/external/glib/cppgir/examples/gtk-obj.cpp
vendored
Normal file
2
cmake/external/glib/cppgir/examples/gtk-obj.cpp
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
#define GI_INCLUDE_IMPL 1
|
||||
#include <gtk/gtk.hpp>
|
||||
429
cmake/external/glib/cppgir/examples/gtk.cpp
vendored
Normal file
429
cmake/external/glib/cppgir/examples/gtk.cpp
vendored
Normal file
@@ -0,0 +1,429 @@
|
||||
#ifndef USE_GI_MODULE
|
||||
// no inline as the implementation is in another TU
|
||||
// #define GI_INLINE 1
|
||||
#include <gtk/gtk.hpp>
|
||||
#else
|
||||
// optional, but recommended if gi macros are used
|
||||
#include <gi/gi_inc.hpp>
|
||||
// we use some gtk macros
|
||||
#include <gtk/gtk.h>
|
||||
// import recursive module
|
||||
import gi.repo.gtk.rec;
|
||||
#endif
|
||||
|
||||
// adapt to API as needed
|
||||
#if GTK_CHECK_VERSION(4, 0, 0)
|
||||
#define GTK4 1
|
||||
#endif
|
||||
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
#include <tuple>
|
||||
#include <vector>
|
||||
|
||||
namespace GLib = gi::repository::GLib;
|
||||
namespace GObject_ = gi::repository::GObject;
|
||||
namespace Gtk = gi::repository::Gtk;
|
||||
|
||||
static GLib::MainLoop loop;
|
||||
|
||||
#ifdef GI_CLASS_IMPL
|
||||
// based on python-gtk3 example
|
||||
// https://python-gtk-3-tutorial.readthedocs.io/en/latest/treeview.html
|
||||
|
||||
// list of tuples for each software,
|
||||
// containing the software name, initial release, and main programming languages
|
||||
const std::vector<std::tuple<std::string, int, std::string>> software_list{
|
||||
std::make_tuple("Firefox", 2002, "C++"),
|
||||
std::make_tuple("Eclipse", 2004, "Java"),
|
||||
std::make_tuple("Pitivi", 2004, "Python"),
|
||||
std::make_tuple("Netbeans", 1996, "Java"),
|
||||
std::make_tuple("Chrome", 2008, "C++"),
|
||||
std::make_tuple("Filezilla", 2001, "C++"),
|
||||
std::make_tuple("Bazaar", 2005, "Python"),
|
||||
std::make_tuple("Git", 2005, "C"),
|
||||
std::make_tuple("Linux Kernel", 1991, "C"),
|
||||
std::make_tuple("GCC", 1987, "C"),
|
||||
std::make_tuple("Frostwire", 2004, "Java")};
|
||||
|
||||
class TreeViewFilterWindow : public Gtk::impl::WindowImpl
|
||||
{
|
||||
typedef TreeViewFilterWindow self_type;
|
||||
|
||||
Gtk::ListStore store_;
|
||||
Gtk::TreeModelFilter language_filter_;
|
||||
std::string current_filter_language_;
|
||||
|
||||
public:
|
||||
TreeViewFilterWindow() : Gtk::impl::WindowImpl(this)
|
||||
{
|
||||
Gtk::Window &self = *(this);
|
||||
self.set_title("TreeView filter demo");
|
||||
#ifdef GTK4
|
||||
#else
|
||||
self.set_border_width(10);
|
||||
#endif
|
||||
|
||||
// set up the grid in which elements are positioned
|
||||
auto grid = Gtk::Grid::new_();
|
||||
grid.set_column_homogeneous(true);
|
||||
grid.set_row_homogeneous(true);
|
||||
#ifdef GTK4
|
||||
self.set_child(grid);
|
||||
#if 0
|
||||
// this could work, but the annotation for .load_from_data
|
||||
// is too unstable across versions
|
||||
{ // migrate call above to CSS style
|
||||
const char *css = "grid { margin: 10px; }";
|
||||
auto provider = Gtk::CssProvider::new_();
|
||||
provider.load_from_data((guint8 *)css, -1);
|
||||
grid.get_style_context().add_provider(
|
||||
provider, Gtk::STYLE_PROVIDER_PRIORITY_USER_);
|
||||
}
|
||||
#endif
|
||||
#else
|
||||
self.add(grid);
|
||||
#endif
|
||||
|
||||
// create ListStore model
|
||||
store_ = Gtk::ListStore::new_type_<std::string, int, std::string>();
|
||||
for (auto &e : software_list) {
|
||||
auto it = store_.append();
|
||||
GObject_::Value cols[] = {std::get<0>(e), std::get<1>(e), std::get<2>(e)};
|
||||
for (unsigned i = 0; i < G_N_ELEMENTS(cols); ++i) {
|
||||
store_.set_value(it, i, cols[i]);
|
||||
}
|
||||
}
|
||||
|
||||
// create the filter, feeding it with the liststore model
|
||||
auto treemodel = store_.interface_(gi::interface_tag<Gtk::TreeModel>());
|
||||
language_filter_ =
|
||||
gi::object_cast<Gtk::TreeModelFilter>(treemodel.filter_new(nullptr));
|
||||
// set the filter function
|
||||
language_filter_.set_visible_func(
|
||||
gi::mem_fun(&self_type::language_filter_func, this));
|
||||
|
||||
// create the treeview, make it use the filter as a model, and add
|
||||
// columns
|
||||
auto treeview = Gtk::TreeView::new_with_model(language_filter_);
|
||||
int i = 0;
|
||||
for (auto &e : {"Software", "Release Year", "Programming Language"}) {
|
||||
auto renderer = Gtk::CellRendererText::new_();
|
||||
auto column = Gtk::TreeViewColumn::new_(e, renderer, {{"text", i}});
|
||||
treeview.append_column(column);
|
||||
++i;
|
||||
}
|
||||
|
||||
// create buttons to filter by programming language, and set up their
|
||||
// events
|
||||
std::vector<Gtk::Widget> buttons;
|
||||
for (auto &prog_language : {"Java", "C", "C++", "Python", "None"}) {
|
||||
auto button = Gtk::Button::new_with_label(prog_language);
|
||||
buttons.push_back(button);
|
||||
button.signal_clicked().connect(
|
||||
gi::mem_fun(&self_type::on_selection_button_clicked, this));
|
||||
}
|
||||
|
||||
// set up the layout;
|
||||
// put the treeview in a scrollwindow, and the buttons in a row
|
||||
auto scrollable_treelist = Gtk::ScrolledWindow::new_();
|
||||
scrollable_treelist.set_vexpand(true);
|
||||
grid.attach(scrollable_treelist, 0, 0, 8, 10);
|
||||
grid.attach_next_to(
|
||||
buttons[0], scrollable_treelist, Gtk::PositionType::BOTTOM_, 1, 1);
|
||||
auto it = buttons.begin() + 1;
|
||||
while (it != buttons.end()) {
|
||||
grid.attach_next_to(*it, *(it - 1), Gtk::PositionType::RIGHT_, 1, 1);
|
||||
++it;
|
||||
}
|
||||
#ifdef GTK4
|
||||
scrollable_treelist.set_child(treeview);
|
||||
self.show();
|
||||
#else
|
||||
scrollable_treelist.add(treeview);
|
||||
self.show_all();
|
||||
#endif
|
||||
}
|
||||
|
||||
bool language_filter_func(Gtk::TreeModel filter, Gtk::TreeIter_Ref it) const
|
||||
{
|
||||
if (current_filter_language_.empty() || current_filter_language_ == "None")
|
||||
return true;
|
||||
return current_filter_language_ ==
|
||||
filter.get_value(it, 2).get_value<std::string>();
|
||||
}
|
||||
|
||||
void on_selection_button_clicked(Gtk::Button button)
|
||||
{
|
||||
// set the current language filter to the button's label
|
||||
current_filter_language_ = button.get_label();
|
||||
std::cout << current_filter_language_ << " language selected!" << std::endl;
|
||||
// update the filter, which updates in turn the view
|
||||
language_filter_.refilter();
|
||||
}
|
||||
};
|
||||
|
||||
// other part based on gtkmm builder example
|
||||
namespace Gio = gi::repository::Gio;
|
||||
|
||||
auto templ = R"|(
|
||||
<interface>
|
||||
<template class="FooWidget" parent="GtkBox">
|
||||
<property name="orientation">GTK_ORIENTATION_HORIZONTAL</property>
|
||||
<property name="spacing">4</property>
|
||||
<child>
|
||||
<object class="GtkButton" id="hello_button">
|
||||
<property name="label">Hello World</property>
|
||||
<signal name="clicked" handler="hello_button_clicked"/>
|
||||
<signal name="clicked" handler="hello_button_clicked_object" object="label" swapped="no"/>
|
||||
<signal name="clicked" handler="hello_button_clicked_object_swapped" object="label" swapped="yes"/>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkButton" id="goodbye_button">
|
||||
<property name="label">Goodbye World</property>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkLabel" id="label">
|
||||
<property name="label">Greetings World</property>
|
||||
</object>
|
||||
</child>
|
||||
</template>
|
||||
</interface>
|
||||
)|";
|
||||
|
||||
auto templ_child = R"|(
|
||||
<child>
|
||||
<object class="FooWidget" id="foowidget1"/>
|
||||
</child>
|
||||
)|";
|
||||
|
||||
using WidgetTemplateHelper = Gtk::impl::WidgetTemplateHelper;
|
||||
|
||||
class FooWidget : public Gtk::impl::BoxImpl, public WidgetTemplateHelper
|
||||
{
|
||||
using self_type = FooWidget;
|
||||
|
||||
public:
|
||||
static void custom_class_init(GtkBoxClass *klass, gpointer)
|
||||
{
|
||||
// this is similar to the C case
|
||||
// plain C functions are used, as klass struct has no direct equivalent
|
||||
auto wklass = GTK_WIDGET_CLASS(klass);
|
||||
auto bytes = GLib::Bytes::new_static((const guint8 *)templ, strlen(templ));
|
||||
gtk_widget_class_set_template(wklass, bytes.gobj_());
|
||||
gtk_widget_class_bind_template_child_full(wklass, "hello_button", TRUE, 0);
|
||||
// chain up
|
||||
WidgetTemplateHelper::custom_class_init<gi::register_type<self_type>>(
|
||||
klass, nullptr);
|
||||
}
|
||||
|
||||
public:
|
||||
Gtk::Button hello_;
|
||||
|
||||
void on_hello(Gtk::Button) { std::cout << "Hi" << std::endl; }
|
||||
|
||||
void on_hello_tail(Gtk::Button b, Gtk::Label l)
|
||||
{
|
||||
g_assert(l.gobj_type_() == GTK_TYPE_LABEL);
|
||||
g_assert(b.gobj_type_() == GTK_TYPE_BUTTON);
|
||||
std::cout << "Hi tail" << std::endl;
|
||||
}
|
||||
|
||||
void on_hello_head(Gtk::Label l, Gtk::Button b)
|
||||
{
|
||||
g_assert(l.gobj_type_() == GTK_TYPE_LABEL);
|
||||
g_assert(b.gobj_type_() == GTK_TYPE_BUTTON);
|
||||
std::cout << "Hi head" << std::endl;
|
||||
}
|
||||
|
||||
FooWidget(const InitData &id)
|
||||
: Gtk::impl::BoxImpl(this, id, "FooWidget"),
|
||||
WidgetTemplateHelper(object_())
|
||||
{
|
||||
// skip registration case
|
||||
if (!id) {
|
||||
// as we have defined a get_type_() below, that should not happen
|
||||
g_assert_not_reached();
|
||||
return;
|
||||
}
|
||||
|
||||
// locate object
|
||||
hello_ = gi::object_cast<Gtk::Button>(
|
||||
get_template_child(gobj_type_(), "hello_button"));
|
||||
|
||||
// setup signal
|
||||
// NOTE the "manual" signature here should suitably match
|
||||
// (also considering object/swapped flags, etc)
|
||||
auto ok = set_handler<void(Gtk::Button)>(
|
||||
"hello_button_clicked", gi::mem_fun(&self_type::on_hello, this));
|
||||
g_assert(ok);
|
||||
ok = set_handler<void(Gtk::Button, Gtk::Label), ConnectObject::TAIL>(
|
||||
"hello_button_clicked_object",
|
||||
gi::mem_fun(&self_type::on_hello_tail, this));
|
||||
g_assert(ok);
|
||||
ok = set_handler<void(Gtk::Label, Gtk::Button), ConnectObject::HEAD>(
|
||||
"hello_button_clicked_object_swapped",
|
||||
gi::mem_fun(&self_type::on_hello_head, this));
|
||||
g_assert(ok);
|
||||
}
|
||||
|
||||
static GType get_type_()
|
||||
{
|
||||
return register_type_<FooWidget>("FooWidget", 0, {}, {}, {});
|
||||
}
|
||||
};
|
||||
|
||||
class ExampleWindow : public Gtk::impl::WindowImpl
|
||||
{
|
||||
using self_type = ExampleWindow;
|
||||
|
||||
public:
|
||||
ExampleWindow(Gtk::Window base, Gtk::Builder builder)
|
||||
: Gtk::impl::WindowImpl(base, this, "ExampleWindow")
|
||||
// custom name parameter is optional, but can be provided if used in .ui
|
||||
{
|
||||
(void)builder;
|
||||
setup();
|
||||
}
|
||||
|
||||
// name parameter is required and specifies registered type as-is
|
||||
// (so some proper namespace prefix is advisable in real case)
|
||||
ExampleWindow(const InitData &id)
|
||||
: Gtk::impl::WindowImpl(this, id, "ExampleWindow")
|
||||
{
|
||||
// skip registration case
|
||||
if (id)
|
||||
setup();
|
||||
}
|
||||
|
||||
void setup()
|
||||
{
|
||||
auto actions = Gio::SimpleActionGroup::new_();
|
||||
auto am = Gio::ActionMap(actions);
|
||||
auto action = Gio::SimpleAction::new_("help");
|
||||
action.signal_activate().connect(gi::mem_fun(&self_type::on_help, this));
|
||||
am.add_action(action);
|
||||
insert_action_group("win", actions);
|
||||
}
|
||||
|
||||
void on_help(Gio::Action, GLib::Variant) { std::cout << "Help" << std::endl; }
|
||||
|
||||
// subclass_type == 0; UI specifies GtkWindow
|
||||
// subclass_type < 0; UI specifies GIOBJECT__ExampleWindow
|
||||
// in either cases above; manual C++ association is needed
|
||||
// (uses first constructor)
|
||||
// -> NOT recommended
|
||||
// subclass_type > 0; UI specifies ExampleWindow;
|
||||
// all is properly created by (C-side) instance construction
|
||||
// (uses second constructor)
|
||||
// -> recommended
|
||||
// subclass_type > 1; also insert a FooWidget
|
||||
// (also all created by C-side construction)
|
||||
static Gtk::Window build(int subclass_type)
|
||||
{
|
||||
const char *UIFILE = G_STRINGIFY(EXAMPLES_DIR) "/gtk-builder.ui";
|
||||
const char *WINID = "window1";
|
||||
|
||||
auto builder = Gtk::Builder::new_();
|
||||
// if the subtype has additional functionality (e.g. method overrides)
|
||||
// then builder should instantiate using that type,
|
||||
// otherwise base type suffices
|
||||
// NOTE in the former case, there is a "short time" that the GObject side
|
||||
// exists without a corresponding C++ side, so any attempt to use the
|
||||
// extended parts (e.g. method calls) will be unfortunate
|
||||
if (subclass_type) {
|
||||
std::string WINCLASS = "GtkWindow";
|
||||
std::ifstream f(UIFILE, std::ios::binary);
|
||||
std::string ui;
|
||||
std::getline(f, ui, '\0');
|
||||
|
||||
auto index = ui.find(WINCLASS);
|
||||
assert(index != ui.npos);
|
||||
ui.replace(ui.begin() + index, ui.begin() + index + WINCLASS.size(),
|
||||
subclass_type < 0 ? "GIOBJECT__ExampleWindow" : "ExampleWindow");
|
||||
if (subclass_type > 1) {
|
||||
// sprinkle a template instance
|
||||
std::string MARKER = "<!-- INSERT -->";
|
||||
index = ui.find(MARKER);
|
||||
ui.replace(ui.begin() + index, ui.begin() + index + MARKER.size(),
|
||||
templ_child);
|
||||
// ensure type registered
|
||||
gi::register_type<FooWidget>();
|
||||
}
|
||||
// now we got a UI file that references the subclass type
|
||||
// ensure the latter is registered
|
||||
if (subclass_type < 0) {
|
||||
// use a temporary instance
|
||||
gi::make_ref<ExampleWindow, gi::construct_cpp_t>(nullptr, builder);
|
||||
} else {
|
||||
// a new cleaner way
|
||||
gi::register_type<ExampleWindow>();
|
||||
}
|
||||
builder.add_from_string(ui, -1);
|
||||
} else {
|
||||
builder.add_from_file(UIFILE);
|
||||
}
|
||||
if (false) {
|
||||
// some compile checks
|
||||
builder.get_object(WINID);
|
||||
builder.get_object<Gtk::Window>(WINID);
|
||||
}
|
||||
if (subclass_type > 1) {
|
||||
// verify proper C++ side setup
|
||||
auto foo_obj =
|
||||
builder.get_object<FooWidget::baseclass_type>("foowidget1");
|
||||
assert(foo_obj);
|
||||
auto foo = gi::ref_ptr_cast<FooWidget>(foo_obj);
|
||||
assert(foo);
|
||||
assert(foo->hello_);
|
||||
}
|
||||
// the special _derived may be needed to setup C++ side and associate
|
||||
return subclass_type > 0 ? builder.get_object<Gtk::Window>(WINID)
|
||||
: builder.get_object_derived<self_type>(WINID);
|
||||
}
|
||||
};
|
||||
#endif // GI_CLASS_IMPL
|
||||
|
||||
int
|
||||
main(int argc, char **argv)
|
||||
{
|
||||
#ifdef GTK4
|
||||
(void)argc;
|
||||
(void)argv;
|
||||
gtk_init();
|
||||
#else
|
||||
gtk_init(&argc, &argv);
|
||||
#endif
|
||||
|
||||
loop = GLib::MainLoop::new_();
|
||||
|
||||
// recommended general approach iso stack based
|
||||
// too much vmethod calling which is not safe for plain case
|
||||
Gtk::Window win;
|
||||
#ifdef GI_CLASS_IMPL
|
||||
// silly compile/link check
|
||||
static_assert(gi::transfer_full.value == 1, "");
|
||||
if (argc == gi::transfer_full.value) {
|
||||
win = gi::make_ref<TreeViewFilterWindow>();
|
||||
} else {
|
||||
win = ExampleWindow::build(std::stoi(argv[1]));
|
||||
}
|
||||
// TODO auto-handle arg ignore ??
|
||||
#ifdef GTK4
|
||||
win.signal_close_request().connect([](Gtk::Window) {
|
||||
loop.quit();
|
||||
return true;
|
||||
});
|
||||
win.show();
|
||||
#else
|
||||
win.signal_destroy().connect([](Gtk::Widget) { loop.quit(); });
|
||||
win.show_all();
|
||||
#endif
|
||||
#else // GI_CLASS_IMPL
|
||||
(void)win;
|
||||
#endif
|
||||
|
||||
loop.run();
|
||||
}
|
||||
Reference in New Issue
Block a user