Skip to content

Commit

Permalink
add donation nag
Browse files Browse the repository at this point in the history
  • Loading branch information
vaxerski committed Jan 7, 2025
1 parent b9f110e commit 0fd023d
Show file tree
Hide file tree
Showing 9 changed files with 267 additions and 114 deletions.
3 changes: 2 additions & 1 deletion src/Compositor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
#endif
#include <ranges>
#include "helpers/varlist/VarList.hpp"
#include "helpers/fs/FsUtils.hpp"
#include "protocols/FractionalScale.hpp"
#include "protocols/PointerConstraints.hpp"
#include "protocols/LayerShell.hpp"
Expand Down Expand Up @@ -2629,7 +2630,7 @@ void CCompositor::performUserChecks() {
}

if (!*PNOCHECKQTUTILS) {
if (!executableExistsInPath("hyprland-dialog")) {
if (!NFsUtils::executableExistsInPath("hyprland-dialog")) {
g_pHyprNotificationOverlay->addNotification(
"Your system does not have hyprland-qtutils installed. This is a runtime dependency for some dialogs. Consider installing it.", CHyprColor{}, 15000, ICON_WARNING);
}
Expand Down
27 changes: 0 additions & 27 deletions src/helpers/MiscFunctions.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -911,30 +911,3 @@ float stringToPercentage(const std::string& VALUE, const float REL) {
else
return std::stof(VALUE);
}

bool executableExistsInPath(const std::string& exe) {
if (!getenv("PATH"))
return false;

static CVarList paths(getenv("PATH"), 0, ':', true);

for (auto& p : paths) {
std::string path = p + std::string{"/"} + exe;
std::error_code ec;
if (!std::filesystem::exists(path, ec) || ec)
continue;

if (!std::filesystem::is_regular_file(path, ec) || ec)
continue;

auto stat = std::filesystem::status(path, ec);
if (ec)
continue;

auto perms = stat.permissions();

return std::filesystem::perms::none != (perms & std::filesystem::perms::others_exec);
}

return false;
}
1 change: 0 additions & 1 deletion src/helpers/MiscFunctions.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,6 @@ bool envEnabled(const std::string& env);
int allocateSHMFile(size_t len);
bool allocateSHMFilePair(size_t size, int* rw_fd_ptr, int* ro_fd_ptr);
float stringToPercentage(const std::string& VALUE, const float REL);
bool executableExistsInPath(const std::string& exe);

template <typename... Args>
[[deprecated("use std::format instead")]] std::string getFormat(std::format_string<Args...> fmt, Args&&... args) {
Expand Down
107 changes: 107 additions & 0 deletions src/helpers/fs/FsUtils.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
#include "FsUtils.hpp"
#include "../../debug/Log.hpp"

#include <cstdlib>
#include <filesystem>

#include <hyprutils/string/String.hpp>
#include <hyprutils/string/VarList.hpp>
using namespace Hyprutils::String;

std::optional<std::string> NFsUtils::getDataHome() {
const auto DATA_HOME = getenv("XDG_DATA_HOME");

std::string dataRoot;

if (!DATA_HOME) {
const auto HOME = getenv("HOME");

if (!HOME) {
Debug::log(ERR, "FsUtils::getDataHome: can't get data home: no $HOME or $XDG_DATA_HOME");
return std::nullopt;
}

dataRoot = HOME + std::string{"/.local/share/"};
} else
dataRoot = DATA_HOME + std::string{"/"};

std::error_code ec;
if (!std::filesystem::exists(dataRoot, ec) || ec) {
Debug::log(ERR, "FsUtils::getDataHome: can't get data home: inaccessible / missing");
return std::nullopt;
}

dataRoot += "hyprland/";

if (!std::filesystem::exists(dataRoot, ec) || ec) {
Debug::log(LOG, "FsUtils::getDataHome: no hyprland data home, creating.");
std::filesystem::create_directory(dataRoot, ec);
if (ec) {
Debug::log(ERR, "FsUtils::getDataHome: can't create new data home for hyprland");
return std::nullopt;
}
std::filesystem::permissions(dataRoot, std::filesystem::perms::owner_read | std::filesystem::perms::owner_write | std::filesystem::perms::owner_exec, ec);
if (ec)
Debug::log(WARN, "FsUtils::getDataHome: couldn't set perms on hyprland data store. Proceeding anyways.");
}

if (!std::filesystem::exists(dataRoot, ec) || ec) {
Debug::log(ERR, "FsUtils::getDataHome: no hyprland data home, failed to create.");
return std::nullopt;
}

return dataRoot;
}

std::optional<std::string> NFsUtils::readFileAsString(const std::string& path) {
std::error_code ec;

if (!std::filesystem::exists(path, ec) || ec)
return std::nullopt;

std::ifstream file(path);
if (!file.good())
return std::nullopt;

return trim(std::string((std::istreambuf_iterator<char>(file)), (std::istreambuf_iterator<char>())));
}

bool NFsUtils::writeToFile(const std::string& path, const std::string& content) {
std::ofstream of(path, std::ios::trunc);
if (!of.good()) {
Debug::log(ERR, "CVersionKeeperManager: couldn't open an ofstream for writing the version file.");
return false;
}

of << content;
of.close();

return true;
}

bool NFsUtils::executableExistsInPath(const std::string& exe) {
if (!getenv("PATH"))
return false;

static CVarList paths(getenv("PATH"), 0, ':', true);

for (auto& p : paths) {
std::string path = p + std::string{"/"} + exe;
std::error_code ec;
if (!std::filesystem::exists(path, ec) || ec)
continue;

if (!std::filesystem::is_regular_file(path, ec) || ec)
continue;

auto stat = std::filesystem::status(path, ec);
if (ec)
continue;

auto perms = stat.permissions();

return std::filesystem::perms::none != (perms & std::filesystem::perms::others_exec);
}

return false;
}
15 changes: 15 additions & 0 deletions src/helpers/fs/FsUtils.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
#pragma once
#include <optional>
#include <string>

namespace NFsUtils {
// Returns the path to the hyprland directory in data home.
std::optional<std::string> getDataHome();

std::optional<std::string> readFileAsString(const std::string& path);

// overwrites the file if exists
bool writeToFile(const std::string& path, const std::string& content);

bool executableExistsInPath(const std::string& exe);
};
111 changes: 111 additions & 0 deletions src/managers/DonationNagManager.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
#include "DonationNagManager.hpp"
#include "../debug/Log.hpp"
#include "VersionKeeperManager.hpp"
#include "eventLoop/EventLoopManager.hpp"

#include <chrono>
#include <format>

#include "../helpers/fs/FsUtils.hpp"

#include <hyprutils/os/Process.hpp>
using namespace Hyprutils::OS;

constexpr const char* LAST_NAG_FILE_NAME = "lastNag";
constexpr uint64_t DAY_IN_SECONDS = 3600ULL * 24;
constexpr uint64_t MONTH_IN_SECONDS = DAY_IN_SECONDS * 30;

struct SNagDatePoint {
// Counted from 1, as in Jan 1st is 1, 1
// No month-boundaries because I am lazy
uint8_t month = 0, dayStart = 0, dayEnd = 0;
};

// clang-format off
const std::vector<SNagDatePoint> NAG_DATE_POINTS = {
SNagDatePoint {
7, 20, 31,
},
SNagDatePoint {
12, 1, 28
},
};
// clang-format on

CDonationNagManager::CDonationNagManager() {
if (g_pVersionKeeperMgr->fired())
return;

const auto DATAROOT = NFsUtils::getDataHome();

if (!DATAROOT)
return;

const auto EPOCH = std::chrono::duration_cast<std::chrono::seconds>(std::chrono::system_clock::now().time_since_epoch()).count();

const auto LASTNAGSTR = NFsUtils::readFileAsString(*DATAROOT + "/" + LAST_NAG_FILE_NAME);

if (!LASTNAGSTR) {
const auto EPOCHSTR = std::format("{}", EPOCH);
NFsUtils::writeToFile(*DATAROOT + "/" + LAST_NAG_FILE_NAME, EPOCHSTR);
return;
}

uint64_t LAST_EPOCH = 0;

try {
LAST_EPOCH = std::stoull(*LASTNAGSTR);
} catch (std::exception& e) {
Debug::log(ERR, "DonationNag: Last epoch invalid? Failed to parse \"{}\". Setting to today.", *LASTNAGSTR);
const auto EPOCHSTR = std::format("{}", EPOCH);
NFsUtils::writeToFile(*DATAROOT + "/" + LAST_NAG_FILE_NAME, EPOCHSTR);
return;
}

// don't nag if the last nag was less than a month ago. This is
// mostly for first-time nags, as other nags happen in specific time frames shorter than a month
if (EPOCH - LAST_EPOCH < MONTH_IN_SECONDS) {
Debug::log(LOG, "DonationNag: last nag was {} days ago, too early for a nag.", (int)std::round((EPOCH - LAST_EPOCH) / (double)MONTH_IN_SECONDS));
return;
}

if (!NFsUtils::executableExistsInPath("hyprland-donation-screen")) {
Debug::log(ERR, "DonationNag: executable doesn't exist, skipping.");
return;
}

auto tt = std::chrono::system_clock::to_time_t(std::chrono::system_clock::now());
auto local = *localtime(&tt);

const auto MONTH = local.tm_mon + 1;
const auto DAY = local.tm_mday;

for (const auto& nagPoint : NAG_DATE_POINTS) {
if (MONTH != nagPoint.month)
continue;

if (DAY < nagPoint.dayStart || DAY > nagPoint.dayEnd)
continue;

Debug::log(LOG, "DonationNag: hit nag month {} days {}-{}, it's {} today, nagging", MONTH, nagPoint.dayStart, nagPoint.dayEnd, DAY);

m_bFired = true;

const auto EPOCHSTR = std::format("{}", EPOCH);
NFsUtils::writeToFile(*DATAROOT + "/" + LAST_NAG_FILE_NAME, EPOCHSTR);

g_pEventLoopManager->doLater([] {
CProcess proc("hyprland-donation-screen", {});
proc.runAsync();
});

break;
}

if (!m_bFired)
Debug::log(LOG, "DonationNag: didn't hit any nagging periods");
}

bool CDonationNagManager::fired() {
return m_bFired;
}
16 changes: 16 additions & 0 deletions src/managers/DonationNagManager.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
#pragma once

#include <memory>

class CDonationNagManager {
public:
CDonationNagManager();

// whether the donation nag was shown this boot.
bool fired();

private:
bool m_bFired = false;
};

inline std::unique_ptr<CDonationNagManager> g_pDonationNagManager;
Loading

0 comments on commit 0fd023d

Please sign in to comment.