791 lines
43 KiB
C++
791 lines
43 KiB
C++
#include "can_data.hpp"
|
|
#include <algorithm>
|
|
#include <chrono>
|
|
#include <cmath>
|
|
#include <cstdint>
|
|
#include <cstring>
|
|
#include <ftxui/component/component.hpp>
|
|
#include <ftxui/component/component_base.hpp>
|
|
#include <ftxui/component/component_options.hpp>
|
|
#include <ftxui/component/mouse.hpp>
|
|
#include <ftxui/component/screen_interactive.hpp>
|
|
#include <ftxui/dom/direction.hpp>
|
|
#include <ftxui/dom/elements.hpp>
|
|
#include <ftxui/screen/color.hpp>
|
|
#include <future>
|
|
#include <memory>
|
|
#include <set>
|
|
|
|
#include <linux/if.h>
|
|
#include <linux/sockios.h>
|
|
#include <mutex>
|
|
#include <sstream>
|
|
#include <stop_token>
|
|
#include <sys/ioctl.h>
|
|
#include <sys/socket.h>
|
|
#include <thread>
|
|
#include <unistd.h>
|
|
|
|
#include <linux/can.h>
|
|
#include <linux/can/raw.h>
|
|
|
|
#include <boost/regex.hpp>
|
|
|
|
#define FMT_HEADER_ONLY
|
|
#include <fmt/format.h>
|
|
|
|
#include "signals.hpp"
|
|
#include "sqlite_modern_cpp.h"
|
|
#include "utils.hpp"
|
|
#include "json/expander.hpp"
|
|
#include "json/json.hpp"
|
|
|
|
ftxui::Component makeCanPlayerDialog(ftxui::ScreenInteractive *scr, signals_map_t &smap, bool &is_ready) {
|
|
|
|
class Impl : public ftxui::ComponentBase {
|
|
public:
|
|
Impl(ftxui::ScreenInteractive *scr, signals_map_t &smap, bool &is_ready) {
|
|
static sqlite::database *database = nullptr;
|
|
static float canbus_player_focus_relative = 0;
|
|
static std::string player_filter_text;
|
|
|
|
auto pgnContainer = ftxui::Container::Vertical({});
|
|
|
|
struct pgn_parameters_s {
|
|
bool selected, is_running, pinned, forward = false;
|
|
uint32_t pgn, priority, datalen;
|
|
std::string label, acronym, descr, address, ifname, period_ms;
|
|
std::vector<uint8_t> payload;
|
|
std::string forward_canid;
|
|
|
|
struct {
|
|
std::unique_ptr<std::mutex> mtx;
|
|
std::future<void> fut;
|
|
std::unique_ptr<std::stop_source> ss;
|
|
} concurrent;
|
|
};
|
|
|
|
struct spn_parameters_s {
|
|
struct fragment_s {
|
|
int32_t byte_offset, bit_offset, size;
|
|
};
|
|
|
|
bool checked, little_endian;
|
|
float resolution, offset, min, max, current;
|
|
std::string unit;
|
|
int32_t slider_percent;
|
|
size_t raw;
|
|
std::vector<fragment_s> fragments;
|
|
struct pgn_parameters_s *pg_ref = nullptr;
|
|
};
|
|
|
|
static std::map<int32_t, pgn_parameters_s> pgs;
|
|
static std::set<uint32_t> received_pgns;
|
|
static const auto send_frame = [](const pgn_parameters_s &pg) {
|
|
struct can_frame frame = {};
|
|
|
|
const int can_socket = ::socket(AF_CAN, SOCK_RAW, CAN_RAW);
|
|
if (can_socket < 0) {
|
|
return;
|
|
}
|
|
|
|
ifreq ifr{};
|
|
|
|
std::strcpy(ifr.ifr_name, pg.ifname.c_str());
|
|
if (::ioctl(can_socket, SIOCGIFINDEX, &ifr) < 0) {
|
|
::close(can_socket);
|
|
return;
|
|
}
|
|
|
|
sockaddr_can addr = {};
|
|
addr.can_family = AF_CAN;
|
|
addr.can_ifindex = ifr.ifr_ifindex;
|
|
::setsockopt(can_socket, SOL_CAN_RAW, CAN_RAW_FILTER, nullptr, 0);
|
|
|
|
if (::bind(can_socket, reinterpret_cast<sockaddr *>(&addr), sizeof(addr)) < 0) {
|
|
::close(can_socket);
|
|
return;
|
|
}
|
|
|
|
uint32_t address;
|
|
std::stringstream{} << std::hex << pg.address >> address;
|
|
|
|
frame.can_id = ((pg.priority & 0x7u) << 26u) | ((pg.pgn & 0x3FFFFu) << 8u) | (address & 0xFFu);
|
|
frame.can_dlc = pg.datalen;
|
|
frame.can_id |= CAN_EFF_FLAG;
|
|
std::memcpy(frame.data, pg.payload.data(), pg.payload.size());
|
|
::write(can_socket, &frame, sizeof(frame));
|
|
::close(can_socket);
|
|
};
|
|
|
|
static const auto calculate_spn = [](spn_parameters_s &spn_params) {
|
|
// Calculate value from slider percentage
|
|
spn_params.current =
|
|
((spn_params.slider_percent / 100.0f) * (spn_params.max - spn_params.min)) + spn_params.offset;
|
|
|
|
// Round and clamp by min/max
|
|
spn_params.current = std::clamp(std::round(spn_params.current), spn_params.min, spn_params.max);
|
|
spn_params.raw = (spn_params.current - spn_params.offset) / spn_params.resolution;
|
|
auto raw = spn_params.raw;
|
|
|
|
// Swap bytes if needed
|
|
if (spn_params.little_endian) {
|
|
// Get size
|
|
size_t size = 0;
|
|
for (const auto &frag : spn_params.fragments) {
|
|
size += frag.size;
|
|
}
|
|
|
|
size /= UINT8_WIDTH;
|
|
if (size > 1) {
|
|
auto swapped = std::shared_ptr<uint8_t>(new uint8_t[size], [](auto *p) { delete[] p; });
|
|
for (size_t i = 0; i < size; ++i) {
|
|
swapped.get()[i] = reinterpret_cast<uint8_t *>(&raw)[size - i - 1];
|
|
}
|
|
|
|
raw = *reinterpret_cast<decltype(raw) *>(swapped.get());
|
|
}
|
|
}
|
|
|
|
// Magic here
|
|
{
|
|
std::lock_guard<std::mutex> lock(*spn_params.pg_ref->concurrent.mtx);
|
|
for (const auto &fragment : spn_params.fragments) {
|
|
for (int32_t i = 0; i < fragment.size / UINT8_WIDTH + (fragment.size % UINT8_WIDTH ? 1 : 0); i++) {
|
|
|
|
uint8_t &byte = spn_params.pg_ref->payload[i + fragment.byte_offset];
|
|
|
|
// Reset bits in this byte depending on fragment size and fragment bit offset
|
|
byte &= fragment.size % UINT8_WIDTH
|
|
? (static_cast<uint8_t>(0xffu << (fragment.size % UINT8_WIDTH + fragment.bit_offset)) |
|
|
static_cast<uint8_t>(~(0xffu << fragment.bit_offset)))
|
|
: 0x00u;
|
|
byte |= static_cast<uint8_t>((raw >> (i * UINT8_WIDTH)) << fragment.bit_offset);
|
|
}
|
|
|
|
raw >>= fragment.size;
|
|
}
|
|
}
|
|
};
|
|
|
|
static const auto stop_pg = [](pgn_parameters_s &pg) {
|
|
if (pg.is_running) {
|
|
pg.concurrent.ss->request_stop();
|
|
pg.concurrent.fut.wait();
|
|
pg.concurrent.ss = std::make_unique<std::stop_source>();
|
|
pg.is_running = false;
|
|
}
|
|
};
|
|
|
|
// Static connections
|
|
{
|
|
static struct on_stopped_connection_s {
|
|
on_stopped_connection_s(signals_map_t &smap) {
|
|
smap.get<void()>("canplayer_stopped")->connect([]() {
|
|
for (auto &[_, pg] : pgs) {
|
|
stop_pg(pg);
|
|
}
|
|
});
|
|
}
|
|
} on_stopped_connection(smap);
|
|
|
|
static struct forward_connection_s {
|
|
forward_connection_s(signals_map_t &smap) {
|
|
smap.get<void(const std::vector<can_frame_update_s> &)>("new_entries_batch")
|
|
->connect([](const std::vector<can_frame_update_s> &batch) {
|
|
for (const auto &entry : batch) {
|
|
uint32_t pgn_num = 0;
|
|
|
|
if (entry.canid.size() >= 6) {
|
|
auto pgn_str = entry.canid.substr(entry.canid.size() >= 8 ? entry.canid.size() - 6 : 2, 4);
|
|
std::stringstream ss;
|
|
ss << std::hex << pgn_str;
|
|
ss >> pgn_num;
|
|
}
|
|
|
|
received_pgns.insert(pgn_num);
|
|
if (pgs.contains(pgn_num) && pgs[pgn_num].forward && pgs[pgn_num].is_running) {
|
|
auto &pg = pgs[pgn_num];
|
|
std::lock_guard<std::mutex> lock(*pg.concurrent.mtx);
|
|
pg.payload = entry.data.payload;
|
|
pg.payload.resize(pg.datalen, 0);
|
|
pg.forward_canid = entry.canid;
|
|
send_frame(pg);
|
|
}
|
|
}
|
|
});
|
|
}
|
|
} forward_connection(smap);
|
|
|
|
static struct database_ready_connection_s {
|
|
database_ready_connection_s(signals_map_t &smap, ftxui::ScreenInteractive *scr, ftxui::Component pgnContainer,
|
|
bool &is_ready) {
|
|
smap.get<void(sqlite::database &)>("j1939_database_ready")
|
|
->connect([scr, &is_ready, pgnContainer](sqlite::database &db) {
|
|
scr->Post([scr, &is_ready, pgnContainer, &db]() {
|
|
std::lock_guard<std::mutex> db_lock(g_j1939_db_mtx);
|
|
db << "SELECT pgn, pg_label, pg_acronym, pg_descr, pg_datalen, pg_priority FROM pgns" >>
|
|
[&, pgnContainer](uint32_t pgn, const std::string &label, const std::string &acronym,
|
|
const std::string &descr, uint32_t datalen, uint32_t priority) {
|
|
if (!pgs.contains(pgn)) {
|
|
pgs.insert(std::pair{
|
|
pgn,
|
|
pgn_parameters_s{
|
|
.selected = false,
|
|
.pinned = false,
|
|
.pgn = pgn,
|
|
.priority = priority,
|
|
.datalen = datalen,
|
|
.label = label,
|
|
.acronym = acronym,
|
|
.descr = descr,
|
|
.address = "0xFF",
|
|
.ifname = "vcan0",
|
|
.period_ms = "1000",
|
|
.payload = std::vector<uint8_t>(datalen),
|
|
.concurrent =
|
|
{
|
|
.mtx = std::make_unique<std::mutex>(),
|
|
.ss = std::make_unique<std::stop_source>(),
|
|
},
|
|
},
|
|
});
|
|
}
|
|
|
|
auto &pg_ref = pgs[pgn];
|
|
auto spnContainer = ftxui::Container::Vertical({});
|
|
db << fmt::format("SELECT id, pgn, spn, spn_name FROM spns WHERE pgn = {};", pgn) >>
|
|
[spnContainer, &db](int32_t id, int32_t pgn, int32_t spn, const std::string &spn_name) {
|
|
static std::map<int32_t, spn_parameters_s> spns;
|
|
db << fmt::format(
|
|
"SELECT min_value, max_value, resolution, offset, units FROM spns WHERE spn = {};",
|
|
spn) >>
|
|
[&](float min, float max, float resolution, float offset, const std::string &unit) {
|
|
db << fmt::format("SELECT COUNT(*) FROM spn_fragments WHERE spn = {}", spn) >>
|
|
[&](int32_t count) {
|
|
std::vector<spn_parameters_s::fragment_s> fragments;
|
|
fragments.resize(count);
|
|
auto *fragment_ptr = fragments.data();
|
|
|
|
// Fill fragments array
|
|
db << fmt::format("SELECT byte_offset, bit_offset, size FROM spn_fragments "
|
|
"WHERE spn = {}",
|
|
spn) >>
|
|
[&fragment_ptr](int32_t byte_offset, int32_t bit_offset, int32_t size) {
|
|
*(fragment_ptr++) = {
|
|
.byte_offset = byte_offset,
|
|
.bit_offset = bit_offset,
|
|
.size = size,
|
|
};
|
|
};
|
|
|
|
spns.insert_or_assign(spn, spn_parameters_s{
|
|
.checked = false,
|
|
.little_endian = false,
|
|
.resolution = resolution,
|
|
.offset = offset,
|
|
.min = min,
|
|
.max = max,
|
|
.current = 0.0f,
|
|
.unit = unit,
|
|
.slider_percent = 0,
|
|
.fragments = fragments,
|
|
.pg_ref = &pgs[pgn],
|
|
});
|
|
};
|
|
};
|
|
|
|
auto &spn_params = spns[spn];
|
|
spnContainer->Add({
|
|
ftxui::Container::Horizontal({
|
|
ftxui::Container::Vertical({
|
|
ftxui::Checkbox({
|
|
.checked = &spns[spn].checked,
|
|
.transform =
|
|
[spn_name](const ftxui::EntryState &state) -> ftxui::Element {
|
|
return ftxui::hbox({
|
|
ftxui::separatorEmpty(),
|
|
ftxui::text(state.state ? "▼ " : "▶ "),
|
|
ftxui::text(spn_name),
|
|
}) |
|
|
(state.focused
|
|
? (ftxui::bold | ftxui::bgcolor(ftxui::Color::Grey11))
|
|
: ftxui::nothing) |
|
|
ftxui::flex;
|
|
},
|
|
}),
|
|
|
|
ftxui::Maybe(
|
|
ftxui::Container::Horizontal({
|
|
ftxui::Renderer([]() {
|
|
ftxui::Elements separators(4u, ftxui::separatorEmpty());
|
|
return ftxui::hbox(separators);
|
|
}),
|
|
|
|
ftxui::Container::Vertical({
|
|
ftxui::Slider(ftxui::SliderOption<int32_t>{
|
|
.value = &spn_params.slider_percent,
|
|
.min = 0,
|
|
.max = 100,
|
|
.increment = 1,
|
|
.on_change =
|
|
[&spn_params =
|
|
spns[spn]]() { calculate_spn(spn_params); },
|
|
}) | ftxui::Renderer([](ftxui::Element inner) {
|
|
return ftxui::hbox({
|
|
ftxui::text("Value: ") | ftxui::bold |
|
|
ftxui::color(ftxui::Color::Yellow),
|
|
ftxui::text("["),
|
|
inner,
|
|
ftxui::text("]"),
|
|
}) |
|
|
ftxui::size(ftxui::WIDTH, ftxui::EQUAL, 100u);
|
|
}),
|
|
|
|
ftxui::Container::Horizontal({
|
|
ftxui::Renderer([]() {
|
|
return ftxui::text("Endianness: ") | ftxui::bold |
|
|
ftxui::color(ftxui::Color::Yellow);
|
|
}),
|
|
|
|
ftxui::Checkbox({
|
|
.checked = &spn_params.little_endian,
|
|
.transform =
|
|
[&spn_params](const ftxui::EntryState &state) {
|
|
auto el = ftxui::hbox({
|
|
ftxui::text("<"),
|
|
ftxui::text("little") |
|
|
(spn_params.little_endian
|
|
? (ftxui::bold |
|
|
ftxui::color(ftxui::Color::Red))
|
|
: ftxui::nothing),
|
|
ftxui::text(" | "),
|
|
ftxui::text("big") |
|
|
(!spn_params.little_endian
|
|
? (ftxui::bold |
|
|
ftxui::color(ftxui::Color::Red))
|
|
: ftxui::nothing),
|
|
ftxui::text(">"),
|
|
});
|
|
|
|
if (state.focused || state.active) {
|
|
el = el | ftxui::bold |
|
|
ftxui::bgcolor(ftxui::Color::Grey11);
|
|
}
|
|
|
|
return el;
|
|
},
|
|
.on_change =
|
|
[&spn_params = spns[spn]]() {
|
|
calculate_spn(spn_params);
|
|
},
|
|
}),
|
|
}),
|
|
|
|
ftxui::Renderer([]() {
|
|
return ftxui::vbox({
|
|
ftxui::separatorEmpty(),
|
|
ftxui::text("SPN info:") |
|
|
ftxui::color(ftxui::Color::Cyan) | ftxui::bold,
|
|
});
|
|
}),
|
|
|
|
From(
|
|
[spn]() -> nlohmann::json {
|
|
auto &spn_params = spns[spn];
|
|
auto fragments = nlohmann::json::array({});
|
|
|
|
for (const auto &frag : spn_params.fragments) {
|
|
fragments.push_back(
|
|
nlohmann::json{{"byte_offset", frag.byte_offset},
|
|
{"bit_offset", frag.bit_offset},
|
|
{"size", frag.size}});
|
|
}
|
|
|
|
return {
|
|
{"fragments", fragments},
|
|
{"min", spn_params.min},
|
|
{"max", spn_params.max},
|
|
{"resolution", spn_params.resolution},
|
|
{"offset", spn_params.offset},
|
|
};
|
|
}(),
|
|
|
|
false, -100, ExpanderImpl::Root()) |
|
|
ftxui::Renderer([](ftxui::Element inner) {
|
|
return ftxui::hbox({
|
|
ftxui::separatorEmpty(),
|
|
ftxui::separatorEmpty(),
|
|
inner,
|
|
});
|
|
}),
|
|
|
|
ftxui::Renderer([]() { return ftxui::separatorEmpty(); }),
|
|
ftxui::Renderer([&spn_params = spns[spn]]() {
|
|
return ftxui::vbox({
|
|
ftxui::hbox({
|
|
ftxui::text("Value: ") | ftxui::bold |
|
|
ftxui::color(ftxui::Color::Cyan),
|
|
ftxui::text(fmt::format("{} {}", spn_params.current,
|
|
spn_params.unit)),
|
|
}),
|
|
|
|
ftxui::hbox({
|
|
ftxui::text("Raw: ") | ftxui::bold |
|
|
ftxui::color(ftxui::Color::Cyan),
|
|
ftxui::text(fmt::format(
|
|
"{} (hex:{}) (bin:{})", spn_params.raw,
|
|
fmt::format("{0:#x}", spn_params.raw),
|
|
fmt::format("{0:#b}", spn_params.raw))),
|
|
}),
|
|
|
|
[&]() -> ftxui::Element {
|
|
// Build bit mask for this SPN's fragments
|
|
const auto &payload = spn_params.pg_ref->payload;
|
|
std::vector<bool> highlight(payload.size() * 8, false);
|
|
for (const auto &frag : spn_params.fragments) {
|
|
int32_t start_bit =
|
|
frag.byte_offset * 8 + frag.bit_offset;
|
|
|
|
for (int32_t b = 0; b < frag.size; ++b) {
|
|
auto idx = static_cast<size_t>(start_bit + b);
|
|
|
|
if (idx < highlight.size()) {
|
|
highlight[idx] = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
ftxui::Elements parts;
|
|
parts.push_back(ftxui::text("PG payload: ") |
|
|
ftxui::bold |
|
|
ftxui::color(ftxui::Color::Cyan));
|
|
parts.push_back(ftxui::text("["));
|
|
|
|
for (size_t i = 0; i < payload.size(); ++i) {
|
|
parts.push_back(ftxui::text("0b"));
|
|
|
|
for (int32_t bit = 7; bit >= 0; --bit) {
|
|
bool is_set = (payload[i] >> bit) & 1;
|
|
bool is_spn = highlight[i * 8 + bit];
|
|
auto ch = ftxui::text(is_set ? "1" : "0");
|
|
|
|
if (is_spn) {
|
|
ch = ch | ftxui::color(ftxui::Color::Red) |
|
|
ftxui::bold;
|
|
}
|
|
|
|
parts.push_back(ch);
|
|
}
|
|
|
|
parts.push_back(ftxui::text(" "));
|
|
}
|
|
|
|
parts.push_back(ftxui::text("]"));
|
|
|
|
return ftxui::hbox(std::move(parts));
|
|
}(),
|
|
|
|
ftxui::separatorEmpty(),
|
|
});
|
|
}),
|
|
}),
|
|
}),
|
|
|
|
&spns[spn].checked),
|
|
}),
|
|
}),
|
|
});
|
|
};
|
|
|
|
auto pgn_entry = ftxui::Container::Vertical({
|
|
ftxui::Container::Horizontal({
|
|
ftxui::Checkbox({
|
|
.checked = &pg_ref.selected,
|
|
.transform = [&pg_ref =
|
|
pgs[pgn]](const ftxui::EntryState &state) -> ftxui::Element {
|
|
return ftxui::hbox({
|
|
ftxui::text(state.state ? "▼ " : "▶ "),
|
|
ftxui::text(fmt::format("0x{:x} - {}", pg_ref.pgn, pg_ref.label)) |
|
|
(pg_ref.is_running ? ftxui::color(ftxui::Color::Green)
|
|
: ftxui::nothing),
|
|
ftxui::filler(),
|
|
}) |
|
|
(state.focused ? (ftxui::bold | ftxui::bgcolor(ftxui::Color::Grey11))
|
|
: ftxui::nothing) |
|
|
ftxui::flex;
|
|
},
|
|
}),
|
|
}),
|
|
|
|
ftxui::Maybe(
|
|
ftxui::Container::Vertical({
|
|
ftxui::Container::Horizontal({
|
|
ftxui::Checkbox({
|
|
.transform = [](const ftxui::EntryState &state) -> ftxui::Element {
|
|
return ftxui::hbox({
|
|
ftxui::text(" >[send_frame]< ") |
|
|
(state.focused ? ftxui::bold : ftxui::nothing) |
|
|
ftxui::color(ftxui::Color::Cyan) |
|
|
(state.focused ? ftxui::bgcolor(ftxui::Color::Grey11)
|
|
: ftxui::nothing),
|
|
});
|
|
},
|
|
|
|
.on_change =
|
|
[&pg = pgs[pgn]]() {
|
|
std::lock_guard<std::mutex> lock(*pg.concurrent.mtx);
|
|
send_frame(pg);
|
|
},
|
|
}),
|
|
|
|
ftxui::Checkbox({
|
|
.transform = [](const ftxui::EntryState &state) -> ftxui::Element {
|
|
return ftxui::text(" >[run]< ") |
|
|
(state.focused ? ftxui::bold : ftxui::nothing) |
|
|
ftxui::color(ftxui::Color::Cyan) |
|
|
(state.focused ? ftxui::bgcolor(ftxui::Color::Grey11)
|
|
: ftxui::nothing);
|
|
},
|
|
|
|
.on_change =
|
|
[&pg = pgs[pgn]]() {
|
|
if (!pg.is_running) {
|
|
pg.concurrent.fut = std::async(
|
|
std::launch::async,
|
|
[&pg](std::stop_token st) {
|
|
int32_t period_ms;
|
|
std::stringstream{} << pg.period_ms >> period_ms;
|
|
period_ms = std::clamp(period_ms, 50, INT32_MAX);
|
|
|
|
while (!st.stop_requested()) {
|
|
{
|
|
std::lock_guard<std::mutex> lock(*pg.concurrent.mtx);
|
|
send_frame(pg);
|
|
}
|
|
|
|
std::this_thread::sleep_for(
|
|
std::chrono::milliseconds(period_ms));
|
|
}
|
|
},
|
|
|
|
pg.concurrent.ss->get_token());
|
|
pg.is_running = true;
|
|
}
|
|
},
|
|
}),
|
|
|
|
ftxui::Checkbox({
|
|
.transform = [](const ftxui::EntryState &state) -> ftxui::Element {
|
|
return ftxui::text(" >[stop]< ") |
|
|
(state.focused ? ftxui::bold : ftxui::nothing) |
|
|
ftxui::color(ftxui::Color::Cyan) |
|
|
(state.focused ? ftxui::bgcolor(ftxui::Color::Grey11)
|
|
: ftxui::nothing);
|
|
},
|
|
|
|
.on_change = [&pg = pgs[pgn]]() { stop_pg(pg); },
|
|
}),
|
|
}),
|
|
|
|
ftxui::Renderer([]() { return ftxui::separator(); }),
|
|
ftxui::Maybe(
|
|
ftxui::Checkbox({
|
|
.checked = &pg_ref.forward,
|
|
.transform = [&pg_ref](const ftxui::EntryState &state) -> ftxui::Element {
|
|
auto el =
|
|
ftxui::text(pg_ref.forward ? " [X] forward " : " [ ] forward ") |
|
|
ftxui::color(pg_ref.forward ? ftxui::Color::Green
|
|
: ftxui::Color::Cyan);
|
|
if (state.focused || state.active)
|
|
el = el | ftxui::bold | ftxui::bgcolor(ftxui::Color::Grey11);
|
|
return el;
|
|
},
|
|
}),
|
|
|
|
[pgn]() { return received_pgns.contains(pgn); }),
|
|
ftxui::Renderer([]() { return ftxui::separatorEmpty(); }),
|
|
|
|
ftxui::Input({
|
|
.content = &pg_ref.address,
|
|
.placeholder = "0xFF",
|
|
.multiline = false,
|
|
}) | ftxui::Renderer([](ftxui::Element inner) {
|
|
return ftxui::hbox({
|
|
ftxui::separatorEmpty(),
|
|
ftxui::text("Address (hex): ") | ftxui::color(ftxui::Color::Magenta) |
|
|
ftxui::bold,
|
|
ftxui::hbox({
|
|
inner,
|
|
ftxui::filler(),
|
|
}),
|
|
});
|
|
}),
|
|
|
|
ftxui::Input({
|
|
.content = &pg_ref.ifname,
|
|
.placeholder = "vcan0",
|
|
.multiline = false,
|
|
}) | ftxui::Renderer([](ftxui::Element inner) {
|
|
return ftxui::hbox({
|
|
ftxui::separatorEmpty(),
|
|
ftxui::text("CAN interface name: ") | ftxui::color(ftxui::Color::Magenta) |
|
|
ftxui::bold,
|
|
ftxui::hbox({
|
|
inner,
|
|
ftxui::filler(),
|
|
}),
|
|
});
|
|
}),
|
|
|
|
ftxui::Input({
|
|
.content = &pg_ref.period_ms,
|
|
.placeholder = "1000",
|
|
.multiline = false,
|
|
}) | ftxui::Renderer([](ftxui::Element inner) {
|
|
return ftxui::hbox({
|
|
ftxui::separatorEmpty(),
|
|
ftxui::text("Send period (ms): ") | ftxui::color(ftxui::Color::Magenta) |
|
|
ftxui::bold,
|
|
ftxui::hbox({
|
|
inner,
|
|
ftxui::filler(),
|
|
}),
|
|
});
|
|
}),
|
|
|
|
ftxui::Renderer([]() { return ftxui::separatorEmpty(); }),
|
|
spnContainer,
|
|
}) | ftxui::border,
|
|
&pg_ref.selected),
|
|
});
|
|
|
|
pgnContainer->Add(ftxui::Maybe(pgn_entry, [&pg_ref = pgs[pgn]]() -> bool {
|
|
if (player_filter_text.empty())
|
|
return true;
|
|
try {
|
|
boost::regex re(player_filter_text, boost::regex_constants::icase);
|
|
std::string subject = fmt::format("0x{:x} {}", pg_ref.pgn, pg_ref.label);
|
|
return boost::regex_search(subject, re);
|
|
} catch (...) {
|
|
return true;
|
|
}
|
|
}));
|
|
};
|
|
|
|
database = &db;
|
|
is_ready = true;
|
|
scr->Post(ftxui::Event::Custom);
|
|
});
|
|
});
|
|
}
|
|
} database_ready_connection(smap, scr, pgnContainer, is_ready);
|
|
}
|
|
|
|
auto main = ftxui::Container::Vertical({
|
|
ftxui::Input({
|
|
.content = &player_filter_text,
|
|
.placeholder = "regex filter ...",
|
|
.transform = [](ftxui::InputState state) -> ftxui::Element {
|
|
bool valid = true;
|
|
if (!player_filter_text.empty()) {
|
|
try {
|
|
boost::regex(player_filter_text, boost::regex_constants::icase);
|
|
} catch (...) {
|
|
valid = false;
|
|
}
|
|
}
|
|
|
|
state.element |= (!valid ? ftxui::color(ftxui::Color::Red) : ftxui::nothing) |
|
|
(state.focused ? ftxui::color(ftxui::Color::Cyan) : ftxui::nothing) |
|
|
(state.hovered ? ftxui::bold : ftxui::nothing);
|
|
|
|
return ftxui::hbox({
|
|
ftxui::text(" Search: [ "),
|
|
state.element |
|
|
(state.hovered || state.focused ? ftxui::bgcolor(ftxui::Color::Grey11) : ftxui::nothing) |
|
|
ftxui::xflex,
|
|
ftxui::text(" ]"),
|
|
});
|
|
},
|
|
.multiline = false,
|
|
}),
|
|
|
|
ftxui::Renderer([]() { return ftxui::separator(); }),
|
|
|
|
(pgnContainer | ftxui::Renderer([](ftxui::Element inner) {
|
|
return inner | ftxui::focusPositionRelative(0, canbus_player_focus_relative) | ftxui::vscroll_indicator |
|
|
ftxui::frame | ftxui::flex;
|
|
})),
|
|
});
|
|
|
|
Add({main | ftxui::CatchEvent([pgnContainer](ftxui::Event event) {
|
|
if (!database)
|
|
return true;
|
|
|
|
const auto scroll_step = []() -> float {
|
|
size_t visible_lines = 0;
|
|
boost::regex re;
|
|
bool has_filter = !player_filter_text.empty();
|
|
|
|
if (has_filter) {
|
|
try {
|
|
re = boost::regex(player_filter_text, boost::regex_constants::icase);
|
|
} catch (...) {
|
|
has_filter = false;
|
|
}
|
|
}
|
|
|
|
for (const auto &[_, pg] : pgs) {
|
|
if (has_filter) {
|
|
std::string subject = fmt::format("0x{:x} {}", pg.pgn, pg.label);
|
|
if (!boost::regex_search(subject, re)) {
|
|
continue;
|
|
}
|
|
}
|
|
|
|
++visible_lines;
|
|
|
|
if (pg.selected) {
|
|
visible_lines += 15;
|
|
}
|
|
}
|
|
|
|
return visible_lines > 0 ? 1.0f / static_cast<float>(visible_lines) : 0.03f;
|
|
};
|
|
|
|
if (event.is_mouse()) {
|
|
switch (static_cast<enum ftxui::Mouse::Button>(event.mouse().button)) {
|
|
case ftxui::Mouse::Button::WheelDown: {
|
|
canbus_player_focus_relative = std::clamp(canbus_player_focus_relative + scroll_step(), 0.0f, 1.0f);
|
|
return true;
|
|
}
|
|
|
|
case ftxui::Mouse::Button::WheelUp: {
|
|
canbus_player_focus_relative = std::clamp(canbus_player_focus_relative - scroll_step(), 0.0f, 1.0f);
|
|
return true;
|
|
}
|
|
|
|
default:
|
|
break;
|
|
}
|
|
} else if (!event.is_character()) {
|
|
if (event == ftxui::Event::ArrowDown) {
|
|
|
|
canbus_player_focus_relative = std::clamp(canbus_player_focus_relative + scroll_step(), 0.0f, 1.0f);
|
|
return true;
|
|
} else if (event == ftxui::Event::ArrowUp) {
|
|
|
|
canbus_player_focus_relative = std::clamp(canbus_player_focus_relative - scroll_step(), 0.0f, 1.0f);
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
})});
|
|
}
|
|
};
|
|
|
|
return ftxui::Make<Impl>(scr, smap, is_ready);
|
|
}
|