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

This commit is contained in:
allhaileris
2026-02-16 15:50:16 +03:00
commit afb81b8278
13816 changed files with 3689732 additions and 0 deletions

View 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(); }
};

View 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)

View 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).

View 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")

View 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,
)

View File

@@ -0,0 +1 @@
#include "../../gio.cpp"

View File

@@ -0,0 +1 @@
#include "../../gobject.cpp"

View File

@@ -0,0 +1,5 @@
[wrap-git]
url = https://gitlab.com/mnauw/cppgir.git
revision = master
clone-recursive = true
depth = 1

View 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);
}

View 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

View 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();
}

View 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();
}

View 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();
}

View 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;
}

View 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();
}

View 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>

View File

@@ -0,0 +1,2 @@
#define GI_INCLUDE_IMPL 1
#include <gtk/gtk.hpp>

View 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();
}