mirror of https://github.com/cutefishos/launcher
You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
148 lines
4.5 KiB
C++
148 lines
4.5 KiB
C++
// SPDX-FileCopyrightText: 2026 Reion Wong <reionwong@gmail.com>
|
|
// SPDX-License-Identifier: BSD-3-Clause
|
|
|
|
#include "backgrounds.h"
|
|
|
|
#include <cerrno>
|
|
#include <filesystem>
|
|
#include <optional>
|
|
#include <string_view>
|
|
#include <pwd.h>
|
|
#include <sys/stat.h>
|
|
#include <unistd.h>
|
|
|
|
#include "base/file.h"
|
|
#include "base/settings.h"
|
|
|
|
using namespace cutefish;
|
|
|
|
namespace dm {
|
|
|
|
static constexpr std::string_view kSettingsSuffix = ".conf";
|
|
static constexpr std::string_view kPictureSuffix = ".picture";
|
|
|
|
static bool userExists(const std::string &user)
|
|
{
|
|
return getpwnam(user.c_str()) != nullptr;
|
|
}
|
|
|
|
static bool isColor(const std::string &color)
|
|
{
|
|
if (color.size() != 4 && color.size() != 7 && color.size() != 9)
|
|
return false;
|
|
|
|
if (color.front() != '#')
|
|
return false;
|
|
|
|
return color.find_first_not_of("0123456789abcdefABCDEF", 1) == std::string::npos;
|
|
}
|
|
|
|
// The whole file from its start, or nothing for anything but a regular file
|
|
// within the limit. The descriptor is the caller's, so its offset is left alone.
|
|
static std::optional<std::string> readPicture(int fd)
|
|
{
|
|
struct stat info = {};
|
|
if (fstat(fd, &info) != 0 || !S_ISREG(info.st_mode) || info.st_size > Backgrounds::kMaximumPictureSize)
|
|
return std::nullopt;
|
|
|
|
std::string bytes;
|
|
bytes.resize(size_t(info.st_size));
|
|
size_t done = 0;
|
|
while (done < bytes.size()) {
|
|
const ssize_t count = pread(fd, bytes.data() + done, bytes.size() - done, off_t(done));
|
|
if (count < 0 && errno == EINTR)
|
|
continue;
|
|
if (count <= 0)
|
|
return std::nullopt;
|
|
done += size_t(count);
|
|
}
|
|
|
|
return bytes.empty() ? std::nullopt : std::optional<std::string>(std::move(bytes));
|
|
}
|
|
|
|
Backgrounds::Backgrounds(std::string directory)
|
|
: m_directory(std::move(directory))
|
|
{
|
|
}
|
|
|
|
std::string Backgrounds::settingsPath(const std::string &user) const
|
|
{
|
|
return m_directory + '/' + user + std::string(kSettingsSuffix);
|
|
}
|
|
|
|
std::string Backgrounds::picturePath(const std::string &user) const
|
|
{
|
|
return m_directory + '/' + user + std::string(kPictureSuffix);
|
|
}
|
|
|
|
void Backgrounds::removeStale()
|
|
{
|
|
std::error_code error;
|
|
for (const auto &entry : std::filesystem::directory_iterator(m_directory, error)) {
|
|
const std::string name = entry.path().filename().string();
|
|
std::string user;
|
|
if (name.ends_with(kSettingsSuffix))
|
|
user = name.substr(0, name.size() - kSettingsSuffix.size());
|
|
else if (name.ends_with(kPictureSuffix))
|
|
user = name.substr(0, name.size() - kPictureSuffix.size());
|
|
|
|
if (user.empty() || !userExists(user))
|
|
std::filesystem::remove(entry.path(), error);
|
|
}
|
|
}
|
|
|
|
void Backgrounds::setPicture(const std::string &user, base::dbus::UnixFd file, std::function<void(bool)> done)
|
|
{
|
|
const uint64_t request = ++m_lastRequest;
|
|
m_latest[user] = request;
|
|
|
|
const std::string pending = picturePath(user) + '.' + std::to_string(request);
|
|
m_copies[request] = base::ThreadPool::global().run(
|
|
[file, pending] {
|
|
const std::optional<std::string> bytes = readPicture(file.fd());
|
|
return bytes && base::file::writeAtomically(pending, *bytes);
|
|
},
|
|
[this, user, request, pending, done = std::move(done)](bool copied) {
|
|
m_copies.erase(request);
|
|
if (!copied) {
|
|
unlink(pending.c_str());
|
|
done(false);
|
|
return;
|
|
}
|
|
|
|
// A later request took over; it has its own answer.
|
|
if (m_latest[user] != request) {
|
|
unlink(pending.c_str());
|
|
done(true);
|
|
return;
|
|
}
|
|
|
|
const std::string picture = picturePath(user);
|
|
if (rename(pending.c_str(), picture.c_str()) != 0) {
|
|
unlink(pending.c_str());
|
|
done(false);
|
|
return;
|
|
}
|
|
|
|
base::Settings settings(settingsPath(user));
|
|
settings.setValue("Appearance/BackgroundType", 0);
|
|
settings.setValue("Appearance/Wallpaper", picture);
|
|
done(settings.sync());
|
|
});
|
|
}
|
|
|
|
bool Backgrounds::setColor(const std::string &user, const std::string &color)
|
|
{
|
|
if (!isColor(color))
|
|
return false;
|
|
|
|
m_latest[user] = ++m_lastRequest;
|
|
|
|
base::Settings settings(settingsPath(user));
|
|
settings.setValue("Appearance/BackgroundType", 1);
|
|
settings.setValue("Appearance/BackgroundColor", color);
|
|
return settings.sync();
|
|
}
|
|
|
|
} // namespace dm
|