Implement Cast Streaming mirroring, DLNA casting, daemon+GUI, and breadd integration
Some checks failed
dev release / build (push) Failing after 12s

Builds out the full v1 scope: a vendored+patched openscreen subset for
low-latency Cast Streaming (Mirroring receiver 0F5096E8) alongside the
existing Cast V2/HLS and new DLNA/AVTransport casting paths, breadcastd's
Idle/Casting state machine with a private IPC socket, the breadcast GTK4
popup as a thin IPC client, and bread.cast.*/bread.command.cast.* breadd
integration (device discovery, start/stop, mirroring lifecycle events).
Also adds bakery/systemd/Forgejo CI packaging.

Validated end-to-end against a real Chromecast/Google TV: negotiated
Cast Streaming session, live pipeline playback, and daemon+GUI click-to-cast/
stop through the actual popup.
This commit is contained in:
Breadway 2026-08-03 09:07:21 +08:00
parent 887c29002f
commit 8c745d18e0
283 changed files with 36788 additions and 0 deletions

View file

@ -0,0 +1,84 @@
// Copyright 2019 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef UTIL_TRACE_LOGGING_MACRO_SUPPORT_H_
#define UTIL_TRACE_LOGGING_MACRO_SUPPORT_H_
#ifndef INCLUDING_FROM_UTIL_TRACE_LOGGING_H_
#error "Do not include this header directly. Use util/trace_logging.h."
#endif
#ifndef ENABLE_TRACE_LOGGING
#error "BUG: This file should not have been reached."
#endif
#include "platform/api/trace_logging_platform.h"
#include "platform/base/trace_logging_activation.h"
#include "platform/base/trace_logging_types.h"
#include "util/trace_logging/scoped_trace_operations.h"
// Helper macros. These are used to simplify the macros below.
// NOTE: These cannot be #undef'd or they will stop working outside this file.
// NOTE: Two of these below macros are intentionally the same. This is to work
// around optimizations in the C++ Precompiler.
#define TRACE_INTERNAL_CONCAT(a, b) a##b
#define TRACE_INTERNAL_CONCAT_CONST(a, b) TRACE_INTERNAL_CONCAT(a, b)
#define TRACE_INTERNAL_UNIQUE_VAR_NAME(a) \
TRACE_INTERNAL_CONCAT_CONST(a, __LINE__)
namespace openscreen::internal {
inline bool IsTraceLoggingEnabled(TraceCategory category) {
const CurrentTracingDestination destination;
return destination && destination->IsTraceLoggingEnabled(category);
}
} // namespace openscreen::internal
#define TRACE_IS_ENABLED(category) \
openscreen::internal::IsTraceLoggingEnabled(category)
// Internal logging macros.
#define TRACE_SET_HIERARCHY_INTERNAL(line, ids) \
alignas(32) uint8_t TRACE_INTERNAL_CONCAT_CONST( \
tracing_storage, line)[sizeof(openscreen::internal::TraceIdSetter)]; \
[[maybe_unused]] \
const auto TRACE_INTERNAL_UNIQUE_VAR_NAME(trace_ref_) = \
TRACE_IS_ENABLED(openscreen::TraceCategory::kAny) \
? openscreen::internal::TraceInstanceHelper< \
openscreen::internal::TraceIdSetter>:: \
Create(TRACE_INTERNAL_CONCAT_CONST(tracing_storage, line), \
ids) \
: openscreen::internal::TraceInstanceHelper< \
openscreen::internal::TraceIdSetter>::Empty()
#define TRACE_SCOPED_INTERNAL(line, category, name, ...) \
alignas(32) uint8_t TRACE_INTERNAL_CONCAT_CONST( \
tracing_storage, \
line)[sizeof(openscreen::internal::SynchronousTraceLogger)]; \
[[maybe_unused]] \
const auto TRACE_INTERNAL_UNIQUE_VAR_NAME(trace_ref_) = \
TRACE_IS_ENABLED(category) \
? openscreen::internal::TraceInstanceHelper< \
openscreen::internal::SynchronousTraceLogger>:: \
Create(TRACE_INTERNAL_CONCAT_CONST(tracing_storage, line), \
category, name, __FILE__, __LINE__, ##__VA_ARGS__) \
: openscreen::internal::TraceInstanceHelper< \
openscreen::internal::SynchronousTraceLogger>::Empty()
#define TRACE_ASYNC_START_INTERNAL(line, category, name, ...) \
alignas(32) uint8_t TRACE_INTERNAL_CONCAT_CONST( \
temp_storage, \
line)[sizeof(openscreen::internal::AsynchronousTraceLogger)]; \
[[maybe_unused]] \
const auto TRACE_INTERNAL_UNIQUE_VAR_NAME(trace_ref_) = \
TRACE_IS_ENABLED(category) \
? openscreen::internal::TraceInstanceHelper< \
openscreen::internal::AsynchronousTraceLogger>:: \
Create(TRACE_INTERNAL_CONCAT_CONST(temp_storage, line), \
category, name, __FILE__, __LINE__, ##__VA_ARGS__) \
: openscreen::internal::TraceInstanceHelper< \
openscreen::internal::AsynchronousTraceLogger>::Empty()
#endif // UTIL_TRACE_LOGGING_MACRO_SUPPORT_H_

View file

@ -0,0 +1,160 @@
// Copyright 2018 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "util/trace_logging/scoped_trace_operations.h"
#include "platform/api/trace_logging_platform.h"
#include "platform/base/trace_logging_activation.h"
#include "util/osp_logging.h"
#if defined(ENABLE_TRACE_LOGGING)
namespace openscreen::internal {
// static
bool ScopedTraceOperation::TraceAsyncEnd(const uint32_t line,
const char* file,
TraceId id,
Error::Code e) {
const CurrentTracingDestination destination;
if (destination) {
TraceEvent end_event;
end_event.start_time = Clock::now();
end_event.line_number = line;
end_event.file_name = file;
end_event.ids.current = id;
end_event.result = e;
destination->LogAsyncEnd(std::move(end_event));
return true;
}
return false;
}
// static
bool ScopedTraceOperation::TraceFlow(
TraceCategory category,
const char* name,
const char* file,
uint32_t line,
uint64_t flow_id,
FlowType type,
std::optional<Clock::time_point> timestamp) {
const CurrentTracingDestination destination;
if (destination) {
const auto start_time = timestamp ? *timestamp : Clock::now();
TraceEvent event(category, start_time, name, file, line);
event.flow_ids.push_back(flow_id);
destination->LogFlow(std::move(event), type);
return true;
}
return false;
}
ScopedTraceOperation::ScopedTraceOperation(TraceId trace_id,
TraceId parent_id,
TraceId root_id) {
if (traces_ == nullptr) {
// Create the stack if it doesnt' exist.
traces_ = new TraceStack();
// Create a new root node. This will re-call this constructor and add the
// root node to the stack before proceeding with the original node.
root_node_ = new TraceIdSetter(TraceIdHierarchy::Empty());
OSP_CHECK(!traces_->empty());
}
// Setting trace id fields.
root_id_ = root_id != kUnsetTraceId ? root_id : traces_->top()->root_id_;
parent_id_ =
parent_id != kUnsetTraceId ? parent_id : traces_->top()->trace_id_;
trace_id_ =
trace_id != kUnsetTraceId ? trace_id : trace_id_counter_.fetch_add(1);
// Add this item to the stack.
traces_->push(this);
OSP_CHECK_LT(traces_->size(), 1024);
}
ScopedTraceOperation::~ScopedTraceOperation() {
OSP_CHECK(traces_ != nullptr && !traces_->empty());
OSP_CHECK_EQ(traces_->top(), this);
traces_->pop();
// If there's only one item left, it must be the root node. Deleting the root
// node will re-call this destructor and delete the traces_ stack.
if (traces_->size() == 1) {
OSP_CHECK_EQ(traces_->top(), root_node_);
delete root_node_;
root_node_ = nullptr;
} else if (traces_->empty()) {
delete traces_;
traces_ = nullptr;
}
}
// static
thread_local ScopedTraceOperation::TraceStack* ScopedTraceOperation::traces_ =
nullptr;
// static
thread_local ScopedTraceOperation* ScopedTraceOperation::root_node_ = nullptr;
// static
std::atomic<std::uint64_t> ScopedTraceOperation::trace_id_counter_{
uint64_t{0x01} << (sizeof(TraceId) * 8 - 1)};
TraceLoggerBase::TraceLoggerBase(TraceCategory category,
const char* name,
const char* file,
uint32_t line,
std::vector<TraceEvent::Argument> arguments,
TraceId current,
TraceId parent,
TraceId root)
: ScopedTraceOperation(current, parent, root),
event_(category, Clock::now(), name, file, line) {
event_.arguments = std::move(arguments);
event_.TruncateStrings();
}
TraceLoggerBase::TraceLoggerBase(TraceCategory category,
const char* name,
const char* file,
uint32_t line,
std::vector<TraceEvent::Argument> arguments,
TraceIdHierarchy ids)
: TraceLoggerBase(category,
name,
file,
line,
std::move(arguments),
ids.current,
ids.parent,
ids.root) {}
SynchronousTraceLogger::~SynchronousTraceLogger() {
const CurrentTracingDestination destination;
if (destination) {
const auto end_time = Clock::now();
event_.ids = to_hierarchy();
destination->LogTrace(event_, end_time);
}
}
AsynchronousTraceLogger::~AsynchronousTraceLogger() {
const CurrentTracingDestination destination;
if (destination) {
event_.ids = to_hierarchy();
destination->LogAsyncStart(event_);
}
}
TraceIdSetter::TraceIdSetter(TraceIdHierarchy ids)
: ScopedTraceOperation(ids.current, ids.parent, ids.root) {}
TraceIdSetter::~TraceIdSetter() = default;
} // namespace openscreen::internal
#endif // defined(ENABLE_TRACE_LOGGING)

View file

@ -0,0 +1,224 @@
// Copyright 2019 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef UTIL_TRACE_LOGGING_SCOPED_TRACE_OPERATIONS_H_
#define UTIL_TRACE_LOGGING_SCOPED_TRACE_OPERATIONS_H_
#include <atomic>
#include <cstring>
#include <memory>
#include <optional>
#include <stack>
#include <utility>
#include <vector>
#include "platform/api/time.h"
#include "platform/api/trace_logging_platform.h"
#include "platform/base/error.h"
#include "platform/base/trace_logging_types.h"
#include "util/osp_logging.h"
#if defined(ENABLE_TRACE_LOGGING)
namespace openscreen::internal {
// A base class for all trace logging objects which will create new entries in
// the Trace Hierarchy.
// 1) The sharing of all static and thread_local variables across template
// specializations.
// 2) Including all children in the same traces vector.
class ScopedTraceOperation {
public:
// Define the destructor to remove this item from the stack when it's
// destroyed.
virtual ~ScopedTraceOperation();
ScopedTraceOperation(const ScopedTraceOperation&) = delete;
ScopedTraceOperation(ScopedTraceOperation&&) noexcept = delete;
ScopedTraceOperation& operator=(const ScopedTraceOperation&) = delete;
ScopedTraceOperation& operator=(ScopedTraceOperation&&) = delete;
// Getters the current Trace Hierarchy. If the traces_ stack hasn't been
// created yet, return as if the empty root node is there.
static TraceId current_id() {
return traces_ == nullptr ? kEmptyTraceId : traces_->top()->trace_id_;
}
static TraceId root_id() {
return traces_ == nullptr ? kEmptyTraceId : traces_->top()->root_id_;
}
static TraceIdHierarchy hierarchy() {
if (traces_ == nullptr) {
return TraceIdHierarchy::Empty();
}
return traces_->top()->to_hierarchy();
}
// Static method to set the result of the most recent trace.
static void set_result(const Error& error) { set_result(error.code()); }
static void set_result(Error::Code error) {
if (traces_ == nullptr) {
return;
}
traces_->top()->SetTraceResult(error);
}
// Traces the end of an asynchronous call.
// NOTE: This returns a bool rather than a void because it keeps the syntax of
// the ternary operator in the macros simpler.
static bool TraceAsyncEnd(const uint32_t line,
const char* file,
TraceId id,
Error::Code e);
// Traces a flow event.
static bool TraceFlow(
TraceCategory category,
const char* name,
const char* file,
uint32_t line,
uint64_t flow_id,
FlowType type,
std::optional<Clock::time_point> timestamp = std::nullopt);
protected:
// Sets the result of this trace log.
// NOTE: this must be define in this class rather than TraceLogger so that it
// can be called on traces.back() without a potentially unsafe cast or type
// checking at runtime.
virtual void SetTraceResult(Error::Code error) = 0;
// Constructor to set all trace id information.
ScopedTraceOperation(TraceId current_id = kUnsetTraceId,
TraceId parent_id = kUnsetTraceId,
TraceId root_id = kUnsetTraceId);
// Current TraceId information.
TraceId trace_id_;
TraceId parent_id_;
TraceId root_id_;
TraceIdHierarchy to_hierarchy() { return {trace_id_, parent_id_, root_id_}; }
private:
// NOTE: A std::vector is used for backing the stack because it provides the
// best perf. Further perf improvement could be achieved later by swapping
// this out for a circular buffer once OSP supports that. Additional details
// can be found here:
// https://www.codeproject.com/Articles/1185449/Performance-of-a-Circular-Buffer-vs-Vector-Deque-a
using TraceStack =
std::stack<ScopedTraceOperation*, std::vector<ScopedTraceOperation*>>;
// Counter to pick IDs when it is not provided.
static std::atomic<std::uint64_t> trace_id_counter_;
// The LIFO stack of TraceLoggers currently being watched by this
// thread.
static thread_local TraceStack* traces_;
static thread_local ScopedTraceOperation* root_node_;
};
// The class which does actual trace logging.
class TraceLoggerBase : public ScopedTraceOperation {
public:
TraceLoggerBase(TraceCategory category,
const char* name,
const char* file,
uint32_t line,
std::vector<TraceEvent::Argument> arguments = {},
TraceId current = kUnsetTraceId,
TraceId parent = kUnsetTraceId,
TraceId root = kUnsetTraceId);
TraceLoggerBase(TraceCategory category,
const char* name,
const char* file,
uint32_t line,
std::vector<TraceEvent::Argument> arguments,
TraceIdHierarchy ids);
TraceLoggerBase(const TraceLoggerBase&) = delete;
TraceLoggerBase(TraceLoggerBase&&) noexcept = delete;
TraceLoggerBase& operator=(const TraceLoggerBase&) = delete;
TraceLoggerBase& operator=(TraceLoggerBase&&) = delete;
protected:
// Set the result.
void SetTraceResult(Error::Code error) override { event_.result = error; }
TraceEvent event_;
};
class SynchronousTraceLogger : public TraceLoggerBase {
public:
using TraceLoggerBase::TraceLoggerBase;
SynchronousTraceLogger(const SynchronousTraceLogger&) = delete;
SynchronousTraceLogger(SynchronousTraceLogger&&) noexcept = delete;
SynchronousTraceLogger& operator=(const SynchronousTraceLogger&) = delete;
SynchronousTraceLogger& operator=(SynchronousTraceLogger&&) = delete;
~SynchronousTraceLogger() override;
};
class AsynchronousTraceLogger : public TraceLoggerBase {
public:
using TraceLoggerBase::TraceLoggerBase;
AsynchronousTraceLogger(const AsynchronousTraceLogger&) = delete;
AsynchronousTraceLogger(AsynchronousTraceLogger&&) noexcept = delete;
AsynchronousTraceLogger& operator=(const AsynchronousTraceLogger&) = delete;
AsynchronousTraceLogger& operator=(AsynchronousTraceLogger&&) = delete;
~AsynchronousTraceLogger() override;
};
// Inserts a fake element into the ScopedTraceOperation stack to set
// the current TraceId Hierarchy manually.
class TraceIdSetter final : public ScopedTraceOperation {
public:
explicit TraceIdSetter(TraceIdHierarchy ids);
TraceIdSetter(const TraceIdSetter&) = delete;
TraceIdSetter(TraceIdSetter&&) noexcept = delete;
TraceIdSetter& operator=(const TraceIdSetter&) = delete;
TraceIdSetter& operator=(TraceIdSetter&&) = delete;
~TraceIdSetter() final;
// Creates a new TraceIdSetter to set the full TraceId Hierarchy to default
// values and does not push it to the traces stack.
static TraceIdSetter* CreateStackRootNode();
private:
// Implement abstract method for use in Macros.
void SetTraceResult(Error::Code error) {}
};
// This helper object allows us to delete objects allocated on the stack in a
// unique_ptr.
template <class T>
class TraceInstanceHelper {
private:
class TraceOperationOnStackDeleter {
public:
void operator()(T* ptr) { ptr->~T(); }
};
using TraceInstanceWrapper = std::unique_ptr<T, TraceOperationOnStackDeleter>;
public:
template <typename... Args>
static TraceInstanceWrapper Create(uint8_t storage[sizeof(T)], Args... args) {
return TraceInstanceWrapper(new (storage) T(std::forward<Args&&>(args)...));
}
static TraceInstanceWrapper Empty() { return TraceInstanceWrapper(); }
};
} // namespace openscreen::internal
#endif // defined(ENABLE_TRACE_LOGGING)
#endif // UTIL_TRACE_LOGGING_SCOPED_TRACE_OPERATIONS_H_