Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5b42e1f0e5 | ||
|
|
e8af9b46b1 | ||
|
|
8abfc6a428 | ||
|
|
936136db06 | ||
|
|
6d7c4f7bc4 | ||
|
|
9487202ea8 |
@@ -20,6 +20,7 @@ add_library(veloxcore STATIC
|
||||
src/net/probe.cpp
|
||||
src/io/sparse_file.cpp
|
||||
src/io/write_buffer.cpp
|
||||
src/io/platform/linux/file_ops.cpp
|
||||
src/meta/veloxpart.cpp
|
||||
src/segment/segmenter.cpp
|
||||
src/segment/budget.cpp
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
// vdm/io/platform/file_ops.hpp — platform seam for the three Linux-only file-I/O
|
||||
// primitives sparse_file.cpp and meta/veloxpart.cpp use (ADR 0020, docs/08-porting.md).
|
||||
//
|
||||
// One implementation file per OS under platform/<os>/, selected at build time. No #ifdef
|
||||
// here, no OS-specific types in the signatures — a reader of the callers must not need to
|
||||
// know which OS they're on. Linux (platform/linux/file_ops.cpp) is the reference
|
||||
// implementation and is the only one that exists until the PORT lane adds macOS.
|
||||
//
|
||||
// This header compiles standalone.
|
||||
|
||||
#ifndef VDM_IO_PLATFORM_FILE_OPS_HPP
|
||||
#define VDM_IO_PLATFORM_FILE_OPS_HPP
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
#include "vdm/util/result.hpp"
|
||||
|
||||
namespace vdm::io::platform {
|
||||
|
||||
// Reserve `bytes` for `fd` so a large transfer doesn't fragment or hit ENOSPC mid-write.
|
||||
// Linux: posix_fallocate, falling back to ftruncate on EOPNOTSUPP/ENOSYS/EINVAL exactly as
|
||||
// SparseFile did before this seam existed. Returns whether a true extent-reserving
|
||||
// preallocation happened (false when the ftruncate fallback was used) so callers can keep
|
||||
// reporting SparseFile::preallocated() unchanged.
|
||||
[[nodiscard]] Result<bool> preallocate(int fd, std::uint64_t bytes);
|
||||
|
||||
// Drop cached pages for [offset, offset+len) from the OS page cache, best-effort. A
|
||||
// platform with no equivalent (macOS) is a documented no-op, not a failure — never let
|
||||
// this block or fail the transfer path.
|
||||
void advise_dontneed(int fd, std::uint64_t offset, std::uint64_t len) noexcept;
|
||||
|
||||
// Flush `fd`'s data to durable storage without waiting on metadata that doesn't affect
|
||||
// data readback. Linux: fdatasync.
|
||||
[[nodiscard]] Result<void> flush_durable(int fd);
|
||||
|
||||
} // namespace vdm::io::platform
|
||||
|
||||
#endif // VDM_IO_PLATFORM_FILE_OPS_HPP
|
||||
@@ -0,0 +1,73 @@
|
||||
// vdm/io/platform/linux/file_ops.cpp — Linux backend for the file_ops seam.
|
||||
//
|
||||
// Reference implementation (ADR 0020): moved unchanged from sparse_file.cpp and
|
||||
// meta/veloxpart.cpp. Linux behaviour must never change as a result of a port; if a port
|
||||
// needs a semantic change here, that's an ADR, not a seam edit.
|
||||
|
||||
#include "io/platform/file_ops.hpp"
|
||||
|
||||
#include <fcntl.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <cerrno>
|
||||
#include <cstring>
|
||||
#include <string>
|
||||
|
||||
namespace vdm::io::platform {
|
||||
namespace {
|
||||
|
||||
Error errno_to_error(int e) noexcept {
|
||||
switch (e) {
|
||||
case ENOSPC:
|
||||
case EDQUOT:
|
||||
return Error::disk_full;
|
||||
case EACCES:
|
||||
case EPERM:
|
||||
case EROFS:
|
||||
return Error::permission_denied;
|
||||
case ENOENT:
|
||||
case ENOTDIR:
|
||||
case EISDIR:
|
||||
case ENAMETOOLONG:
|
||||
case ELOOP:
|
||||
return Error::path_rejected;
|
||||
default:
|
||||
return Error::io_error;
|
||||
}
|
||||
}
|
||||
|
||||
ErrorInfo sys_error(std::string_view what, int e) {
|
||||
return ErrorInfo(errno_to_error(e), std::string(what) + ": " + std::strerror(e));
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
Result<bool> preallocate(int fd, std::uint64_t bytes) {
|
||||
// posix_fallocate returns the error number directly and does not set errno.
|
||||
int rc = ::posix_fallocate(fd, 0, static_cast<off_t>(bytes));
|
||||
if (rc == 0)
|
||||
return true;
|
||||
if (rc == EOPNOTSUPP || rc == ENOSYS || rc == EINVAL) {
|
||||
if (::ftruncate(fd, static_cast<off_t>(bytes)) != 0)
|
||||
return sys_error("ftruncate", errno);
|
||||
return false;
|
||||
}
|
||||
return sys_error("posix_fallocate", rc);
|
||||
}
|
||||
|
||||
void advise_dontneed(int fd, std::uint64_t offset, std::uint64_t len) noexcept {
|
||||
if (len == 0)
|
||||
return;
|
||||
::posix_fadvise(fd, static_cast<off_t>(offset), static_cast<off_t>(len), POSIX_FADV_DONTNEED);
|
||||
}
|
||||
|
||||
Result<void> flush_durable(int fd) {
|
||||
while (::fdatasync(fd) != 0) {
|
||||
if (errno == EINTR)
|
||||
continue;
|
||||
return sys_error("fdatasync", errno);
|
||||
}
|
||||
return ok();
|
||||
}
|
||||
|
||||
} // namespace vdm::io::platform
|
||||
@@ -9,6 +9,8 @@
|
||||
#include <cstring>
|
||||
#include <utility>
|
||||
|
||||
#include "io/platform/file_ops.hpp"
|
||||
|
||||
namespace vdm::io {
|
||||
namespace {
|
||||
|
||||
@@ -90,20 +92,12 @@ Result<void> SparseFile::open(std::string_view path, const OpenOptions &opts) {
|
||||
bool prealloc = false;
|
||||
if (opts.total_size > 0) {
|
||||
if (opts.preallocate) {
|
||||
// posix_fallocate returns the error number directly and does not set errno.
|
||||
int rc = ::posix_fallocate(fd, 0, static_cast<off_t>(opts.total_size));
|
||||
if (rc == 0) {
|
||||
prealloc = true;
|
||||
} else if (rc == EOPNOTSUPP || rc == ENOSYS || rc == EINVAL) {
|
||||
if (::ftruncate(fd, static_cast<off_t>(opts.total_size)) != 0) {
|
||||
int e = errno;
|
||||
Result<bool> r = platform::preallocate(fd, opts.total_size);
|
||||
if (!r.has_value()) {
|
||||
::close(fd);
|
||||
return sys_error("ftruncate " + p, e);
|
||||
}
|
||||
} else {
|
||||
::close(fd);
|
||||
return sys_error("posix_fallocate " + p, rc);
|
||||
return std::move(r).error();
|
||||
}
|
||||
prealloc = *r;
|
||||
} else if (!opts.truncate_existing) {
|
||||
// Resuming: make sure the file is at least total_size so pwrite offsets land.
|
||||
if (::ftruncate(fd, static_cast<off_t>(opts.total_size)) != 0) {
|
||||
@@ -147,18 +141,13 @@ Result<void> SparseFile::write_at(std::uint64_t offset, ConstByteSpan data) {
|
||||
Result<void> SparseFile::sync() {
|
||||
if (fd_ < 0)
|
||||
return ErrorInfo(Error::internal, "sync on a closed SparseFile");
|
||||
while (::fdatasync(fd_) != 0) {
|
||||
if (errno == EINTR)
|
||||
continue;
|
||||
return sys_error("fdatasync", errno);
|
||||
}
|
||||
return ok();
|
||||
return platform::flush_durable(fd_);
|
||||
}
|
||||
|
||||
void SparseFile::advise_dontneed(std::uint64_t offset, std::uint64_t len) noexcept {
|
||||
if (fd_ < 0 || len == 0)
|
||||
return;
|
||||
::posix_fadvise(fd_, static_cast<off_t>(offset), static_cast<off_t>(len), POSIX_FADV_DONTNEED);
|
||||
platform::advise_dontneed(fd_, offset, len);
|
||||
}
|
||||
|
||||
Result<void> SparseFile::resize(std::uint64_t size) {
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
#include <string>
|
||||
#include <utility>
|
||||
|
||||
#include "io/platform/file_ops.hpp"
|
||||
#include "vdm/util/crc32.hpp"
|
||||
|
||||
namespace vdm::meta {
|
||||
@@ -272,13 +273,11 @@ Result<void> write_veloxpart_file(std::string_view path, const VeloxPart &vp, bo
|
||||
}
|
||||
|
||||
if (fsync) {
|
||||
while (::fdatasync(fd) != 0) {
|
||||
if (errno == EINTR)
|
||||
continue;
|
||||
int e = errno;
|
||||
Result<void> r = vdm::io::platform::flush_durable(fd);
|
||||
if (!r.has_value()) {
|
||||
::close(fd);
|
||||
::unlink(tmp.c_str());
|
||||
return sys_error("fdatasync " + tmp, e);
|
||||
return ErrorInfo(r.error().code, "fdatasync " + tmp + ": " + r.error().context);
|
||||
}
|
||||
}
|
||||
if (::close(fd) != 0) {
|
||||
|
||||
@@ -74,7 +74,6 @@ target_link_libraries(veloxd_sched PUBLIC velox::proto velox::core veloxd_store
|
||||
# --- veloxd_rpc — the RPC transports + dispatcher ------------------------------------
|
||||
add_library(veloxd_rpc STATIC
|
||||
src/rpc/runtime_dir.cpp
|
||||
src/rpc/single_instance.cpp
|
||||
src/rpc/systemd_activation.cpp
|
||||
src/rpc/event_loop.cpp
|
||||
src/rpc/event_hub.cpp
|
||||
@@ -84,6 +83,14 @@ add_library(veloxd_rpc STATIC
|
||||
src/rpc/ws_server.cpp
|
||||
src/rpc/pairing.cpp
|
||||
src/rpc/dispatcher.cpp
|
||||
# Per-OS backends behind daemon/src/rpc/platform/*.hpp (docs/adr/0020,
|
||||
# docs/08-porting.md). Only the Linux backend exists (Phase 0); lane PORT adds
|
||||
# cmake/platform.cmake + platform/macos/ in Phase 1 and this hardcoded list becomes
|
||||
# a velox_platform_sources() call.
|
||||
src/rpc/platform/linux/wakeup.cpp
|
||||
src/rpc/platform/linux/peer_id.cpp
|
||||
src/rpc/platform/linux/instance_lock.cpp
|
||||
src/rpc/platform/linux/runtime_paths.cpp
|
||||
)
|
||||
add_library(velox::daemon_rpc ALIAS veloxd_rpc)
|
||||
|
||||
|
||||
@@ -27,4 +27,5 @@ close. Kept here (not buried in commit messages) so the next pass can see them a
|
||||
| ~~D5~~ | **Mostly closed** — `rpc/event_hub` fans out per-subscription; `session.subscribe` on both transports registers/updates/tears down a real subscription; `Scheduler::transition()` publishes `event.task.state` (with `previousState`) on every state change, scheduler-driven or engine-reported; `dispatcher::on_download_add` publishes `event.task.added`; a 250 ms timer batches `Scheduler::progress_snapshot()` into one `event.task.progress` array per AGENT-DAEMON.md item 5 / the schema's `x-maxRateHz: 4`. Verified live end to end. | — | `event.task.removed` has no source yet (`download.remove` is D3); `event.speed.global`, `event.notify`, `event.auth.required`, `event.settings.changed`, `event.grabber.progress` are unpublished — each lands with its owning handler | as each owning D3 handler lands |
|
||||
| ~~D6~~ | **Closed** — engine numbers now reach the store: `Scheduler::tick()` probes (`EnginePort::probe`) before every `start()`, persisting `sizeBytes`/`resumable`/validators via `Tasks::set_probe_result` before a byte moves; `Scheduler::persist_progress()` (called from `progress_snapshot()` *and* once more from `on_engine_state` right before `release()`/unmap on every terminal transition) writes `downloadedBytes`/`speedBps`/`segments`/`segmentDetail` from the engine's `Progress`, so a task that finishes between two 250 ms ticks (the common case for anything small or fast) still leaves real numbers instead of the pre-persistence defaults. `TaskSummary.segments` is sourced from `segments.size()` when the task has any (matching what actually lands in `segmentDetail`, per the schema's "exactly `segments` entries"), falling back to the engine's `effective_segments` (budget slots *held*, not necessarily physical range count — see `core/include/vdm/task/download.hpp`'s `Progress` comment) only pre-segmentation. `Tasks::set_final_bytes` tops up `on_finished`'s byte count as a last-resort backstop. Migration `0002` adds `speed_bps` to both `tasks` and `segments`, and fixes `segments.state`'s CHECK to include `'pending'` (0001 omitted it, so a pre-connect snapshot could never be written). Verified against real `veloxd` + `tools/testserver` (not just unit tests): `download.list`/`download.get` correct immediately after completion and after a daemon restart. | `sched/scheduler.{cpp,hpp}`, `store/{tasks,segments}.{cpp,hpp}`, `store/migrations/0002_*.sql` | — | done |
|
||||
| ~~D11~~ | **Closed — build order items 7 (the systemd half) and 9: `velox-nmhost`, socket activation, the systemd user units, and `velox(1)`.** `nmhost/src/main.cpp` (185 lines): a `poll()`-driven byte pump between Firefox's native-messaging framing on stdio (4-byte native-byte-order length prefix) and `veloxd`'s own NDJSON framing on the Unix socket — reframes each direction, no JSON parsing, no retry/backoff, exits the moment either side closes. Deliberately dependency-free (no `veloxd_*` library, no `nlohmann_json`) since it runs unconfined outside Firefox's sandbox whatever the packaging format. Two real bugs found and fixed while getting the integration test to actually pass rather than hang: (1) never set the pumped fds non-blocking, so the "drain what's available" read loop blocked on its own second `read()` instead of returning to `poll()`; (2) stdin and stdout are two different descriptors (0 and 1), not one — an early draft polled `POLLOUT` on fd 0, which is opened read-only, so EOF/writability were never both observable through the same `pollfd` entry. Both are exactly the class of bug a "trivial pump" invites and unit tests over the real binary (not just its helper functions) exist specifically to catch. `packaging/nativehost/com.velox.host.json` + its own `README.md` supersede `AGENT-DAEMON.md`'s stale "four locations" (spike S1 / ADR 0003 found only three are real — the fourth, `~/snap/firefox/common/.mozilla/...`, is not read by snap Firefox at all) and spell out the per-user-manifest / postinst implication for PKG/QA. `EnginePort`-style: `rpc/systemd_activation.cpp` is a from-scratch `sd_listen_fds()` (env vars only, no `libsystemd` link — `LISTEN_PID`/`LISTEN_FDS`, fd 3) that `UdsServer::start()` checks first, skipping its own create/bind/chmod/listen when systemd already bound the socket; `packaging/systemd/velox.socket` + `velox.service` are the unit pair, verified both by `systemd-analyze verify` and by an actual fork/dup2/execve simulation of the activation handshake (a real `session.hello` round-tripped over the handed-off fd with no `bind()` ever called inside the daemon for that run). `velox.service` deliberately skips `ProtectSystem=`/`ProtectHome=`/`ReadWritePaths=` — `saveTo.allowedRoots` is user-configurable to anywhere on the filesystem, and a sandbox here would turn a legitimately-configured save location into an opaque `EROFS`/`EACCES` instead of the daemon's own clear `-32011`. `cli/man/velox.1` documents the CLI as it actually exists today (`add`/`ls`/`pause`/`resume`/`rm`, `--json`, the three-tier `queue`/`settings` subcommands `AGENT-DAEMON.md` build step 8 originally sketched are not implemented in `cli/src/main.cpp` yet, so the page doesn't claim they are) — checked warning-free with `groff -mandoc -ww -z`. | `nmhost/{CMakeLists.txt,src/main.cpp,tests/}`, `daemon/src/rpc/{systemd_activation.{hpp,cpp},uds_server.cpp}`, `packaging/{nativehost,systemd}/`, `cli/man/velox.1` | — | done |
|
||||
| ~~D12~~ | **Closed — ADR 0020 Phase 0 for `daemon/`.** Pure refactor, zero behaviour change: the four Linux-only surfaces docs/08-porting.md lists for this lane now sit behind seams in `daemon/src/rpc/platform/{wakeup,peer_id,instance_lock,runtime_paths}.hpp`, with today's syscalls moved unchanged into `platform/linux/*.cpp` (`eventfd` -> `platform::Wakeup`, `SO_PEERCRED`/`struct ucred` -> `platform::peer_of` — same-UID check preserved verbatim as the security property, not touched — abstract-namespace socket -> `platform::acquire_instance_lock`, XDG env lookups -> `platform::runtime_base_dir`/`data_base_dir`, with the 0700-and-owned enforcement itself staying portable POSIX logic in `rpc/runtime_dir.cpp` rather than moving). No fifth seam for `timerfd`: both daemon timers (1 s scheduler tick, 250 ms progress batch) now go through a new portable `EventLoop::add_timer`, which folds the next timer deadline into the existing `poll()` timeout instead of a platform timer fd — exactly the case docs/08-porting.md calls out as not needing a backend ("the loop already has a deadline set"), so it stays in `rpc/event_loop.{hpp,cpp}` untouched by lane PORT. `daemon/CMakeLists.txt` lists the four `platform/linux/*.cpp` files directly (no `cmake/platform.cmake` yet — that and `platform/macos/` are PORT's, per the CLAUDE.md lane table added alongside ADR 0020); PORT's Phase 1 replaces the hardcoded list with `velox_platform_sources()`. Old `rpc/single_instance.{hpp,cpp}` deleted (folded into the new seam); `single_instance_test.cpp` retargeted at `rpc/platform/instance_lock.hpp` with no behaviour change. Full suite 59/59 green; `git diff` outside `platform/linux/` is moves/call-site updates only, no `#ifdef` anywhere in `daemon/`. | `rpc/platform/{wakeup,peer_id,instance_lock,runtime_paths}.hpp`, `rpc/platform/linux/*.cpp`, `rpc/event_loop.{hpp,cpp}`, `rpc/{uds_server,runtime_dir}.cpp`, `main.cpp`, `CMakeLists.txt` | — | done |
|
||||
| — | ~~Observed, not fixed (CORE, not this lane)~~ — **routed to CORE by the user.** `vdm::task::Progress.speed_bps` reads back as `0` for the whole lifetime of a live, real (non-fake) throttled download, despite `downloadedBytes` visibly advancing between polls — `core/src/task/download_task.cpp`'s per-worker EWMA never seems to produce a nonzero aggregate in this build. DAEMON passes `EnginePort::progress()`'s `speed_bps` straight through (`Scheduler::persist_progress`); nothing in this lane drops it. Still reproduces in the D4b live checks above (0 throughout a paused/resumed/cancelled transfer whose `downloadedBytes` visibly moved) — not re-filed, since it's already CORE's. |
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
// (AGENT-DAEMON.md build order, steps 1 and 3). The scheduler and the engine link land
|
||||
// next.
|
||||
|
||||
#include <chrono>
|
||||
#include <csignal>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
@@ -15,16 +16,14 @@
|
||||
#include <sys/un.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <sys/timerfd.h>
|
||||
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
#include "rpc/dispatcher.hpp"
|
||||
#include "rpc/event_hub.hpp"
|
||||
#include "rpc/event_loop.hpp"
|
||||
#include "rpc/pairing.hpp"
|
||||
#include "rpc/platform/instance_lock.hpp"
|
||||
#include "rpc/runtime_dir.hpp"
|
||||
#include "rpc/single_instance.hpp"
|
||||
#include "rpc/uds_server.hpp"
|
||||
#include "rpc/ws_server.hpp"
|
||||
#include "sched/engine_port_core.hpp"
|
||||
@@ -56,7 +55,7 @@ int main() {
|
||||
return 1;
|
||||
}
|
||||
|
||||
const int lock_fd = velox::daemon::rpc::acquire_single_instance_lock(rt.path);
|
||||
const int lock_fd = velox::daemon::rpc::platform::acquire_instance_lock(rt.path);
|
||||
if (lock_fd < 0) {
|
||||
std::cerr << "veloxd: another instance is already running for this runtime "
|
||||
"directory (" << rt.path << ")\n";
|
||||
@@ -109,32 +108,15 @@ int main() {
|
||||
});
|
||||
|
||||
// A 1 s timer re-runs the scheduler so schedule windows opening/closing and any
|
||||
// missed nudge are picked up. Registered on the loop, no extra thread.
|
||||
const int tick_fd = ::timerfd_create(CLOCK_MONOTONIC, TFD_NONBLOCK | TFD_CLOEXEC);
|
||||
if (tick_fd >= 0) {
|
||||
itimerspec spec{};
|
||||
spec.it_value.tv_sec = 1;
|
||||
spec.it_interval.tv_sec = 1;
|
||||
::timerfd_settime(tick_fd, 0, &spec, nullptr);
|
||||
loop.add_fd(tick_fd, velox::daemon::rpc::kRead, [&](int fd, unsigned) {
|
||||
std::uint64_t ticks = 0;
|
||||
[[maybe_unused]] ssize_t n = ::read(fd, &ticks, sizeof(ticks));
|
||||
(void)scheduler.tick();
|
||||
});
|
||||
}
|
||||
// missed nudge are picked up. Expressed as a poll() timeout behind EventLoop rather
|
||||
// than a timerfd (docs/08-porting.md: "the loop already has a deadline set" — no
|
||||
// per-OS backend needed here, unlike the other three daemon/rpc seams).
|
||||
loop.add_timer(std::chrono::seconds(1), [&] { (void)scheduler.tick(); });
|
||||
|
||||
// event.task.progress: one array message at <=4 Hz (schema x-maxRateHz), never one
|
||||
// notification per task (AGENT-DAEMON.md item 5). 250 ms keeps every active task's
|
||||
// segment bar under 4 Hz without depending on how many tasks are running.
|
||||
const int progress_fd = ::timerfd_create(CLOCK_MONOTONIC, TFD_NONBLOCK | TFD_CLOEXEC);
|
||||
if (progress_fd >= 0) {
|
||||
itimerspec spec{};
|
||||
spec.it_value.tv_nsec = 250'000'000;
|
||||
spec.it_interval.tv_nsec = 250'000'000;
|
||||
::timerfd_settime(progress_fd, 0, &spec, nullptr);
|
||||
loop.add_fd(progress_fd, velox::daemon::rpc::kRead, [&](int fd, unsigned) {
|
||||
std::uint64_t ticks = 0;
|
||||
[[maybe_unused]] ssize_t n = ::read(fd, &ticks, sizeof(ticks));
|
||||
loop.add_timer(std::chrono::milliseconds(250), [&] {
|
||||
const auto rows = scheduler.progress_snapshot();
|
||||
if (rows.empty()) return;
|
||||
|
||||
@@ -159,7 +141,6 @@ int main() {
|
||||
hub.publish(velox::proto::Event::TaskProgress,
|
||||
velox::proto::make_notification(velox::proto::Event::TaskProgress, params));
|
||||
});
|
||||
}
|
||||
|
||||
velox::daemon::rpc::UdsServer uds(loop, dispatcher, hub, rt.socket_path(), &scheduler);
|
||||
if (const auto ec = uds.start()) {
|
||||
@@ -186,14 +167,6 @@ int main() {
|
||||
loop.run();
|
||||
std::cout << "veloxd: shutting down\n";
|
||||
|
||||
if (tick_fd >= 0) {
|
||||
loop.del_fd(tick_fd);
|
||||
::close(tick_fd);
|
||||
}
|
||||
if (progress_fd >= 0) {
|
||||
loop.del_fd(progress_fd);
|
||||
::close(progress_fd);
|
||||
}
|
||||
g_loop = nullptr;
|
||||
::close(lock_fd);
|
||||
return 0;
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
#include "rpc/event_loop.hpp"
|
||||
|
||||
#include <poll.h>
|
||||
#include <sys/eventfd.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <cerrno>
|
||||
#include <cstdint>
|
||||
#include <stdexcept>
|
||||
@@ -12,14 +11,10 @@
|
||||
namespace velox::daemon::rpc {
|
||||
|
||||
EventLoop::EventLoop() {
|
||||
wake_fd_ = ::eventfd(0, EFD_NONBLOCK | EFD_CLOEXEC);
|
||||
if (wake_fd_ < 0) throw std::runtime_error("eventfd() failed");
|
||||
fds_.emplace(wake_fd_, Entry{kRead, [this](int, unsigned) { drain_wakeup(); }});
|
||||
fds_.emplace(wakeup_.pollfd(), Entry{kRead, [this](int, unsigned) { wakeup_.drain(); }});
|
||||
}
|
||||
|
||||
EventLoop::~EventLoop() {
|
||||
if (wake_fd_ >= 0) ::close(wake_fd_);
|
||||
}
|
||||
EventLoop::~EventLoop() = default;
|
||||
|
||||
void EventLoop::add_fd(int fd, unsigned interest, Callback cb) {
|
||||
fds_[fd] = Entry{interest, std::move(cb)};
|
||||
@@ -30,15 +25,11 @@ void EventLoop::mod_fd(int fd, unsigned interest) {
|
||||
}
|
||||
|
||||
void EventLoop::del_fd(int fd) {
|
||||
if (fd == wake_fd_) return; // internal, never removed
|
||||
if (fd == wakeup_.pollfd()) return; // internal, never removed
|
||||
fds_.erase(fd);
|
||||
}
|
||||
|
||||
void EventLoop::wake() noexcept {
|
||||
const std::uint64_t one = 1;
|
||||
// Best-effort: an EAGAIN here means a wakeup is already pending, which is fine.
|
||||
[[maybe_unused]] ssize_t n = ::write(wake_fd_, &one, sizeof(one));
|
||||
}
|
||||
void EventLoop::wake() noexcept { wakeup_.signal(); }
|
||||
|
||||
void EventLoop::stop() noexcept {
|
||||
stop_requested_ = true;
|
||||
@@ -62,9 +53,39 @@ void EventLoop::drain_posts() {
|
||||
for (auto& fn : batch) fn();
|
||||
}
|
||||
|
||||
void EventLoop::drain_wakeup() noexcept {
|
||||
std::uint64_t sink = 0;
|
||||
while (::read(wake_fd_, &sink, sizeof(sink)) > 0) {
|
||||
EventLoop::TimerId EventLoop::add_timer(std::chrono::milliseconds interval,
|
||||
std::function<void()> cb) {
|
||||
const TimerId id = next_timer_id_++;
|
||||
timers_.emplace(id, Timer{std::chrono::steady_clock::now() + interval, interval,
|
||||
std::move(cb)});
|
||||
wake(); // the loop may already be blocked on a longer timeout
|
||||
return id;
|
||||
}
|
||||
|
||||
void EventLoop::remove_timer(TimerId id) { timers_.erase(id); }
|
||||
|
||||
int EventLoop::next_timeout_ms() const {
|
||||
if (timers_.empty()) return -1;
|
||||
auto soonest = timers_.begin()->second.next;
|
||||
for (const auto& [id, t] : timers_) soonest = std::min(soonest, t.next);
|
||||
const auto now = std::chrono::steady_clock::now();
|
||||
const auto ms = std::chrono::duration_cast<std::chrono::milliseconds>(soonest - now);
|
||||
return ms.count() > 0 ? static_cast<int>(ms.count()) : 0;
|
||||
}
|
||||
|
||||
void EventLoop::run_due_timers() {
|
||||
const auto now = std::chrono::steady_clock::now();
|
||||
// Snapshot ids first: a callback may add/remove timers, which would invalidate
|
||||
// iteration over timers_ directly.
|
||||
std::vector<TimerId> due;
|
||||
for (auto& [id, t] : timers_) {
|
||||
if (t.next <= now) due.push_back(id);
|
||||
}
|
||||
for (const TimerId id : due) {
|
||||
const auto it = timers_.find(id);
|
||||
if (it == timers_.end()) continue; // removed by an earlier callback this pass
|
||||
it->second.next = now + it->second.interval;
|
||||
it->second.cb();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -83,18 +104,22 @@ void EventLoop::run() {
|
||||
short ev = 0;
|
||||
if (e.interest & kRead) ev |= POLLIN;
|
||||
if (e.interest & kWrite) ev |= POLLOUT;
|
||||
if (ev == 0 && fd != wake_fd_) continue;
|
||||
if (ev == 0 && fd != wakeup_.pollfd()) continue;
|
||||
pollfd p{};
|
||||
p.fd = fd;
|
||||
p.events = ev;
|
||||
pfds.push_back(p);
|
||||
}
|
||||
|
||||
const int rc = ::poll(pfds.data(), pfds.size(), -1);
|
||||
const int rc = ::poll(pfds.data(), pfds.size(), next_timeout_ms());
|
||||
if (rc < 0) {
|
||||
if (errno == EINTR) continue;
|
||||
throw std::runtime_error("poll() failed");
|
||||
}
|
||||
|
||||
drain_posts();
|
||||
run_due_timers();
|
||||
|
||||
if (rc == 0) continue;
|
||||
|
||||
// Snapshot the fds that fired before invoking any callback: a callback may erase
|
||||
@@ -104,8 +129,6 @@ void EventLoop::run() {
|
||||
if (p.revents != 0) fired.push_back(p.fd);
|
||||
}
|
||||
|
||||
drain_posts();
|
||||
|
||||
for (const int fd : fired) {
|
||||
const auto it = fds_.find(fd);
|
||||
if (it == fds_.end()) continue; // removed by an earlier callback this pass
|
||||
|
||||
@@ -10,12 +10,15 @@
|
||||
// from any thread or a signal handler — they only write() a byte to an internal eventfd.
|
||||
|
||||
#include <atomic>
|
||||
#include <chrono>
|
||||
#include <cstdint>
|
||||
#include <functional>
|
||||
#include <mutex>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
#include "rpc/platform/wakeup.hpp"
|
||||
|
||||
namespace velox::daemon::rpc {
|
||||
|
||||
enum Interest : unsigned {
|
||||
@@ -58,22 +61,43 @@ public:
|
||||
// marshal an engine-thread callback back onto the RPC loop.
|
||||
void post(std::function<void()> fn);
|
||||
|
||||
using TimerId = std::uint64_t;
|
||||
|
||||
// Fire `cb` roughly every `interval` for as long as the loop runs, expressed as a
|
||||
// poll(2) timeout rather than a platform timer fd (docs/08-porting.md: "the loop
|
||||
// already has a deadline set" — no per-OS backend needed for this one). Not
|
||||
// reentrant-safe to call from inside a timer callback other than the one running.
|
||||
TimerId add_timer(std::chrono::milliseconds interval, std::function<void()> cb);
|
||||
void remove_timer(TimerId id);
|
||||
|
||||
private:
|
||||
struct Entry {
|
||||
unsigned interest;
|
||||
Callback cb;
|
||||
};
|
||||
|
||||
void drain_wakeup() noexcept;
|
||||
void drain_posts();
|
||||
struct Timer {
|
||||
std::chrono::steady_clock::time_point next;
|
||||
std::chrono::milliseconds interval;
|
||||
std::function<void()> cb;
|
||||
};
|
||||
|
||||
int wake_fd_; // eventfd, always registered
|
||||
void drain_posts();
|
||||
// Milliseconds until the next timer is due, or -1 if there are none (poll()'s "block
|
||||
// forever" convention).
|
||||
int next_timeout_ms() const;
|
||||
void run_due_timers();
|
||||
|
||||
platform::Wakeup wakeup_;
|
||||
bool running_ = false;
|
||||
std::atomic<bool> stop_requested_ = false; // set from stop(), read by run()
|
||||
std::unordered_map<int, Entry> fds_;
|
||||
|
||||
std::mutex post_mu_;
|
||||
std::vector<std::function<void()>> posts_;
|
||||
|
||||
TimerId next_timer_id_ = 1;
|
||||
std::unordered_map<TimerId, Timer> timers_;
|
||||
};
|
||||
|
||||
} // namespace velox::daemon::rpc
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
#pragma once
|
||||
|
||||
// Single-instance guard, keyed by the resolved runtime directory (see rpc/runtime_dir.hpp)
|
||||
// so isolated instances pointed at different runtime dirs never contend (docs/01 §2). The
|
||||
// mechanism is Linux's abstract-namespace Unix socket; macOS has no abstract namespace and
|
||||
// uses a real socket file plus flock() instead, which must unlink a stale socket left by a
|
||||
// crashed process (docs/08-porting.md "API mapping" — the abstract version got that for
|
||||
// free from the kernel).
|
||||
|
||||
#include <string>
|
||||
#include <system_error>
|
||||
|
||||
namespace velox::daemon::rpc::platform {
|
||||
|
||||
// Returns the held fd (kept open for the process lifetime; closing it releases the lock)
|
||||
// or -1 if another process already holds the lock for this exact `runtime_dir`, or on any
|
||||
// other error acquiring it.
|
||||
int acquire_instance_lock(const std::string& runtime_dir);
|
||||
|
||||
} // namespace velox::daemon::rpc::platform
|
||||
@@ -1,4 +1,4 @@
|
||||
#include "rpc/single_instance.hpp"
|
||||
#include "rpc/platform/instance_lock.hpp"
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstring>
|
||||
@@ -9,9 +9,9 @@
|
||||
|
||||
#include "util/crypto.hpp"
|
||||
|
||||
namespace velox::daemon::rpc {
|
||||
namespace velox::daemon::rpc::platform {
|
||||
|
||||
int acquire_single_instance_lock(const std::string& runtime_dir) {
|
||||
int acquire_instance_lock(const std::string& runtime_dir) {
|
||||
const int fd = ::socket(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0);
|
||||
if (fd < 0) return -1;
|
||||
|
||||
@@ -35,4 +35,4 @@ int acquire_single_instance_lock(const std::string& runtime_dir) {
|
||||
return fd;
|
||||
}
|
||||
|
||||
} // namespace velox::daemon::rpc
|
||||
} // namespace velox::daemon::rpc::platform
|
||||
@@ -0,0 +1,17 @@
|
||||
#include "rpc/platform/peer_id.hpp"
|
||||
|
||||
#include <sys/socket.h>
|
||||
|
||||
namespace velox::daemon::rpc::platform {
|
||||
|
||||
std::error_code peer_of(int fd, PeerId& out) {
|
||||
ucred cred{};
|
||||
socklen_t len = sizeof(cred);
|
||||
if (::getsockopt(fd, SOL_SOCKET, SO_PEERCRED, &cred, &len) != 0) {
|
||||
return std::error_code(errno, std::generic_category());
|
||||
}
|
||||
out.uid = cred.uid;
|
||||
return {};
|
||||
}
|
||||
|
||||
} // namespace velox::daemon::rpc::platform
|
||||
@@ -0,0 +1,44 @@
|
||||
#include "rpc/platform/runtime_paths.hpp"
|
||||
|
||||
#include <sys/stat.h>
|
||||
#include <sys/types.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <cerrno>
|
||||
#include <cstdlib>
|
||||
|
||||
namespace velox::daemon::rpc::platform {
|
||||
|
||||
namespace {
|
||||
std::error_code errc(int e) { return std::error_code(e, std::generic_category()); }
|
||||
} // namespace
|
||||
|
||||
std::error_code runtime_base_dir(std::string& out) {
|
||||
if (const char* xdg = ::getenv("XDG_RUNTIME_DIR"); xdg != nullptr && xdg[0] != '\0') {
|
||||
out = xdg;
|
||||
return {};
|
||||
}
|
||||
const std::string base = "/run/user/" + std::to_string(::geteuid());
|
||||
struct stat st{};
|
||||
if (::stat(base.c_str(), &st) != 0 || !S_ISDIR(st.st_mode)) {
|
||||
// No XDG_RUNTIME_DIR and no /run/user/<uid>: refuse rather than pick an insecure
|
||||
// fallback. The caller surfaces this as "cannot start".
|
||||
return errc(ENOENT);
|
||||
}
|
||||
out = base;
|
||||
return {};
|
||||
}
|
||||
|
||||
std::error_code data_base_dir(std::string& out) {
|
||||
if (const char* xdg = ::getenv("XDG_DATA_HOME"); xdg != nullptr && xdg[0] != '\0') {
|
||||
out = xdg;
|
||||
return {};
|
||||
}
|
||||
if (const char* home = ::getenv("HOME"); home != nullptr && home[0] != '\0') {
|
||||
out = std::string(home) + "/.local/share";
|
||||
return {};
|
||||
}
|
||||
return errc(ENOENT);
|
||||
}
|
||||
|
||||
} // namespace velox::daemon::rpc::platform
|
||||
@@ -0,0 +1,32 @@
|
||||
#include "rpc/platform/wakeup.hpp"
|
||||
|
||||
#include <sys/eventfd.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <cstdint>
|
||||
#include <stdexcept>
|
||||
|
||||
namespace velox::daemon::rpc::platform {
|
||||
|
||||
Wakeup::Wakeup() {
|
||||
fd_ = ::eventfd(0, EFD_NONBLOCK | EFD_CLOEXEC);
|
||||
if (fd_ < 0) throw std::runtime_error("eventfd() failed");
|
||||
}
|
||||
|
||||
Wakeup::~Wakeup() {
|
||||
if (fd_ >= 0) ::close(fd_);
|
||||
}
|
||||
|
||||
void Wakeup::signal() noexcept {
|
||||
const std::uint64_t one = 1;
|
||||
// Best-effort: an EAGAIN here means a wakeup is already pending, which is fine.
|
||||
[[maybe_unused]] ssize_t n = ::write(fd_, &one, sizeof(one));
|
||||
}
|
||||
|
||||
void Wakeup::drain() noexcept {
|
||||
std::uint64_t sink = 0;
|
||||
while (::read(fd_, &sink, sizeof(sink)) > 0) {
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace velox::daemon::rpc::platform
|
||||
@@ -0,0 +1,22 @@
|
||||
#pragma once
|
||||
|
||||
// Identifies the process on the other end of a connected Unix-domain socket, for the
|
||||
// same-UID check that is the Unix transport's authorization boundary (docs/01 §2,
|
||||
// CLAUDE.md §4). `SO_PEERCRED`/`struct ucred` is Linux-only; macOS has `getpeereid`,
|
||||
// Windows named pipes carry a token instead (docs/08-porting.md "The seams" /
|
||||
// "API mapping"). The same-UID check itself is the security property and must not change
|
||||
// per-OS (docs/adr/0020 decision 2).
|
||||
|
||||
#include <system_error>
|
||||
|
||||
namespace velox::daemon::rpc::platform {
|
||||
|
||||
struct PeerId {
|
||||
unsigned int uid = 0;
|
||||
};
|
||||
|
||||
// On success, fills `out` with the peer's identity of the already-connected `fd`. On
|
||||
// failure, `out` is untouched and the error_code explains why (matches errno on Linux).
|
||||
std::error_code peer_of(int fd, PeerId& out);
|
||||
|
||||
} // namespace velox::daemon::rpc::platform
|
||||
@@ -0,0 +1,22 @@
|
||||
#pragma once
|
||||
|
||||
// Where the OS wants ephemeral runtime state and persistent user data to live, before
|
||||
// velox appends its own "/velox" subdirectory and applies the shared 0700-and-owned check
|
||||
// (rpc/runtime_dir.cpp — that enforcement is portable POSIX logic and stays there; only
|
||||
// "which base directory" is per-OS). Linux: XDG. macOS: $TMPDIR (runtime) and
|
||||
// ~/Library/Application Support (data) — see docs/08-porting.md "API mapping".
|
||||
|
||||
#include <string>
|
||||
#include <system_error>
|
||||
|
||||
namespace velox::daemon::rpc::platform {
|
||||
|
||||
// The base directory ephemeral runtime state (sockets, lock files) should live under,
|
||||
// e.g. "$XDG_RUNTIME_DIR" or "/run/user/<uid>" on Linux. No trailing slash.
|
||||
std::error_code runtime_base_dir(std::string& out);
|
||||
|
||||
// The base directory persistent user data should live under, e.g. "$XDG_DATA_HOME" or
|
||||
// "~/.local/share" on Linux. No trailing slash.
|
||||
std::error_code data_base_dir(std::string& out);
|
||||
|
||||
} // namespace velox::daemon::rpc::platform
|
||||
@@ -0,0 +1,36 @@
|
||||
#pragma once
|
||||
|
||||
// The event-loop wakeup primitive (docs/adr/0020, docs/08-porting.md). EventLoop uses this
|
||||
// to interrupt a blocked poll() from another thread or a signal handler — the eventfd
|
||||
// mechanics themselves are Linux-only; every other OS backend just needs something
|
||||
// poll()-able that signal()/drain() can drive the same way (docs/08 §"The seams": a
|
||||
// self-pipe on macOS, an event object on Windows).
|
||||
//
|
||||
// One implementation file per OS under platform/<os>/wakeup.cpp; this header carries no
|
||||
// OS types and no #ifdef (ADR 0020 decision 1).
|
||||
|
||||
namespace velox::daemon::rpc::platform {
|
||||
|
||||
class Wakeup {
|
||||
public:
|
||||
Wakeup();
|
||||
~Wakeup();
|
||||
|
||||
Wakeup(const Wakeup&) = delete;
|
||||
Wakeup& operator=(const Wakeup&) = delete;
|
||||
|
||||
// The fd to register with poll(2) for readability.
|
||||
int pollfd() const noexcept { return fd_; }
|
||||
|
||||
// Make pollfd() readable. Async-signal-safe and thread-safe.
|
||||
void signal() noexcept;
|
||||
|
||||
// Drain whatever signal() queued so pollfd() stops being readable. Call this from the
|
||||
// loop thread once pollfd() fires.
|
||||
void drain() noexcept;
|
||||
|
||||
private:
|
||||
int fd_ = -1;
|
||||
};
|
||||
|
||||
} // namespace velox::daemon::rpc::platform
|
||||
@@ -5,9 +5,10 @@
|
||||
#include <unistd.h>
|
||||
|
||||
#include <cerrno>
|
||||
#include <cstdlib>
|
||||
#include <string>
|
||||
|
||||
#include "rpc/platform/runtime_paths.hpp"
|
||||
|
||||
namespace velox::daemon::rpc {
|
||||
|
||||
namespace {
|
||||
@@ -33,17 +34,7 @@ std::error_code ensure_private_dir(const std::string& dir) {
|
||||
|
||||
std::error_code resolve_runtime_dir(RuntimeDir& out) {
|
||||
std::string base;
|
||||
if (const char* xdg = ::getenv("XDG_RUNTIME_DIR"); xdg != nullptr && xdg[0] != '\0') {
|
||||
base = xdg;
|
||||
} else {
|
||||
base = "/run/user/" + std::to_string(::geteuid());
|
||||
struct stat st{};
|
||||
if (::stat(base.c_str(), &st) != 0 || !S_ISDIR(st.st_mode)) {
|
||||
// No XDG_RUNTIME_DIR and no /run/user/<uid>: we refuse rather than pick an
|
||||
// insecure fallback. The caller surfaces this as "cannot start".
|
||||
return errc(ENOENT);
|
||||
}
|
||||
}
|
||||
if (auto ec = platform::runtime_base_dir(base)) return ec;
|
||||
if (!base.empty() && base.back() == '/') base.pop_back();
|
||||
|
||||
const std::string dir = base + "/velox";
|
||||
@@ -55,13 +46,7 @@ std::error_code resolve_runtime_dir(RuntimeDir& out) {
|
||||
|
||||
std::error_code resolve_data_dir(std::string& out) {
|
||||
std::string base;
|
||||
if (const char* xdg = ::getenv("XDG_DATA_HOME"); xdg != nullptr && xdg[0] != '\0') {
|
||||
base = xdg;
|
||||
} else if (const char* home = ::getenv("HOME"); home != nullptr && home[0] != '\0') {
|
||||
base = std::string(home) + "/.local/share";
|
||||
} else {
|
||||
return errc(ENOENT);
|
||||
}
|
||||
if (auto ec = platform::data_base_dir(base)) return ec;
|
||||
if (!base.empty() && base.back() == '/') base.pop_back();
|
||||
|
||||
// Create the XDG base components leniently, then the velox dir with a strict check.
|
||||
|
||||
@@ -1,27 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
// Single-instance guard: bind an abstract-namespace Unix socket whose name is derived
|
||||
// from the canonical runtime directory (resolve_runtime_dir's result — already the
|
||||
// per-user default, /run/user/<uid>/velox, unless XDG_RUNTIME_DIR says otherwise). A
|
||||
// second daemon pointed at the same runtime dir gets EADDRINUSE and exits; one pointed at
|
||||
// a different (isolated / test) runtime dir gets its own lock and starts fine. The kernel
|
||||
// reclaims an abstract-namespace address when the holding process dies, so a crash never
|
||||
// wedges it (docs/01 §2).
|
||||
//
|
||||
// Naming this "velox-daemon-<euid>" alone (the old scheme) meant exactly one name per
|
||||
// user system-wide, so XDG_RUNTIME_DIR isolation never reached it: a leaked test veloxd
|
||||
// with the same euid held the lock for every isolated instance too, real or test, until
|
||||
// it was killed. Hashing the resolved runtime dir path instead keeps the real per-user
|
||||
// daemon unique (its runtime dir is unique to it) while letting isolated instances that
|
||||
// each point at their own runtime dir coexist.
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace velox::daemon::rpc {
|
||||
|
||||
// Returns the held fd (kept open for the process lifetime; closing it releases the lock)
|
||||
// or -1 if another process already holds the lock for this exact `runtime_dir`, or on any
|
||||
// other socket error.
|
||||
int acquire_single_instance_lock(const std::string& runtime_dir);
|
||||
|
||||
} // namespace velox::daemon::rpc
|
||||
@@ -15,6 +15,7 @@
|
||||
#include <fcntl.h>
|
||||
|
||||
#include "rpc/event_loop.hpp"
|
||||
#include "rpc/platform/peer_id.hpp"
|
||||
#include "rpc/systemd_activation.hpp"
|
||||
#include "version.hpp"
|
||||
|
||||
@@ -140,10 +141,8 @@ void UdsServer::on_listener_readable() {
|
||||
break; // EMFILE/ENFILE: stop accepting this pass; loop retries on next readable
|
||||
}
|
||||
|
||||
ucred cred{};
|
||||
socklen_t len = sizeof(cred);
|
||||
if (::getsockopt(cfd, SOL_SOCKET, SO_PEERCRED, &cred, &len) != 0 ||
|
||||
cred.uid != ::geteuid()) {
|
||||
platform::PeerId peer{};
|
||||
if (platform::peer_of(cfd, peer) || peer.uid != ::geteuid()) {
|
||||
// Not the same user. The socket mode should already prevent this; refuse hard
|
||||
// regardless — this is the authorization on the Unix transport (docs/01 §2).
|
||||
::close(cfd);
|
||||
|
||||
@@ -5,15 +5,15 @@
|
||||
#include <unistd.h>
|
||||
|
||||
#include "check.hpp"
|
||||
#include "rpc/single_instance.hpp"
|
||||
#include "rpc/platform/instance_lock.hpp"
|
||||
|
||||
using namespace velox::daemon::rpc;
|
||||
using namespace velox::daemon::rpc::platform;
|
||||
|
||||
void run() {
|
||||
// Two different runtime dirs: both acquire the lock independently.
|
||||
{
|
||||
const int a = acquire_single_instance_lock("/run/user/1000/velox-test-a");
|
||||
const int b = acquire_single_instance_lock("/run/user/1000/velox-test-b");
|
||||
const int a = acquire_instance_lock("/run/user/1000/velox-test-a");
|
||||
const int b = acquire_instance_lock("/run/user/1000/velox-test-b");
|
||||
CHECK(a >= 0);
|
||||
CHECK(b >= 0);
|
||||
if (a >= 0) ::close(a);
|
||||
@@ -22,16 +22,16 @@ void run() {
|
||||
|
||||
// Same runtime dir: the second attempt is refused while the first still holds it.
|
||||
{
|
||||
const int first = acquire_single_instance_lock("/run/user/1000/velox-test-shared");
|
||||
const int first = acquire_instance_lock("/run/user/1000/velox-test-shared");
|
||||
CHECK(first >= 0);
|
||||
const int second = acquire_single_instance_lock("/run/user/1000/velox-test-shared");
|
||||
const int second = acquire_instance_lock("/run/user/1000/velox-test-shared");
|
||||
CHECK(second < 0);
|
||||
if (first >= 0) ::close(first);
|
||||
if (second >= 0) ::close(second);
|
||||
|
||||
// Releasing (closing) the fd frees the abstract-namespace name immediately — a
|
||||
// third attempt at the same dir succeeds once the first is gone.
|
||||
const int third = acquire_single_instance_lock("/run/user/1000/velox-test-shared");
|
||||
const int third = acquire_instance_lock("/run/user/1000/velox-test-shared");
|
||||
CHECK(third >= 0);
|
||||
if (third >= 0) ::close(third);
|
||||
}
|
||||
|
||||
@@ -37,6 +37,7 @@ add_library(velox-gui-lib STATIC
|
||||
src/tray/TrayIcon.cpp
|
||||
src/widgets/DropTargetWidget.cpp
|
||||
src/util/ThemeManager.cpp
|
||||
src/util/IconTheme.cpp
|
||||
src/util/UiThreadWatchdog.cpp
|
||||
src/mainwindow/CategoryPanel.cpp
|
||||
src/mainwindow/MainWindow.cpp
|
||||
@@ -50,6 +51,18 @@ qt_add_resources(velox-gui-lib "theme"
|
||||
FILES resources/qss/idm-like.qss resources/qss/dark.qss
|
||||
)
|
||||
|
||||
# gui/resources/icons/LICENSE: Adwaita-derived (GNOME Project), LGPL-3 or CC-BY-SA-3/4.
|
||||
# light/dark are the same glyphs recoloured for each toolbar skin (IconTheme picks
|
||||
# between them the same way ThemeManager picks QSS); app/ is the fixed full-colour
|
||||
# window/tray glyph.
|
||||
file(GLOB VELOX_GUI_ICONS_LIGHT resources/icons/light/*.svg)
|
||||
file(GLOB VELOX_GUI_ICONS_DARK resources/icons/dark/*.svg)
|
||||
qt_add_resources(velox-gui-lib "icons"
|
||||
PREFIX "/icons"
|
||||
BASE "resources/icons"
|
||||
FILES ${VELOX_GUI_ICONS_LIGHT} ${VELOX_GUI_ICONS_DARK} resources/icons/app/velox.svg
|
||||
)
|
||||
|
||||
target_include_directories(velox-gui-lib PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/src)
|
||||
target_compile_features(velox-gui-lib PUBLIC cxx_std_23)
|
||||
target_compile_options(velox-gui-lib PRIVATE -Wall -Wextra -Wpedantic -Werror)
|
||||
|
||||
@@ -219,3 +219,35 @@ Worth making explicit anyway, since relying on undocumented target leakage is fr
|
||||
No apt change needed either way — `qt6-base-dev` (already in `APT_GUI`) ships `QtDBus`'s
|
||||
headers directly (verified live: `dpkg -L qt6-base-dev | grep -i dbus` lists the whole
|
||||
`QtDBus/` include tree).
|
||||
|
||||
---
|
||||
|
||||
## R5 — real icon set has landed; stop shipping the placeholder, here's the install path
|
||||
|
||||
`gui/resources/icons/.gitkeep` is gone. There's a real, licensed icon set now (Adwaita,
|
||||
GNOME Project, LGPL-3-or-CC-BY-SA — full text and per-file provenance in
|
||||
`gui/resources/icons/LICENSE`), embedded into `velox-gui` via `qt_add_resources` in
|
||||
`gui/CMakeLists.txt` (`:/icons/{light,dark}/<name>.svg` for toolbar/menu actions,
|
||||
`:/icons/app/velox.svg` for the window and tray icon). Nothing needs installing
|
||||
separately for the binary itself — the SVGs are baked into the Qt resource system at
|
||||
build time, not read from disk at runtime.
|
||||
|
||||
What that means for whatever `.deb` postinst / desktop-entry work PKG is doing:
|
||||
|
||||
* **Stop bundling a placeholder icon file in the test `.deb`.** There's no longer a gap
|
||||
to fill — `velox-gui`'s own binary already carries its icon.
|
||||
* **App icon for the `.desktop` file / desktop menu entry**, if PKG is generating one:
|
||||
the source SVG is `gui/resources/icons/app/velox.svg` (full colour, unmodified Adwaita
|
||||
`folder-download` glyph, same in light and dark). If the packaging pipeline wants a
|
||||
filesystem-installed icon (standard XDG icon-theme layout, e.g.
|
||||
`/usr/share/icons/hicolor/scalable/apps/velox.svg`, referenced from the `.desktop`
|
||||
file's `Icon=velox` key) rather than relying on the binary's embedded window icon for
|
||||
the taskbar/launcher, that single file is the one to install — no rasterization needed,
|
||||
it's already a clean scalable SVG. GUI has no packaging-stage code to add this itself
|
||||
(CLAUDE.md §1: `packaging/` is PKG's lane), so filing the path here rather than reaching
|
||||
across.
|
||||
* Licence compliance for the `.deb`: `gui/resources/icons/LICENSE` should ship somewhere
|
||||
discoverable from the package (e.g. copied into `debian/copyright` or
|
||||
`/usr/share/doc/velox/copyright` alongside whatever other third-party attributions PKG
|
||||
already tracks) — it documents the Adwaita provenance and dual LGPL-3/CC-BY-SA license
|
||||
per file, which needs to be preserved wherever the binary that embeds these SVGs ships.
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
Icon set for Velox Download Manager
|
||||
====================================
|
||||
|
||||
Source: GNOME Adwaita icon theme (`adwaita-icon-theme`), version bundled with
|
||||
Debian/Ubuntu, downloaded from <https://download.gnome.org/sources/adwaita-icon-theme/>.
|
||||
This is the same "compatibly-licensed set" path called out in
|
||||
docs/03-gui-spec.md's legal note (Papirus/Breeze-derived is the other option; we used
|
||||
what was locally available and equally suitable) and gui/docs/agents/AGENT-GUI.md.
|
||||
|
||||
Nothing in this directory is IDM's artwork. Files below are recolored (fill colour only —
|
||||
geometry untouched) derivatives of Adwaita SVGs, laid out in IDM's toolbar positions per
|
||||
docs/03-gui-spec.md §1, so the *positions* rhyme with IDM's muscle memory while the
|
||||
*pictures* are GNOME's.
|
||||
|
||||
Copyright
|
||||
---------
|
||||
(c) 2002-2014 The GNOME Project and Adwaita icon theme contributors, including:
|
||||
Ulisse Perusin, Riccardo Buzzotta, Josef Vybíral, Hylke Bons, Ricardo González,
|
||||
Lapo Calamandrei, Rodney Dawes, Luca Ferretti, Tuomas Kuosmanen, Andreas Nilsson,
|
||||
Jakub Steiner, Claire Alexander, Darren Wilson, and other contributors.
|
||||
|
||||
License
|
||||
-------
|
||||
Dual-licensed, at the recipient's option:
|
||||
- GNU Lesser General Public License v3.0 (LGPL-3.0-only), or
|
||||
- Creative Commons Attribution-Share Alike 3.0 or 4.0 (CC-BY-SA-3.0 / CC-BY-SA-4.0)
|
||||
|
||||
Full text: https://www.gnu.org/licenses/lgpl-3.0.html
|
||||
https://creativecommons.org/licenses/by-sa/4.0/
|
||||
|
||||
Attribution: "GNOME Project" (https://www.gnome.org), per the upstream copyright file's
|
||||
own attribution note.
|
||||
|
||||
Provenance and modifications
|
||||
-----------------------------
|
||||
All files trace back to `/usr/share/icons/Adwaita/{symbolic,scalable}/...` from the
|
||||
`adwaita-icon-theme` Debian package. Only the `fill` colour was changed (geometry, paths
|
||||
and viewBox are untouched) to produce a light-toolbar and a dark-toolbar variant of each
|
||||
symbolic glyph:
|
||||
|
||||
light/*.svg fill #2e2e2e — used when the app is in the idm-like (light) theme
|
||||
dark/*.svg fill #eeeeec — used when the app is in the dark theme
|
||||
app/velox.svg unmodified — full-colour "folder-download" glyph, used for the
|
||||
window icon, the tray icon, and the .desktop icon
|
||||
|
||||
Adwaita source file -> our name
|
||||
-----------------------------------------------------------------------------
|
||||
symbolic/actions/list-add-symbolic.svg -> add-url.svg
|
||||
symbolic/actions/media-playback-start-symbolic.svg -> resume.svg
|
||||
symbolic/actions/media-playback-pause-symbolic.svg -> pause.svg
|
||||
symbolic/actions/media-playback-stop-symbolic.svg -> stop-all.svg
|
||||
symbolic/actions/edit-delete-symbolic.svg -> delete.svg
|
||||
symbolic/actions/edit-clear-all-symbolic.svg -> delete-completed.svg
|
||||
symbolic/actions/appointment-new-symbolic.svg -> scheduler.svg
|
||||
symbolic/categories/preferences-system-symbolic.svg -> options.svg
|
||||
symbolic/legacy/web-browser-symbolic.svg -> grabber.svg
|
||||
symbolic/actions/document-properties-symbolic.svg -> properties.svg
|
||||
symbolic/actions/insert-link-symbolic.svg -> batch.svg
|
||||
scalable/places/folder-download.svg -> app/velox.svg (unmodified)
|
||||
|
||||
Toolbar/menu -> icon mapping (docs/03-gui-spec.md §1 toolbar order)
|
||||
---------------------------------------------------------------------
|
||||
Add URL -> add-url · Resume -> resume · Pause -> pause · Stop All -> stop-all ·
|
||||
Delete -> delete · Delete Completed -> delete-completed · Scheduler -> scheduler ·
|
||||
Options -> options · Grabber -> grabber. Properties and Batch (menu-only, not on the
|
||||
default toolbar) use properties and batch respectively.
|
||||
@@ -0,0 +1,15 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg height="128px" viewBox="0 0 128 128" width="128px" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<linearGradient id="a" gradientTransform="matrix(0.45451 0 0 0.455522 -1210.292114 616.172607)" gradientUnits="userSpaceOnUse" x1="2689.251953" x2="2918.069824" y1="-1106.802979" y2="-1106.802979">
|
||||
<stop offset="0" stop-color="#62a0ea"/>
|
||||
<stop offset="0.0576991" stop-color="#afd4ff"/>
|
||||
<stop offset="0.122204" stop-color="#62a0ea"/>
|
||||
<stop offset="0.873306" stop-color="#62a0ea"/>
|
||||
<stop offset="0.955997" stop-color="#c0d5ea"/>
|
||||
<stop offset="1" stop-color="#62a0ea"/>
|
||||
</linearGradient>
|
||||
<path d="m 21.976562 12 c -5.527343 0 -9.976562 4.460938 -9.976562 10 v 86.03125 c 0 5.542969 4.449219 10 9.976562 10 h 84.042969 c 5.53125 0 9.980469 -4.457031 9.980469 -10 v -72.085938 c 0 -6.628906 -5.359375 -12 -11.972656 -12 h -46.027344 c -2.453125 0 -4.695312 -1.386718 -5.796875 -3.582031 l -1.503906 -2.992187 c -1.65625 -3.292969 -5.019531 -5.371094 -8.699219 -5.371094 z m 0 0" fill="#438de6"/>
|
||||
<path d="m 65.976562 36 c -2.746093 0 -5.226562 1.101562 -7.027343 2.890625 c -2.273438 2.253906 -5.382813 5.109375 -8.632813 5.109375 h -28.339844 c -5.527343 0 -9.976562 4.460938 -9.976562 10 v 54.03125 c 0 5.542969 4.449219 10 9.976562 10 h 84.042969 c 5.53125 0 9.980469 -4.457031 9.980469 -10 v -62.03125 c 0 -5.539062 -4.449219 -10 -9.980469 -10 z m 0 0" fill="url(#a)"/>
|
||||
<path d="m 65.976562 32 c -2.746093 0 -5.226562 1.101562 -7.027343 2.890625 c -2.273438 2.253906 -5.382813 5.109375 -8.632813 5.109375 h -28.339844 c -5.527343 0 -9.976562 4.460938 -9.976562 10 v 55.976562 c 0 5.539063 4.449219 10 9.976562 10 h 84.042969 c 5.53125 0 9.980469 -4.460937 9.980469 -10 v -63.976562 c 0 -5.539062 -4.449219 -10 -9.980469 -10 z m 0 0" fill="#a4caee"/>
|
||||
<path d="m 64 60 c -1.101562 0 -2 0.898438 -2 2 v 17.171875 l -4.585938 -4.585937 c -0.375 -0.375 -0.882812 -0.585938 -1.414062 -0.585938 s -1.039062 0.210938 -1.414062 0.585938 c -0.78125 0.78125 -0.78125 2.046874 0 2.828124 l 8 8 c 0.78125 0.78125 2.046874 0.78125 2.828124 0 l 8 -8 c 0.78125 -0.78125 0.78125 -2.046874 0 -2.828124 s -2.046874 -0.78125 -2.828124 0 l -4.585938 4.585937 v -17.171875 c 0 -1.101562 -0.898438 -2 -2 -2 z m -12 28 v 4 h 24 v -4 z m 0 0" fill="#438de6"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.3 KiB |
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg height="16px" viewBox="0 0 16 16" width="16px" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="m 7 1 v 6 h -6 v 2 h 6 v 6 h 2 v -6 h 6 v -2 h -6 v -6 z m 0 0" fill="#eeeeec"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 228 B |
@@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg height="15.980469px" viewBox="0 0 16 15.980469" width="16px" xmlns="http://www.w3.org/2000/svg">
|
||||
<g fill="#eeeeec">
|
||||
<path d="m 5 11.976562 v 1 h 0.007812 c -0.003906 0.261719 0.101563 0.515626 0.285157 0.707032 l 2 1.996094 c 0.390625 0.390624 1.023437 0.390624 1.414062 0 l 2 -1.996094 c 0.1875 -0.191406 0.289063 -0.445313 0.289063 -0.707032 h 0.003906 v -1 z m 0 0"/>
|
||||
<path d="m 7 1.972656 c -1.296875 0 -2.40625 0.839844 -2.824219 2 h 7.652344 c -0.417969 -1.160156 -1.53125 -2 -2.828125 -2 z m -5.996094 3 c -0.558594 0 -1.003906 0.445313 -1.003906 1 c 0 0.554688 0.445312 1 1.003906 1 h 4.996094 c 0.554688 0 1 -0.445312 1 -1 c 0 -0.554687 -0.445312 -1 -1 -1 z m 9 0 c -0.558594 0 -1.003906 0.445313 -1.003906 1 c 0 0.554688 0.445312 1 1.003906 1 h 4.996094 c 0.554688 0 1 -0.445312 1 -1 c 0 -0.554687 -0.445312 -1 -1 -1 z m -5.828125 3 c 0.414063 1.160156 1.527344 1.996094 2.824219 1.996094 h 2 c 1.296875 0 2.414062 -0.835938 2.828125 -1.996094 h -2.792969 c -0.011718 0 -0.023437 0.003906 -0.035156 0.003906 h -2 c -0.011719 0 -0.023438 -0.003906 -0.035156 -0.003906 z m 0 0"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg height="16px" viewBox="0 0 16 16" width="16px" xmlns="http://www.w3.org/2000/svg">
|
||||
<g fill="#eeeeec">
|
||||
<path d="m 2 0 c -1.109375 0 -2 0.890625 -2 2 v 6 c 0 1.109375 0.890625 2 2 2 v -8 h 8 c 0 -1.109375 -0.890625 -2 -2 -2 z m 0 0"/>
|
||||
<path d="m 8 6 c -1.109375 0 -2 0.890625 -2 2 v 6 c 0 1.109375 0.890625 2 2 2 h 6 c 1.109375 0 2 -0.890625 2 -2 v -6 c 0 -1.109375 -0.890625 -2 -2 -2 z m 0.003906 2 h 1 h 0.03125 c 0.253906 0.011719 0.507813 0.128906 0.6875 0.3125 l 1.28125 1.28125 l 1.308594 -1.28125 c 0.265625 -0.230469 0.445312 -0.304688 0.6875 -0.3125 h 1 v 1 c 0 0.285156 -0.035156 0.550781 -0.25 0.75 l -1.28125 1.28125 l 1.25 1.25 c 0.1875 0.1875 0.28125 0.453125 0.28125 0.71875 v 1 h -1 c -0.265625 0 -0.53125 -0.09375 -0.71875 -0.28125 l -1.28125 -1.28125 l -1.28125 1.28125 c -0.1875 0.1875 -0.453125 0.28125 -0.71875 0.28125 h -0.996094 v -1 c 0 -0.265625 0.09375 -0.53125 0.28125 -0.71875 l 1.277344 -1.25 l -1.277344 -1.28125 c -0.210937 -0.195312 -0.304687 -0.46875 -0.28125 -0.75 z m 0 0"/>
|
||||
<path d="m 5 3 c -1.109375 0 -2 0.890625 -2 2 v 6 c 0 1.109375 0.890625 2 2 2 v -8 h 8 c 0 -1.109375 -0.890625 -2 -2 -2 z m 0 0"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.2 KiB |
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg height="16px" viewBox="0 0 16 16" width="16px" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="m 8 0 c -4.410156 0 -8 3.589844 -8 8 s 3.589844 8 8 8 s 8 -3.589844 8 -8 s -3.589844 -8 -8 -8 z m 0 2 c 3.332031 0 6 2.667969 6 6 s -2.667969 6 -6 6 s -6 -2.667969 -6 -6 s 2.667969 -6 6 -6 z m -2.03125 2.96875 c -0.265625 0 -0.519531 0.105469 -0.707031 0.292969 c -0.390625 0.390625 -0.390625 1.023437 0 1.414062 l 1.292969 1.292969 l -1.292969 1.292969 c -0.390625 0.390625 -0.390625 1.023437 0 1.414062 s 1.023437 0.390625 1.414062 0 l 1.292969 -1.292969 l 1.292969 1.292969 c 0.390625 0.390625 1.023437 0.390625 1.414062 0 s 0.390625 -1.023437 0 -1.414062 l -1.292969 -1.292969 l 1.292969 -1.292969 c 0.390625 -0.390625 0.390625 -1.023437 0 -1.414062 c -0.1875 -0.1875 -0.441406 -0.292969 -0.707031 -0.292969 s -0.519531 0.105469 -0.707031 0.292969 l -1.292969 1.292969 l -1.292969 -1.292969 c -0.1875 -0.1875 -0.441406 -0.292969 -0.707031 -0.292969 z m 0 0" fill="#eeeeec"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.0 KiB |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16.004"><g color="#000" fill="#eeeeec"><path d="M8 4.846v10.38l-2.344-2.288-1.34 2.734c-.327.74-2.033.145-1.548-.844l1.326-2.839H1.136z" style="marker:none" display="block" overflow="visible"/><path d="M7.156 1.063C3.33 1.522.578 5.02 1.063 8.843c.131 1.035.293 1.383.293 1.383l1.675-1.632A4.992 4.992 0 017.406 3.03a4.992 4.992 0 015.563 4.375c.314 2.608-1.391 5.025-3.969 5.532l.031 2s.521-.106.624-.131c3.416-.834 5.706-4.128 5.283-7.65-.46-3.827-3.955-6.555-7.782-6.095z" style="line-height:normal;-inkscape-font-specification:Sans;text-indent:0;text-align:start;text-decoration-line:none;text-transform:none;marker:none" font-weight="400" font-family="Sans" overflow="visible"/><path d="M4.113 7.815v-.206l-.198.019.05-.393h-.117l-.115.15-.116.056-.165-.093-.017-.206.033-.225.248-.186h.199l.033-.113.247.056.182.225.033-.374.314-.262.116-.28.231-.094.133-.187.297-.056.149-.225h-.446l.28-.13h.199l.28-.094.034-.112-.1-.094-.115-.037.033-.112-.083-.169-.198.075.033-.15-.231-.13-.182.317.017.113-.182.075-.116.243-.05-.225-.313-.13-.05-.169.413-.243.182-.168.017-.206-.1-.056L4.84 4l-.082.206s-.139.027-.174.036C4.13 4.66 3.214 5.562 3 7.266c.008.04.155.268.155.268l.347.206.347.093m3.966-4.3l-.43-.168-.496.056-.611.168-.116.112.38.262v.15l-.149.15.199.392.132-.075.165-.262a5.51 5.51 0 00.727-.28l.199-.505m2.529.342l-.375.094-.219.156v.125l-.375.25.094.344.219-.157.125.157.156.093.094-.28L10 4.5l.063-.094.218-.187h.094l-.094.218v.188c.09-.024.16-.051.25-.063l-.25.188v.125l-.312.219-.281-.063v-.156l-.125.062.062.157h-.219l-.125.218-.156.157-.094.03v.188l.032.157H9.03v.53l.063-.03.094-.219L9.375 6l.031-.094.282-.062.156.187.187.094-.093.187.156-.03.062-.22-.187-.218h.062l.22.156.03.219.157.219.062-.313.094-.031c.096.1.169.231.25.344h.281l.188.125-.094.093-.156.157h-.25l-.344-.094h-.188l-.125.156-.343-.375-.25-.062-.375.062-.157.094V9l.032.031.25-.156.093.094h.281l.125.156-.093.312.187.188V10l.125.25-.093.25c-.01.161 0 .307 0 .469.08.219.143.435.218.656l.063.344v.187h.125l.219-.125h.25l.375-.437-.032-.157.25-.218-.187-.188.219-.187.218-.125.094-.125-.062-.25V9.75l.187-.375.188-.25.25-.563v-.156c-.117.015-.23.023-.344.031-.072.005-.145 0-.219 0a7.442 7.442 0 01-.312-.78l-.157-.188-.093-.313.062-.062.219.25.25.562.156.156-.062.22.156.155.25-.25.312-.218.157-.188v-.219c-.04-.073-.055-.145-.094-.218l-.156.187-.125-.156-.188-.125v-.281l.219.218.219-.03c.101.091.192.207.28.312L13 7.28c0-.174-.2-1.02-.625-1.75S11.22 4.125 11.22 4.125l-.063.094-.218.218-.25-.25h.25l.125-.125-.47-.093-.25-.094z" style="marker:none" overflow="visible" opacity=".3"/></g></svg>
|
||||
|
After Width: | Height: | Size: 2.6 KiB |
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg height="16px" viewBox="0 0 16 16" width="16px" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="m 13.855469 0 l -1.539063 1.4375 c -0.453125 0.421875 -0.53125 1.148438 -0.269531 1.707031 l -5.886719 5.996094 c -0.011718 0 -0.019531 0 -0.03125 0 c -0.257812 -0.128906 -0.550781 -0.183594 -0.839844 -0.148437 c -0.328124 0.046874 -0.632812 0.199218 -0.867187 0.441406 l -3.945313 3.996094 c -0.3906245 0.375 -0.5468745 0.933593 -0.4062495 1.457031 c 0.1406255 0.523437 0.5546875 0.929687 1.0820315 1.058593 c 0.527344 0.132813 1.082031 -0.03125 1.453125 -0.425781 l 3.945312 -3.996093 c 0.472657 -0.453126 0.59375 -1.15625 0.296875 -1.738282 l 5.890625 -5.964844 c 0.558594 0.25 1.273438 0.148438 1.707031 -0.289062 l 1.414063 -1.5625 z m -10.308594 0.0898438 c -0.398437 0 -0.785156 0.0937502 -1.140625 0.2187502 l 1.882812 1.878906 c 0.390626 0.382812 0.390626 1 0 1.386719 l -0.710937 0.707031 c -0.386719 0.386719 -1 0.386719 -1.390625 0 l -1.882812 -1.878906 c -0.125 0.355468 -0.2187505 0.742187 -0.2187505 1.140625 c 0 1.90625 1.5507815 3.453125 3.4609375 3.453125 c 0.402344 0 0.789063 -0.09375 1.144531 -0.21875 l 1.175782 1.171875 h 0.058593 l 2.070313 -2.0625 l -1.203125 -1.203125 c 0.125 -0.359375 0.214843 -0.742188 0.214843 -1.140625 c 0 -1.90625 -1.546874 -3.4531252 -3.460937 -3.4531252 z m 6.550781 7.8906252 l -2.070312 2.066406 c 0.011718 0.027344 0.023437 0.058594 0.03125 0.089844 l 1.144531 1.140625 c -0.125 0.355468 -0.21875 0.742187 -0.21875 1.140625 c 0 1.902343 1.550781 3.449219 3.460937 3.449219 c 0.433594 0 0.855469 -0.101563 1.238282 -0.246094 l -2.007813 -2 c -0.386719 -0.386719 -0.386719 -1.035156 0 -1.417969 l 0.679688 -0.679687 c 0.195312 -0.191407 0.457031 -0.308594 0.710937 -0.308594 s 0.515625 0.117187 0.710938 0.308594 l 1.945312 1.941406 c 0.105469 -0.328125 0.183594 -0.683594 0.183594 -1.046875 c 0 -1.90625 -1.546875 -3.453125 -3.460938 -3.453125 c -0.398437 0 -0.785156 0.09375 -1.140624 0.21875 z m 0 0" fill="#eeeeec"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.0 KiB |
@@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg height="16px" viewBox="0 0 16 16" width="16px" xmlns="http://www.w3.org/2000/svg">
|
||||
<g fill="#eeeeec">
|
||||
<path d="m 3 1 h 3 c 0.550781 0 1 0.449219 1 1 v 12 c 0 0.550781 -0.449219 1 -1 1 h -3 c -0.550781 0 -1 -0.449219 -1 -1 v -12 c 0 -0.550781 0.449219 -1 1 -1 z m 0 0"/>
|
||||
<path d="m 10 1 h 3 c 0.550781 0 1 0.449219 1 1 v 12 c 0 0.550781 -0.449219 1 -1 1 h -3 c -0.550781 0 -1 -0.449219 -1 -1 v -12 c 0 -0.550781 0.449219 -1 1 -1 z m 0 0"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 519 B |
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg height="16px" viewBox="0 0 16 16" width="16px" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="m 4 0 c -1.644531 0 -3 1.355469 -3 3 v 10 c 0 1.644531 1.355469 3 3 3 h 3 c 0.550781 0 1 -0.449219 1 -1 s -0.449219 -1 -1 -1 h -3 c -0.570312 0 -1 -0.429688 -1 -1 v -10 c 0 -0.570312 0.429688 -1 1 -1 h 5.585938 l 3.414062 3.414062 v 6.585938 c 0 0.550781 0.449219 1 1 1 s 1 -0.449219 1 -1 v -7 c 0 -0.265625 -0.105469 -0.519531 -0.292969 -0.707031 l -4 -4 c -0.1875 -0.1875 -0.441406 -0.292969 -0.707031 -0.292969 z m 4.640625 6.167969 c -0.199219 0.003906 -0.402344 0.019531 -0.597656 0.046875 l 1.171875 2.074218 c 0.242187 0.425782 -0.027344 0.914063 -0.539063 1.210938 l -0.925781 0.53125 c -0.515625 0.289062 -1.089844 0.25 -1.332031 -0.175781 l -1.171875 -2.074219 c -0.25 0.308594 -0.476563 0.648438 -0.617188 1.019531 c -0.679687 1.761719 0.285156 3.46875 2.152344 3.804688 c 0.386719 0.074219 0.800781 0.054687 1.191406 0.007812 c 0.03125 -0.007812 1.859375 3.386719 1.859375 3.386719 l 4.167969 0.003906 l -3.199219 -4.957031 c 0.246094 -0.308594 0.445313 -0.652344 0.585938 -1.019531 c 0.679687 -1.765625 -0.285157 -3.472656 -2.148438 -3.808594 c -0.195312 -0.039062 -0.398437 -0.050781 -0.597656 -0.046875 z m 0 0" fill="#eeeeec"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.3 KiB |
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg height="16px" viewBox="0 0 16 16" width="16px" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="m 2 2.5 v 11 c 0 1.5 1.269531 1.492188 1.269531 1.492188 h 0.128907 c 0.246093 0.003906 0.488281 -0.050782 0.699218 -0.171876 l 9.796875 -5.597656 c 0.433594 -0.242187 0.65625 -0.734375 0.65625 -1.226562 c 0 -0.492188 -0.222656 -0.984375 -0.65625 -1.222656 l -9.796875 -5.597657 c -0.210937 -0.121093 -0.453125 -0.175781 -0.699218 -0.175781 h -0.128907 s -1.269531 0 -1.269531 1.5 z m 0 0" fill="#eeeeec"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 554 B |
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg height="16px" viewBox="0 0 16 16" width="16px" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="m 6.5 0 c -3.578125 0 -6.5 2.921875 -6.5 6.5 s 2.921875 6.5 6.5 6.5 c 0.167969 0 0.335938 -0.007812 0.5 -0.019531 v -2.007813 c -0.164062 0.019532 -0.332031 0.027344 -0.5 0.027344 c -2.496094 0 -4.5 -2.003906 -4.5 -4.5 s 2.003906 -4.5 4.5 -4.5 s 4.5 2.003906 4.5 4.5 c 0 0.167969 -0.007812 0.335938 -0.027344 0.5 h 2.007813 c 0.011719 -0.164062 0.019531 -0.332031 0.019531 -0.5 c 0 -3.578125 -2.921875 -6.5 -6.5 -6.5 z m 0 3 c -0.277344 0 -0.5 0.222656 -0.5 0.5 v 2.5 h -1.5 c -0.277344 0 -0.5 0.222656 -0.5 0.5 s 0.222656 0.5 0.5 0.5 h 2 c 0.277344 0 0.5 -0.222656 0.5 -0.5 v -3 c 0 -0.277344 -0.222656 -0.5 -0.5 -0.5 z m 4.5 5 v 3 h -3 v 2 h 3 v 3 h 2 v -3 h 3 v -2 h -3 v -3 z m 0 0" fill="#eeeeec"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 851 B |
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg height="16px" viewBox="0 0 16 16" width="16px" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="m 3.5 2 h 9 c 0.828125 0 1.5 0.671875 1.5 1.5 v 9 c 0 0.828125 -0.671875 1.5 -1.5 1.5 h -9 c -0.828125 0 -1.5 -0.671875 -1.5 -1.5 v -9 c 0 -0.828125 0.671875 -1.5 1.5 -1.5 z m 0 0" fill="#eeeeec"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 345 B |
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg height="16px" viewBox="0 0 16 16" width="16px" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="m 7 1 v 6 h -6 v 2 h 6 v 6 h 2 v -6 h 6 v -2 h -6 v -6 z m 0 0" fill="#2e2e2e"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 228 B |
@@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg height="15.980469px" viewBox="0 0 16 15.980469" width="16px" xmlns="http://www.w3.org/2000/svg">
|
||||
<g fill="#2e2e2e">
|
||||
<path d="m 5 11.976562 v 1 h 0.007812 c -0.003906 0.261719 0.101563 0.515626 0.285157 0.707032 l 2 1.996094 c 0.390625 0.390624 1.023437 0.390624 1.414062 0 l 2 -1.996094 c 0.1875 -0.191406 0.289063 -0.445313 0.289063 -0.707032 h 0.003906 v -1 z m 0 0"/>
|
||||
<path d="m 7 1.972656 c -1.296875 0 -2.40625 0.839844 -2.824219 2 h 7.652344 c -0.417969 -1.160156 -1.53125 -2 -2.828125 -2 z m -5.996094 3 c -0.558594 0 -1.003906 0.445313 -1.003906 1 c 0 0.554688 0.445312 1 1.003906 1 h 4.996094 c 0.554688 0 1 -0.445312 1 -1 c 0 -0.554687 -0.445312 -1 -1 -1 z m 9 0 c -0.558594 0 -1.003906 0.445313 -1.003906 1 c 0 0.554688 0.445312 1 1.003906 1 h 4.996094 c 0.554688 0 1 -0.445312 1 -1 c 0 -0.554687 -0.445312 -1 -1 -1 z m -5.828125 3 c 0.414063 1.160156 1.527344 1.996094 2.824219 1.996094 h 2 c 1.296875 0 2.414062 -0.835938 2.828125 -1.996094 h -2.792969 c -0.011718 0 -0.023437 0.003906 -0.035156 0.003906 h -2 c -0.011719 0 -0.023438 -0.003906 -0.035156 -0.003906 z m 0 0"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg height="16px" viewBox="0 0 16 16" width="16px" xmlns="http://www.w3.org/2000/svg">
|
||||
<g fill="#2e2e2e">
|
||||
<path d="m 2 0 c -1.109375 0 -2 0.890625 -2 2 v 6 c 0 1.109375 0.890625 2 2 2 v -8 h 8 c 0 -1.109375 -0.890625 -2 -2 -2 z m 0 0"/>
|
||||
<path d="m 8 6 c -1.109375 0 -2 0.890625 -2 2 v 6 c 0 1.109375 0.890625 2 2 2 h 6 c 1.109375 0 2 -0.890625 2 -2 v -6 c 0 -1.109375 -0.890625 -2 -2 -2 z m 0.003906 2 h 1 h 0.03125 c 0.253906 0.011719 0.507813 0.128906 0.6875 0.3125 l 1.28125 1.28125 l 1.308594 -1.28125 c 0.265625 -0.230469 0.445312 -0.304688 0.6875 -0.3125 h 1 v 1 c 0 0.285156 -0.035156 0.550781 -0.25 0.75 l -1.28125 1.28125 l 1.25 1.25 c 0.1875 0.1875 0.28125 0.453125 0.28125 0.71875 v 1 h -1 c -0.265625 0 -0.53125 -0.09375 -0.71875 -0.28125 l -1.28125 -1.28125 l -1.28125 1.28125 c -0.1875 0.1875 -0.453125 0.28125 -0.71875 0.28125 h -0.996094 v -1 c 0 -0.265625 0.09375 -0.53125 0.28125 -0.71875 l 1.277344 -1.25 l -1.277344 -1.28125 c -0.210937 -0.195312 -0.304687 -0.46875 -0.28125 -0.75 z m 0 0"/>
|
||||
<path d="m 5 3 c -1.109375 0 -2 0.890625 -2 2 v 6 c 0 1.109375 0.890625 2 2 2 v -8 h 8 c 0 -1.109375 -0.890625 -2 -2 -2 z m 0 0"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.2 KiB |
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg height="16px" viewBox="0 0 16 16" width="16px" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="m 8 0 c -4.410156 0 -8 3.589844 -8 8 s 3.589844 8 8 8 s 8 -3.589844 8 -8 s -3.589844 -8 -8 -8 z m 0 2 c 3.332031 0 6 2.667969 6 6 s -2.667969 6 -6 6 s -6 -2.667969 -6 -6 s 2.667969 -6 6 -6 z m -2.03125 2.96875 c -0.265625 0 -0.519531 0.105469 -0.707031 0.292969 c -0.390625 0.390625 -0.390625 1.023437 0 1.414062 l 1.292969 1.292969 l -1.292969 1.292969 c -0.390625 0.390625 -0.390625 1.023437 0 1.414062 s 1.023437 0.390625 1.414062 0 l 1.292969 -1.292969 l 1.292969 1.292969 c 0.390625 0.390625 1.023437 0.390625 1.414062 0 s 0.390625 -1.023437 0 -1.414062 l -1.292969 -1.292969 l 1.292969 -1.292969 c 0.390625 -0.390625 0.390625 -1.023437 0 -1.414062 c -0.1875 -0.1875 -0.441406 -0.292969 -0.707031 -0.292969 s -0.519531 0.105469 -0.707031 0.292969 l -1.292969 1.292969 l -1.292969 -1.292969 c -0.1875 -0.1875 -0.441406 -0.292969 -0.707031 -0.292969 z m 0 0" fill="#2e2e2e"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.0 KiB |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16.004"><g color="#000" fill="#2e2e2e"><path d="M8 4.846v10.38l-2.344-2.288-1.34 2.734c-.327.74-2.033.145-1.548-.844l1.326-2.839H1.136z" style="marker:none" display="block" overflow="visible"/><path d="M7.156 1.063C3.33 1.522.578 5.02 1.063 8.843c.131 1.035.293 1.383.293 1.383l1.675-1.632A4.992 4.992 0 017.406 3.03a4.992 4.992 0 015.563 4.375c.314 2.608-1.391 5.025-3.969 5.532l.031 2s.521-.106.624-.131c3.416-.834 5.706-4.128 5.283-7.65-.46-3.827-3.955-6.555-7.782-6.095z" style="line-height:normal;-inkscape-font-specification:Sans;text-indent:0;text-align:start;text-decoration-line:none;text-transform:none;marker:none" font-weight="400" font-family="Sans" overflow="visible"/><path d="M4.113 7.815v-.206l-.198.019.05-.393h-.117l-.115.15-.116.056-.165-.093-.017-.206.033-.225.248-.186h.199l.033-.113.247.056.182.225.033-.374.314-.262.116-.28.231-.094.133-.187.297-.056.149-.225h-.446l.28-.13h.199l.28-.094.034-.112-.1-.094-.115-.037.033-.112-.083-.169-.198.075.033-.15-.231-.13-.182.317.017.113-.182.075-.116.243-.05-.225-.313-.13-.05-.169.413-.243.182-.168.017-.206-.1-.056L4.84 4l-.082.206s-.139.027-.174.036C4.13 4.66 3.214 5.562 3 7.266c.008.04.155.268.155.268l.347.206.347.093m3.966-4.3l-.43-.168-.496.056-.611.168-.116.112.38.262v.15l-.149.15.199.392.132-.075.165-.262a5.51 5.51 0 00.727-.28l.199-.505m2.529.342l-.375.094-.219.156v.125l-.375.25.094.344.219-.157.125.157.156.093.094-.28L10 4.5l.063-.094.218-.187h.094l-.094.218v.188c.09-.024.16-.051.25-.063l-.25.188v.125l-.312.219-.281-.063v-.156l-.125.062.062.157h-.219l-.125.218-.156.157-.094.03v.188l.032.157H9.03v.53l.063-.03.094-.219L9.375 6l.031-.094.282-.062.156.187.187.094-.093.187.156-.03.062-.22-.187-.218h.062l.22.156.03.219.157.219.062-.313.094-.031c.096.1.169.231.25.344h.281l.188.125-.094.093-.156.157h-.25l-.344-.094h-.188l-.125.156-.343-.375-.25-.062-.375.062-.157.094V9l.032.031.25-.156.093.094h.281l.125.156-.093.312.187.188V10l.125.25-.093.25c-.01.161 0 .307 0 .469.08.219.143.435.218.656l.063.344v.187h.125l.219-.125h.25l.375-.437-.032-.157.25-.218-.187-.188.219-.187.218-.125.094-.125-.062-.25V9.75l.187-.375.188-.25.25-.563v-.156c-.117.015-.23.023-.344.031-.072.005-.145 0-.219 0a7.442 7.442 0 01-.312-.78l-.157-.188-.093-.313.062-.062.219.25.25.562.156.156-.062.22.156.155.25-.25.312-.218.157-.188v-.219c-.04-.073-.055-.145-.094-.218l-.156.187-.125-.156-.188-.125v-.281l.219.218.219-.03c.101.091.192.207.28.312L13 7.28c0-.174-.2-1.02-.625-1.75S11.22 4.125 11.22 4.125l-.063.094-.218.218-.25-.25h.25l.125-.125-.47-.093-.25-.094z" style="marker:none" overflow="visible" opacity=".3"/></g></svg>
|
||||
|
After Width: | Height: | Size: 2.6 KiB |
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg height="16px" viewBox="0 0 16 16" width="16px" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="m 13.855469 0 l -1.539063 1.4375 c -0.453125 0.421875 -0.53125 1.148438 -0.269531 1.707031 l -5.886719 5.996094 c -0.011718 0 -0.019531 0 -0.03125 0 c -0.257812 -0.128906 -0.550781 -0.183594 -0.839844 -0.148437 c -0.328124 0.046874 -0.632812 0.199218 -0.867187 0.441406 l -3.945313 3.996094 c -0.3906245 0.375 -0.5468745 0.933593 -0.4062495 1.457031 c 0.1406255 0.523437 0.5546875 0.929687 1.0820315 1.058593 c 0.527344 0.132813 1.082031 -0.03125 1.453125 -0.425781 l 3.945312 -3.996093 c 0.472657 -0.453126 0.59375 -1.15625 0.296875 -1.738282 l 5.890625 -5.964844 c 0.558594 0.25 1.273438 0.148438 1.707031 -0.289062 l 1.414063 -1.5625 z m -10.308594 0.0898438 c -0.398437 0 -0.785156 0.0937502 -1.140625 0.2187502 l 1.882812 1.878906 c 0.390626 0.382812 0.390626 1 0 1.386719 l -0.710937 0.707031 c -0.386719 0.386719 -1 0.386719 -1.390625 0 l -1.882812 -1.878906 c -0.125 0.355468 -0.2187505 0.742187 -0.2187505 1.140625 c 0 1.90625 1.5507815 3.453125 3.4609375 3.453125 c 0.402344 0 0.789063 -0.09375 1.144531 -0.21875 l 1.175782 1.171875 h 0.058593 l 2.070313 -2.0625 l -1.203125 -1.203125 c 0.125 -0.359375 0.214843 -0.742188 0.214843 -1.140625 c 0 -1.90625 -1.546874 -3.4531252 -3.460937 -3.4531252 z m 6.550781 7.8906252 l -2.070312 2.066406 c 0.011718 0.027344 0.023437 0.058594 0.03125 0.089844 l 1.144531 1.140625 c -0.125 0.355468 -0.21875 0.742187 -0.21875 1.140625 c 0 1.902343 1.550781 3.449219 3.460937 3.449219 c 0.433594 0 0.855469 -0.101563 1.238282 -0.246094 l -2.007813 -2 c -0.386719 -0.386719 -0.386719 -1.035156 0 -1.417969 l 0.679688 -0.679687 c 0.195312 -0.191407 0.457031 -0.308594 0.710937 -0.308594 s 0.515625 0.117187 0.710938 0.308594 l 1.945312 1.941406 c 0.105469 -0.328125 0.183594 -0.683594 0.183594 -1.046875 c 0 -1.90625 -1.546875 -3.453125 -3.460938 -3.453125 c -0.398437 0 -0.785156 0.09375 -1.140624 0.21875 z m 0 0" fill="#2e2e2e"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.0 KiB |
@@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg height="16px" viewBox="0 0 16 16" width="16px" xmlns="http://www.w3.org/2000/svg">
|
||||
<g fill="#2e2e2e">
|
||||
<path d="m 3 1 h 3 c 0.550781 0 1 0.449219 1 1 v 12 c 0 0.550781 -0.449219 1 -1 1 h -3 c -0.550781 0 -1 -0.449219 -1 -1 v -12 c 0 -0.550781 0.449219 -1 1 -1 z m 0 0"/>
|
||||
<path d="m 10 1 h 3 c 0.550781 0 1 0.449219 1 1 v 12 c 0 0.550781 -0.449219 1 -1 1 h -3 c -0.550781 0 -1 -0.449219 -1 -1 v -12 c 0 -0.550781 0.449219 -1 1 -1 z m 0 0"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 519 B |
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg height="16px" viewBox="0 0 16 16" width="16px" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="m 4 0 c -1.644531 0 -3 1.355469 -3 3 v 10 c 0 1.644531 1.355469 3 3 3 h 3 c 0.550781 0 1 -0.449219 1 -1 s -0.449219 -1 -1 -1 h -3 c -0.570312 0 -1 -0.429688 -1 -1 v -10 c 0 -0.570312 0.429688 -1 1 -1 h 5.585938 l 3.414062 3.414062 v 6.585938 c 0 0.550781 0.449219 1 1 1 s 1 -0.449219 1 -1 v -7 c 0 -0.265625 -0.105469 -0.519531 -0.292969 -0.707031 l -4 -4 c -0.1875 -0.1875 -0.441406 -0.292969 -0.707031 -0.292969 z m 4.640625 6.167969 c -0.199219 0.003906 -0.402344 0.019531 -0.597656 0.046875 l 1.171875 2.074218 c 0.242187 0.425782 -0.027344 0.914063 -0.539063 1.210938 l -0.925781 0.53125 c -0.515625 0.289062 -1.089844 0.25 -1.332031 -0.175781 l -1.171875 -2.074219 c -0.25 0.308594 -0.476563 0.648438 -0.617188 1.019531 c -0.679687 1.761719 0.285156 3.46875 2.152344 3.804688 c 0.386719 0.074219 0.800781 0.054687 1.191406 0.007812 c 0.03125 -0.007812 1.859375 3.386719 1.859375 3.386719 l 4.167969 0.003906 l -3.199219 -4.957031 c 0.246094 -0.308594 0.445313 -0.652344 0.585938 -1.019531 c 0.679687 -1.765625 -0.285157 -3.472656 -2.148438 -3.808594 c -0.195312 -0.039062 -0.398437 -0.050781 -0.597656 -0.046875 z m 0 0" fill="#2e2e2e"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.3 KiB |
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg height="16px" viewBox="0 0 16 16" width="16px" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="m 2 2.5 v 11 c 0 1.5 1.269531 1.492188 1.269531 1.492188 h 0.128907 c 0.246093 0.003906 0.488281 -0.050782 0.699218 -0.171876 l 9.796875 -5.597656 c 0.433594 -0.242187 0.65625 -0.734375 0.65625 -1.226562 c 0 -0.492188 -0.222656 -0.984375 -0.65625 -1.222656 l -9.796875 -5.597657 c -0.210937 -0.121093 -0.453125 -0.175781 -0.699218 -0.175781 h -0.128907 s -1.269531 0 -1.269531 1.5 z m 0 0" fill="#2e2e2e"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 554 B |
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg height="16px" viewBox="0 0 16 16" width="16px" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="m 6.5 0 c -3.578125 0 -6.5 2.921875 -6.5 6.5 s 2.921875 6.5 6.5 6.5 c 0.167969 0 0.335938 -0.007812 0.5 -0.019531 v -2.007813 c -0.164062 0.019532 -0.332031 0.027344 -0.5 0.027344 c -2.496094 0 -4.5 -2.003906 -4.5 -4.5 s 2.003906 -4.5 4.5 -4.5 s 4.5 2.003906 4.5 4.5 c 0 0.167969 -0.007812 0.335938 -0.027344 0.5 h 2.007813 c 0.011719 -0.164062 0.019531 -0.332031 0.019531 -0.5 c 0 -3.578125 -2.921875 -6.5 -6.5 -6.5 z m 0 3 c -0.277344 0 -0.5 0.222656 -0.5 0.5 v 2.5 h -1.5 c -0.277344 0 -0.5 0.222656 -0.5 0.5 s 0.222656 0.5 0.5 0.5 h 2 c 0.277344 0 0.5 -0.222656 0.5 -0.5 v -3 c 0 -0.277344 -0.222656 -0.5 -0.5 -0.5 z m 4.5 5 v 3 h -3 v 2 h 3 v 3 h 2 v -3 h 3 v -2 h -3 v -3 z m 0 0" fill="#2e2e2e"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 851 B |
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg height="16px" viewBox="0 0 16 16" width="16px" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="m 3.5 2 h 9 c 0.828125 0 1.5 0.671875 1.5 1.5 v 9 c 0 0.828125 -0.671875 1.5 -1.5 1.5 h -9 c -0.828125 0 -1.5 -0.671875 -1.5 -1.5 v -9 c 0 -0.828125 0.671875 -1.5 1.5 -1.5 z m 0 0" fill="#2e2e2e"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 345 B |
@@ -37,6 +37,7 @@
|
||||
#include "rpc/RpcClient.hpp"
|
||||
#include "tray/TrayIcon.hpp"
|
||||
#include "util/Format.hpp"
|
||||
#include "util/IconTheme.hpp"
|
||||
#include "util/Theme.hpp"
|
||||
#include "widgets/DropTargetWidget.hpp"
|
||||
#include "widgets/ProgressDelegate.hpp"
|
||||
@@ -67,8 +68,12 @@ MainWindow::MainWindow(rpc::RpcClient *client, QWidget *parent)
|
||||
offlineBanner_(new QWidget(this)),
|
||||
countsTimer_(new QTimer(this)) {
|
||||
setWindowTitle(tr("Velox Download Manager"));
|
||||
setWindowIcon(IconTheme::appIcon());
|
||||
resize(1040, 600);
|
||||
|
||||
iconTheme_ = new IconTheme(this);
|
||||
connect(iconTheme_, &IconTheme::iconsChanged, this, &MainWindow::applyIcons);
|
||||
|
||||
proxy_->setSourceModel(model_);
|
||||
proxy_->setSortCaseSensitivity(Qt::CaseInsensitive);
|
||||
|
||||
@@ -116,6 +121,7 @@ MainWindow::MainWindow(rpc::RpcClient *client, QWidget *parent)
|
||||
setCentralWidget(central);
|
||||
|
||||
buildActions();
|
||||
applyIcons();
|
||||
buildMenus();
|
||||
buildToolBar();
|
||||
buildTray();
|
||||
@@ -205,6 +211,24 @@ void MainWindow::buildActions() {
|
||||
connect(actShow_, &QAction::triggered, this, &MainWindow::showAndRaise);
|
||||
}
|
||||
|
||||
// gui/resources/icons/LICENSE: toolbar-position -> icon-stem mapping. Re-run on
|
||||
// IconTheme::iconsChanged so a live light/dark switch swaps every action's glyph, the
|
||||
// same way ThemeManager swaps the QSS on the same signal.
|
||||
void MainWindow::applyIcons() {
|
||||
actAddUrl_->setIcon(IconTheme::icon(QStringLiteral("add-url")));
|
||||
actResume_->setIcon(IconTheme::icon(QStringLiteral("resume")));
|
||||
actPause_->setIcon(IconTheme::icon(QStringLiteral("pause")));
|
||||
actStop_->setIcon(IconTheme::icon(QStringLiteral("stop-all")));
|
||||
actRemove_->setIcon(IconTheme::icon(QStringLiteral("delete")));
|
||||
actResumeAll_->setIcon(IconTheme::icon(QStringLiteral("resume")));
|
||||
actPauseAll_->setIcon(IconTheme::icon(QStringLiteral("pause")));
|
||||
actProperties_->setIcon(IconTheme::icon(QStringLiteral("properties")));
|
||||
actOptions_->setIcon(IconTheme::icon(QStringLiteral("options")));
|
||||
actScheduler_->setIcon(IconTheme::icon(QStringLiteral("scheduler")));
|
||||
actBatch_->setIcon(IconTheme::icon(QStringLiteral("batch")));
|
||||
actGrabber_->setIcon(IconTheme::icon(QStringLiteral("grabber")));
|
||||
}
|
||||
|
||||
void MainWindow::buildMenus() {
|
||||
QMenu *tasks = menuBar()->addMenu(tr("&Tasks"));
|
||||
tasks->addAction(actAddUrl_);
|
||||
|
||||
@@ -27,6 +27,7 @@ class DownloadTableModel;
|
||||
class CategoryPanel;
|
||||
class TrayIcon;
|
||||
class DropTargetWidget;
|
||||
class IconTheme;
|
||||
#ifdef VELOX_GUI_HAVE_DBUS
|
||||
class GlobalShortcut;
|
||||
#endif
|
||||
@@ -69,6 +70,7 @@ class MainWindow : public QMainWindow {
|
||||
void showAndRaise();
|
||||
void onSettingsChanged(const QJsonObject ¶ms);
|
||||
void onUrlDropped(const QString &url);
|
||||
void applyIcons();
|
||||
|
||||
private:
|
||||
void buildActions();
|
||||
@@ -107,6 +109,7 @@ class MainWindow : public QMainWindow {
|
||||
|
||||
QJsonArray categoriesCache_;
|
||||
QJsonArray queuesCache_;
|
||||
IconTheme *iconTheme_ = nullptr;
|
||||
TrayIcon *trayIcon_ = nullptr;
|
||||
DropTargetWidget *dropTarget_ = nullptr;
|
||||
bool minimizeToTrayEnabled_ = false;
|
||||
|
||||
@@ -1,29 +1,26 @@
|
||||
#include "tray/TrayIcon.hpp"
|
||||
|
||||
#include <QAction>
|
||||
#include <QApplication>
|
||||
#include <QCoreApplication>
|
||||
#include <QJsonObject>
|
||||
#include <QMenu>
|
||||
#include <QMessageBox>
|
||||
#include <QStyle>
|
||||
#include <QWidget>
|
||||
|
||||
#include "dialogs/SpeedLimiterDialog.hpp"
|
||||
#include "rpc/Protocol.hpp"
|
||||
#include "rpc/RpcClient.hpp"
|
||||
#include "util/IconTheme.hpp"
|
||||
|
||||
namespace velox::gui {
|
||||
|
||||
TrayIcon::TrayIcon(rpc::RpcClient *client, QAction *actShow, QAction *actAddUrl,
|
||||
QAction *actResumeAll, QAction *actPauseAll, QWidget *parent)
|
||||
: QSystemTrayIcon(parent), client_(client) {
|
||||
// No icon set has been designed yet (that's its own build-order step); a standard
|
||||
// style icon is a placeholder, not a shipped asset.
|
||||
if (auto *w = qobject_cast<QWidget *>(parent)) {
|
||||
setIcon(w->style()->standardIcon(QStyle::SP_ArrowDown));
|
||||
} else {
|
||||
setIcon(qApp->style()->standardIcon(QStyle::SP_ArrowDown));
|
||||
}
|
||||
// gui/resources/icons/LICENSE — same full-colour glyph as the window icon; the tray
|
||||
// icon is fixed (not theme-picked) since Adwaita's folder-download already reads
|
||||
// fine against both light and dark shell backgrounds.
|
||||
setIcon(IconTheme::appIcon());
|
||||
setToolTip(QCoreApplication::translate("velox::gui::TrayIcon", "Velox Download Manager"));
|
||||
|
||||
auto *menu = new QMenu(parent);
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
#include "util/IconTheme.hpp"
|
||||
|
||||
#include <QGuiApplication>
|
||||
#include <QStyleHints>
|
||||
|
||||
namespace velox::gui {
|
||||
|
||||
IconTheme::IconTheme(QObject *parent) : QObject(parent) {
|
||||
connect(QGuiApplication::styleHints(), &QStyleHints::colorSchemeChanged, this,
|
||||
&IconTheme::onColorSchemeChanged);
|
||||
}
|
||||
|
||||
QIcon IconTheme::icon(const QString &name) {
|
||||
const bool dark = QGuiApplication::styleHints()->colorScheme() == Qt::ColorScheme::Dark;
|
||||
return QIcon(QStringLiteral(":/icons/%1/%2.svg")
|
||||
.arg(dark ? QStringLiteral("dark") : QStringLiteral("light"), name));
|
||||
}
|
||||
|
||||
QIcon IconTheme::appIcon() {
|
||||
return QIcon(QStringLiteral(":/icons/app/velox.svg"));
|
||||
}
|
||||
|
||||
void IconTheme::onColorSchemeChanged() {
|
||||
emit iconsChanged();
|
||||
}
|
||||
|
||||
} // namespace velox::gui
|
||||
@@ -0,0 +1,34 @@
|
||||
// Resolves action icons to the light/dark SVG variant matching the current
|
||||
// QStyleHints::colorScheme(), same trigger ThemeManager uses for the QSS skin. Lane GUI.
|
||||
// docs/03-gui-spec.md §1/§7, gui/resources/icons/LICENSE.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QIcon>
|
||||
#include <QObject>
|
||||
#include <QString>
|
||||
|
||||
namespace velox::gui {
|
||||
|
||||
class IconTheme : public QObject {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit IconTheme(QObject *parent = nullptr);
|
||||
|
||||
/// `name` is a stem under gui/resources/icons/{light,dark}/, e.g. "add-url".
|
||||
static QIcon icon(const QString &name);
|
||||
|
||||
/// The fixed full-colour glyph (window icon, tray icon) — same in both themes.
|
||||
static QIcon appIcon();
|
||||
|
||||
signals:
|
||||
/// Fired on a live light/dark switch; callers holding an icon(name) result should
|
||||
/// re-fetch and re-apply it.
|
||||
void iconsChanged();
|
||||
|
||||
private slots:
|
||||
void onColorSchemeChanged();
|
||||
};
|
||||
|
||||
} // namespace velox::gui
|
||||