mirror of
https://github.com/hyprwm/Hyprland
synced 2025-01-11 23:49:49 +01:00
Merge branch 'hyprwm:main' into main
This commit is contained in:
commit
337369495c
21 changed files with 411 additions and 135 deletions
24
.github/workflows/ci.yaml
vendored
24
.github/workflows/ci.yaml
vendored
|
@ -126,3 +126,27 @@ jobs:
|
|||
|
||||
- name: clang-format check
|
||||
run: ninja -C build clang-format-check
|
||||
|
||||
- name: clang-format apply
|
||||
if: ${{ failure() && github.event_name == 'pull_request' }}
|
||||
run: ninja -C build clang-format
|
||||
|
||||
- name: Create patch
|
||||
if: ${{ failure() && github.event_name == 'pull_request' }}
|
||||
run: |
|
||||
echo 'Please fix the formatting issues by running [`clang-format`](https://wiki.hyprland.org/Contributing-and-Debugging/PR-Guidelines/#code-style), or directly apply this patch:' > clang-format.patch
|
||||
echo '<details>' >> clang-format.patch
|
||||
echo '<summary>clang-format.patch</summary>' >> clang-format.patch
|
||||
echo >> clang-format.patch
|
||||
echo '```diff' >> clang-format.patch
|
||||
git diff >> clang-format.patch
|
||||
echo '```' >> clang-format.patch
|
||||
echo >> clang-format.patch
|
||||
echo '</details>' >> clang-format.patch
|
||||
|
||||
- name: Comment patch
|
||||
if: ${{ failure() && github.event_name == 'pull_request' }}
|
||||
uses: mshick/add-pr-comment@v2
|
||||
with:
|
||||
message-path: |
|
||||
clang-format.patch
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
#include "managers/PointerManager.hpp"
|
||||
#include "managers/SeatManager.hpp"
|
||||
#include "managers/VersionKeeperManager.hpp"
|
||||
#include "managers/DonationNagManager.hpp"
|
||||
#include "managers/eventLoop/EventLoopManager.hpp"
|
||||
#include <aquamarine/output/Output.hpp>
|
||||
#include <bit>
|
||||
|
@ -25,6 +26,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"
|
||||
|
@ -544,6 +546,8 @@ void CCompositor::cleanup() {
|
|||
g_pSeatManager.reset();
|
||||
g_pHyprCtl.reset();
|
||||
g_pEventLoopManager.reset();
|
||||
g_pVersionKeeperMgr.reset();
|
||||
g_pDonationNagManager.reset();
|
||||
|
||||
if (m_pAqBackend)
|
||||
m_pAqBackend.reset();
|
||||
|
@ -645,6 +649,9 @@ void CCompositor::initManagers(eManagersInitStage stage) {
|
|||
Debug::log(LOG, "Creating the VersionKeeper!");
|
||||
g_pVersionKeeperMgr = std::make_unique<CVersionKeeperManager>();
|
||||
|
||||
Debug::log(LOG, "Creating the DonationNag!");
|
||||
g_pDonationNagManager = std::make_unique<CDonationNagManager>();
|
||||
|
||||
Debug::log(LOG, "Starting XWayland");
|
||||
g_pXWayland = std::make_unique<CXWayland>(g_pCompositor->m_bEnableXwayland);
|
||||
} break;
|
||||
|
@ -2401,6 +2408,9 @@ PHLWINDOW CCompositor::getWindowByRegex(const std::string& regexp_) {
|
|||
} else if (regexp.starts_with("initialtitle:")) {
|
||||
mode = MODE_INITIAL_TITLE_REGEX;
|
||||
regexCheck = regexp.substr(13);
|
||||
} else if (regexp.starts_with("tag:")) {
|
||||
mode = MODE_TAG_REGEX;
|
||||
regexCheck = regexp.substr(4);
|
||||
} else if (regexp.starts_with("address:")) {
|
||||
mode = MODE_ADDRESS;
|
||||
matchCheck = regexp.substr(8);
|
||||
|
@ -2438,6 +2448,18 @@ PHLWINDOW CCompositor::getWindowByRegex(const std::string& regexp_) {
|
|||
continue;
|
||||
break;
|
||||
}
|
||||
case MODE_TAG_REGEX: {
|
||||
bool tagMatched = false;
|
||||
for (auto const& t : w->m_tags.getTags()) {
|
||||
if (RE2::FullMatch(t, regexCheck)) {
|
||||
tagMatched = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!tagMatched)
|
||||
continue;
|
||||
break;
|
||||
}
|
||||
case MODE_ADDRESS: {
|
||||
std::string addr = std::format("0x{:x}", (uintptr_t)w.get());
|
||||
if (matchCheck != addr)
|
||||
|
@ -2630,7 +2652,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);
|
||||
}
|
||||
|
|
|
@ -1313,6 +1313,12 @@ inline static const std::vector<SConfigOptionDescription> CONFIG_OPTIONS = {
|
|||
.type = CONFIG_OPTION_INT,
|
||||
.data = SConfigOptionDescription::SRangeData{2, 0, 2},
|
||||
},
|
||||
SConfigOptionDescription{
|
||||
.value = "render:allow_early_buffer_release",
|
||||
.description = "Allow early buffer release event. Fixes stuttering and missing frames for some apps. May cause graphical glitches and memory leaks in others",
|
||||
.type = CONFIG_OPTION_BOOL,
|
||||
.data = SConfigOptionDescription::SBoolData{true},
|
||||
},
|
||||
|
||||
/*
|
||||
* cursor:
|
||||
|
|
|
@ -619,8 +619,10 @@ CConfigManager::CConfigManager() {
|
|||
m_pConfig->addConfigValue("render:expand_undersized_textures", Hyprlang::INT{1});
|
||||
m_pConfig->addConfigValue("render:xp_mode", Hyprlang::INT{0});
|
||||
m_pConfig->addConfigValue("render:ctm_animation", Hyprlang::INT{2});
|
||||
m_pConfig->addConfigValue("render:allow_early_buffer_release", Hyprlang::INT{1});
|
||||
|
||||
m_pConfig->addConfigValue("ecosystem:no_update_news", Hyprlang::INT{0});
|
||||
m_pConfig->addConfigValue("ecosystem:no_donation_nag", Hyprlang::INT{0});
|
||||
|
||||
m_pConfig->addConfigValue("experimental:wide_color_gamut", Hyprlang::INT{0});
|
||||
m_pConfig->addConfigValue("experimental:hdr", Hyprlang::INT{0});
|
||||
|
@ -2053,6 +2055,11 @@ std::optional<std::string> CConfigManager::handleAnimation(const std::string& co
|
|||
if (enabledInt != 0 && enabledInt != 1)
|
||||
return "invalid animation on/off state";
|
||||
|
||||
if (!enabledInt) {
|
||||
m_AnimationTree.setConfigForNode(ANIMNAME, enabledInt, 1, "default");
|
||||
return {};
|
||||
}
|
||||
|
||||
int64_t speed = -1;
|
||||
|
||||
// speed
|
||||
|
|
|
@ -444,7 +444,7 @@ void CWindow::moveToWorkspace(PHLWORKSPACE pWorkspace) {
|
|||
}
|
||||
|
||||
// update xwayland coords
|
||||
g_pXWaylandManager->setWindowSize(m_pSelf.lock(), m_vRealSize->value());
|
||||
g_pXWaylandManager->setWindowSize(m_pSelf.lock(), m_vRealSize->goal());
|
||||
|
||||
if (OLDWORKSPACE && g_pCompositor->isWorkspaceSpecial(OLDWORKSPACE->m_iID) && OLDWORKSPACE->getWindows() == 0 && *PCLOSEONLASTSPECIAL) {
|
||||
if (const auto PMONITOR = OLDWORKSPACE->m_pMonitor.lock(); PMONITOR)
|
||||
|
@ -1383,14 +1383,25 @@ void CWindow::activate(bool force) {
|
|||
|
||||
void CWindow::onUpdateState() {
|
||||
std::optional<bool> requestsFS = m_pXDGSurface ? m_pXDGSurface->toplevel->state.requestsFullscreen : m_pXWaylandSurface->state.requestsFullscreen;
|
||||
std::optional<MONITORID> requestsID = m_pXDGSurface ? m_pXDGSurface->toplevel->state.requestsFullscreenMonitor : MONITOR_INVALID;
|
||||
std::optional<bool> requestsMX = m_pXDGSurface ? m_pXDGSurface->toplevel->state.requestsMaximize : m_pXWaylandSurface->state.requestsMaximize;
|
||||
|
||||
if (requestsFS.has_value() && !(m_eSuppressedEvents & SUPPRESS_FULLSCREEN)) {
|
||||
bool fs = requestsFS.value();
|
||||
if (requestsID.has_value() && (requestsID.value() != MONITOR_INVALID) && !(m_eSuppressedEvents & SUPPRESS_FULLSCREEN_OUTPUT)) {
|
||||
if (m_bIsMapped) {
|
||||
g_pCompositor->changeWindowFullscreenModeClient(m_pSelf.lock(), FSMODE_FULLSCREEN, requestsFS.value());
|
||||
const auto monitor = g_pCompositor->getMonitorFromID(requestsID.value());
|
||||
g_pCompositor->moveWindowToWorkspaceSafe(m_pSelf.lock(), monitor->activeWorkspace);
|
||||
g_pCompositor->setActiveMonitor(monitor);
|
||||
}
|
||||
|
||||
if (!m_bIsMapped)
|
||||
m_iWantsInitialFullscreenMonitor = requestsID.value();
|
||||
}
|
||||
|
||||
bool fs = requestsFS.value();
|
||||
if (m_bIsMapped)
|
||||
g_pCompositor->changeWindowFullscreenModeClient(m_pSelf.lock(), FSMODE_FULLSCREEN, requestsFS.value());
|
||||
|
||||
if (!m_bIsMapped)
|
||||
m_bWantsInitialFullscreen = fs;
|
||||
}
|
||||
|
|
|
@ -57,6 +57,7 @@ enum eSuppressEvents : uint8_t {
|
|||
SUPPRESS_MAXIMIZE = 1 << 1,
|
||||
SUPPRESS_ACTIVATE = 1 << 2,
|
||||
SUPPRESS_ACTIVATE_FOCUSONLY = 1 << 3,
|
||||
SUPPRESS_FULLSCREEN_OUTPUT = 1 << 4,
|
||||
};
|
||||
|
||||
class IWindowTransformer;
|
||||
|
@ -289,6 +290,7 @@ class CWindow {
|
|||
|
||||
// Fullscreen and Maximize
|
||||
bool m_bWantsInitialFullscreen = false;
|
||||
MONITORID m_iWantsInitialFullscreenMonitor = MONITOR_INVALID;
|
||||
|
||||
// bitfield eSuppressEvents
|
||||
uint64_t m_eSuppressedEvents = SUPPRESS_NONE;
|
||||
|
|
|
@ -131,6 +131,7 @@ void Events::listener_mapWindow(void* owner, void* data) {
|
|||
std::optional<SFullscreenState> requestedFSState;
|
||||
if (PWINDOW->m_bWantsInitialFullscreen || (PWINDOW->m_bIsX11 && PWINDOW->m_pXWaylandSurface->fullscreen))
|
||||
requestedClientFSMode = FSMODE_FULLSCREEN;
|
||||
MONITORID requestedFSMonitor = PWINDOW->m_iWantsInitialFullscreenMonitor;
|
||||
|
||||
for (auto const& r : PWINDOW->m_vMatchedRules) {
|
||||
switch (r->ruleType) {
|
||||
|
@ -168,6 +169,7 @@ void Events::listener_mapWindow(void* owner, void* data) {
|
|||
PWORKSPACE = PWINDOW->m_pWorkspace;
|
||||
|
||||
Debug::log(LOG, "Rule monitor, applying to {:mw}", PWINDOW);
|
||||
requestedFSMonitor = MONITOR_INVALID;
|
||||
} catch (std::exception& e) { Debug::log(ERR, "Rule monitor failed, rule: {} -> {} | err: {}", r->szRule, r->szValue, e.what()); }
|
||||
break;
|
||||
}
|
||||
|
@ -186,6 +188,7 @@ void Events::listener_mapWindow(void* owner, void* data) {
|
|||
requestedWorkspace = "";
|
||||
|
||||
Debug::log(LOG, "Rule workspace matched by {}, {} applied.", PWINDOW, r->szValue);
|
||||
requestedFSMonitor = MONITOR_INVALID;
|
||||
break;
|
||||
}
|
||||
case CWindowRule::RULE_FLOAT: {
|
||||
|
@ -227,6 +230,8 @@ void Events::listener_mapWindow(void* owner, void* data) {
|
|||
PWINDOW->m_eSuppressedEvents |= SUPPRESS_ACTIVATE;
|
||||
else if (vars[i] == "activatefocus")
|
||||
PWINDOW->m_eSuppressedEvents |= SUPPRESS_ACTIVATE_FOCUSONLY;
|
||||
else if (vars[i] == "fullscreenoutput")
|
||||
PWINDOW->m_eSuppressedEvents |= SUPPRESS_FULLSCREEN_OUTPUT;
|
||||
else
|
||||
Debug::log(ERR, "Error while parsing suppressevent windowrule: unknown event type {}", vars[i]);
|
||||
}
|
||||
|
@ -337,10 +342,30 @@ void Events::listener_mapWindow(void* owner, void* data) {
|
|||
|
||||
PMONITOR = g_pCompositor->m_pLastMonitor.lock();
|
||||
}
|
||||
|
||||
requestedFSMonitor = MONITOR_INVALID;
|
||||
} else
|
||||
workspaceSilent = false;
|
||||
}
|
||||
|
||||
if (PWINDOW->m_eSuppressedEvents & SUPPRESS_FULLSCREEN_OUTPUT)
|
||||
requestedFSMonitor = MONITOR_INVALID;
|
||||
else if (requestedFSMonitor != MONITOR_INVALID) {
|
||||
if (const auto PM = g_pCompositor->getMonitorFromID(requestedFSMonitor); PM)
|
||||
PWINDOW->m_pMonitor = PM;
|
||||
|
||||
const auto PMONITORFROMID = PWINDOW->m_pMonitor.lock();
|
||||
|
||||
if (PWINDOW->m_pMonitor != PMONITOR) {
|
||||
g_pKeybindManager->m_mDispatchers["focusmonitor"](std::to_string(PWINDOW->monitorID()));
|
||||
PMONITOR = PMONITORFROMID;
|
||||
}
|
||||
PWINDOW->m_pWorkspace = PMONITOR->activeSpecialWorkspace ? PMONITOR->activeSpecialWorkspace : PMONITOR->activeWorkspace;
|
||||
PWORKSPACE = PWINDOW->m_pWorkspace;
|
||||
|
||||
Debug::log(LOG, "Requested monitor, applying to {:mw}", PWINDOW);
|
||||
}
|
||||
|
||||
if (PWORKSPACE->m_bDefaultFloating)
|
||||
PWINDOW->m_bIsFloating = true;
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
107
src/helpers/fs/FsUtils.cpp
Normal file
107
src/helpers/fs/FsUtils.cpp
Normal file
|
@ -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
src/helpers/fs/FsUtils.hpp
Normal file
15
src/helpers/fs/FsUtils.hpp
Normal file
|
@ -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);
|
||||
};
|
|
@ -215,7 +215,11 @@ void CHyprAnimationManager::tick() {
|
|||
lastTick = std::chrono::high_resolution_clock::now();
|
||||
|
||||
static auto PANIMENABLED = CConfigValue<Hyprlang::INT>("animations:enabled");
|
||||
for (auto const& pav : m_vActiveAnimatedVariables) {
|
||||
|
||||
// We need to do this because it's perfectly valid to add/change a var during this (via callbacks)
|
||||
// FIXME: instead of doing this, make a fn to defer adding until tick is done and not in progress anymore.
|
||||
const auto PAVS = m_vActiveAnimatedVariables;
|
||||
for (auto const& pav : PAVS) {
|
||||
const auto PAV = pav.lock();
|
||||
if (!PAV)
|
||||
continue;
|
||||
|
|
114
src/managers/DonationNagManager.cpp
Normal file
114
src/managers/DonationNagManager.cpp
Normal file
|
@ -0,0 +1,114 @@
|
|||
#include "DonationNagManager.hpp"
|
||||
#include "../debug/Log.hpp"
|
||||
#include "VersionKeeperManager.hpp"
|
||||
#include "eventLoop/EventLoopManager.hpp"
|
||||
#include "../config/ConfigValue.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() {
|
||||
static auto PNONAG = CConfigValue<Hyprlang::INT>("ecosystem:no_donation_nag");
|
||||
|
||||
if (g_pVersionKeeperMgr->fired() || *PNONAG)
|
||||
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-donate-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-donate-screen", {});
|
||||
proc.runAsync();
|
||||
});
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
if (!m_bFired)
|
||||
Debug::log(LOG, "DonationNag: didn't hit any nagging periods");
|
||||
}
|
||||
|
||||
bool CDonationNagManager::fired() {
|
||||
return m_bFired;
|
||||
}
|
16
src/managers/DonationNagManager.hpp
Normal file
16
src/managers/DonationNagManager.hpp
Normal file
|
@ -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;
|
|
@ -983,7 +983,14 @@ uint64_t CKeybindManager::spawnRawProc(std::string args, PHLWORKSPACE pInitialWo
|
|||
}
|
||||
|
||||
SDispatchResult CKeybindManager::killActive(std::string args) {
|
||||
kill(g_pCompositor->m_pLastWindow.lock()->getPID(), SIGKILL);
|
||||
const auto PWINDOW = g_pCompositor->m_pLastWindow.lock();
|
||||
|
||||
if (!PWINDOW) {
|
||||
Debug::log(ERR, "killActive: no window found");
|
||||
return {.success = false, .error = "killActive: no window found"};
|
||||
}
|
||||
|
||||
kill(PWINDOW->getPID(), SIGKILL);
|
||||
|
||||
return {};
|
||||
}
|
||||
|
@ -1891,8 +1898,8 @@ SDispatchResult CKeybindManager::workspaceOpt(std::string args) {
|
|||
continue;
|
||||
|
||||
if (!w->m_bRequestsFloat && w->m_bIsFloating != PWORKSPACE->m_bDefaultFloating) {
|
||||
const auto SAVEDPOS = w->m_vRealPosition->value();
|
||||
const auto SAVEDSIZE = w->m_vRealSize->value();
|
||||
const auto SAVEDPOS = w->m_vRealPosition->goal();
|
||||
const auto SAVEDSIZE = w->m_vRealSize->goal();
|
||||
|
||||
w->m_bIsFloating = PWORKSPACE->m_bDefaultFloating;
|
||||
g_pLayoutManager->getCurrentLayout()->changeWindowFloatingMode(w);
|
||||
|
|
|
@ -49,6 +49,7 @@ enum eFocusWindowMode : uint8_t {
|
|||
MODE_INITIAL_CLASS_REGEX,
|
||||
MODE_TITLE_REGEX,
|
||||
MODE_INITIAL_TITLE_REGEX,
|
||||
MODE_TAG_REGEX,
|
||||
MODE_ADDRESS,
|
||||
MODE_PID,
|
||||
MODE_ACTIVE_WINDOW
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
#include "../helpers/varlist/VarList.hpp"
|
||||
#include "eventLoop/EventLoopManager.hpp"
|
||||
#include "../config/ConfigValue.hpp"
|
||||
#include "../helpers/fs/FsUtils.hpp"
|
||||
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
|
@ -20,12 +21,12 @@ constexpr const char* VERSION_FILE_NAME = "lastVersion";
|
|||
CVersionKeeperManager::CVersionKeeperManager() {
|
||||
static auto PNONOTIFY = CConfigValue<Hyprlang::INT>("ecosystem:no_update_news");
|
||||
|
||||
const auto DATAROOT = getDataHome();
|
||||
const auto DATAROOT = NFsUtils::getDataHome();
|
||||
|
||||
if (!DATAROOT)
|
||||
return;
|
||||
|
||||
const auto LASTVER = getDataLastVersion(*DATAROOT);
|
||||
const auto LASTVER = NFsUtils::readFileAsString(*DATAROOT + "/" + VERSION_FILE_NAME);
|
||||
|
||||
if (!LASTVER)
|
||||
return;
|
||||
|
@ -35,101 +36,26 @@ CVersionKeeperManager::CVersionKeeperManager() {
|
|||
return;
|
||||
}
|
||||
|
||||
writeVersionToVersionFile(*DATAROOT);
|
||||
NFsUtils::writeToFile(*DATAROOT + "/" + VERSION_FILE_NAME, HYPRLAND_VERSION);
|
||||
|
||||
if (*PNONOTIFY) {
|
||||
Debug::log(LOG, "CVersionKeeperManager: updated, but update news is disabled in the config :(");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!executableExistsInPath("hyprland-update-screen")) {
|
||||
if (!NFsUtils::executableExistsInPath("hyprland-update-screen")) {
|
||||
Debug::log(ERR, "CVersionKeeperManager: hyprland-update-screen doesn't seem to exist, skipping notif about update...");
|
||||
return;
|
||||
}
|
||||
|
||||
m_bFired = true;
|
||||
|
||||
g_pEventLoopManager->doLater([]() {
|
||||
CProcess proc("hyprland-update-screen", {"--new-version", HYPRLAND_VERSION});
|
||||
proc.runAsync();
|
||||
});
|
||||
}
|
||||
|
||||
std::optional<std::string> CVersionKeeperManager::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, "CVersionKeeperManager: 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, "CVersionKeeperManager: can't get data home: inaccessible / missing");
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
dataRoot += "hyprland/";
|
||||
|
||||
if (!std::filesystem::exists(dataRoot, ec) || ec) {
|
||||
Debug::log(LOG, "CVersionKeeperManager: no hyprland data home, creating.");
|
||||
std::filesystem::create_directory(dataRoot, ec);
|
||||
if (ec) {
|
||||
Debug::log(ERR, "CVersionKeeperManager: 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, "CVersionKeeperManager: couldn't set perms on hyprland data store. Proceeding anyways.");
|
||||
}
|
||||
|
||||
if (!std::filesystem::exists(dataRoot, ec) || ec) {
|
||||
Debug::log(ERR, "CVersionKeeperManager: no hyprland data home, failed to create.");
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
return dataRoot;
|
||||
}
|
||||
|
||||
std::optional<std::string> CVersionKeeperManager::getDataLastVersion(const std::string& dataRoot) {
|
||||
std::error_code ec;
|
||||
std::string lastVerFile = dataRoot + "/" + VERSION_FILE_NAME;
|
||||
|
||||
if (!std::filesystem::exists(lastVerFile, ec) || ec) {
|
||||
Debug::log(LOG, "CVersionKeeperManager: no hyprland last version file, creating.");
|
||||
writeVersionToVersionFile(dataRoot);
|
||||
|
||||
return "0.0.0";
|
||||
}
|
||||
|
||||
std::ifstream file(lastVerFile);
|
||||
if (!file.good()) {
|
||||
Debug::log(ERR, "CVersionKeeperManager: couldn't open an ifstream for reading the version file.");
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
return trim(std::string((std::istreambuf_iterator<char>(file)), (std::istreambuf_iterator<char>())));
|
||||
}
|
||||
|
||||
void CVersionKeeperManager::writeVersionToVersionFile(const std::string& dataRoot) {
|
||||
std::string lastVerFile = dataRoot + "/" + VERSION_FILE_NAME;
|
||||
std::ofstream of(lastVerFile, std::ios::trunc);
|
||||
if (!of.good()) {
|
||||
Debug::log(ERR, "CVersionKeeperManager: couldn't open an ofstream for writing the version file.");
|
||||
return;
|
||||
}
|
||||
|
||||
of << HYPRLAND_VERSION;
|
||||
of.close();
|
||||
}
|
||||
|
||||
bool CVersionKeeperManager::isVersionOlderThanRunning(const std::string& ver) {
|
||||
const CVarList verStrings(ver, 0, '.', true);
|
||||
|
||||
|
@ -151,3 +77,7 @@ bool CVersionKeeperManager::isVersionOlderThanRunning(const std::string& ver) {
|
|||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
bool CVersionKeeperManager::fired() {
|
||||
return m_bFired;
|
||||
}
|
||||
|
|
|
@ -1,17 +1,18 @@
|
|||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
|
||||
class CVersionKeeperManager {
|
||||
public:
|
||||
CVersionKeeperManager();
|
||||
|
||||
// whether the update screen was shown this boot.
|
||||
bool fired();
|
||||
|
||||
private:
|
||||
std::optional<std::string> getDataHome();
|
||||
std::optional<std::string> getDataLastVersion(const std::string& dataRoot);
|
||||
void writeVersionToVersionFile(const std::string& dataRoot);
|
||||
bool isVersionOlderThanRunning(const std::string& ver);
|
||||
|
||||
bool m_bFired = false;
|
||||
};
|
||||
|
||||
inline std::unique_ptr<CVersionKeeperManager> g_pVersionKeeperMgr;
|
|
@ -5,6 +5,7 @@
|
|||
#include "../managers/SeatManager.hpp"
|
||||
#include "core/Seat.hpp"
|
||||
#include "core/Compositor.hpp"
|
||||
#include "protocols/core/Output.hpp"
|
||||
#include <cstring>
|
||||
#include <ranges>
|
||||
|
||||
|
@ -191,9 +192,14 @@ CXDGToplevelResource::CXDGToplevelResource(SP<CXdgToplevel> resource_, SP<CXDGSu
|
|||
});
|
||||
|
||||
resource->setSetFullscreen([this](CXdgToplevel* r, wl_resource* output) {
|
||||
if (output)
|
||||
if (const auto PM = CWLOutputResource::fromResource(output)->monitor; PM)
|
||||
state.requestsFullscreenMonitor = PM->ID;
|
||||
|
||||
state.requestsFullscreen = true;
|
||||
events.stateChanged.emit();
|
||||
state.requestsFullscreen.reset();
|
||||
state.requestsFullscreenMonitor.reset();
|
||||
});
|
||||
|
||||
resource->setUnsetFullscreen([this](CXdgToplevel* r) {
|
||||
|
@ -205,7 +211,7 @@ CXDGToplevelResource::CXDGToplevelResource(SP<CXdgToplevel> resource_, SP<CXDGSu
|
|||
resource->setSetMinimized([this](CXdgToplevel* r) {
|
||||
state.requestsMinimize = true;
|
||||
events.stateChanged.emit();
|
||||
state.requestsFullscreen.reset();
|
||||
state.requestsMinimize.reset();
|
||||
});
|
||||
|
||||
resource->setSetParent([this](CXdgToplevel* r, wl_resource* parentR) {
|
||||
|
|
|
@ -125,6 +125,7 @@ class CXDGToplevelResource {
|
|||
// volatile state: is reset after the stateChanged signal fires
|
||||
std::optional<bool> requestsMaximize;
|
||||
std::optional<bool> requestsFullscreen;
|
||||
std::optional<MONITORID> requestsFullscreenMonitor;
|
||||
std::optional<bool> requestsMinimize;
|
||||
} state;
|
||||
|
||||
|
|
|
@ -11,6 +11,7 @@
|
|||
#include "../PresentationTime.hpp"
|
||||
#include "../DRMSyncobj.hpp"
|
||||
#include "../../render/Renderer.hpp"
|
||||
#include "config/ConfigValue.hpp"
|
||||
#include <cstring>
|
||||
|
||||
class CDefaultSurfaceRole : public ISurfaceRole {
|
||||
|
@ -423,11 +424,13 @@ void CWLSurfaceResource::unlockPendingState() {
|
|||
}
|
||||
|
||||
void CWLSurfaceResource::commitPendingState() {
|
||||
static auto PDROP = CConfigValue<Hyprlang::INT>("render:allow_early_buffer_release");
|
||||
auto const previousBuffer = current.buffer;
|
||||
current = pending;
|
||||
pending.damage.clear();
|
||||
pending.bufferDamage.clear();
|
||||
pending.newBuffer = false;
|
||||
if (!*PDROP)
|
||||
dropPendingBuffer(); // at this point current.buffer holds the same SP and we don't use pending anymore
|
||||
|
||||
events.roleCommit.emit();
|
||||
|
@ -450,8 +453,10 @@ void CWLSurfaceResource::commitPendingState() {
|
|||
// release the buffer if it's synchronous as update() has done everything thats needed
|
||||
// so we can let the app know we're done.
|
||||
// Some clients aren't ready to receive a release this early. Should be fine to release it on the next commitPendingState.
|
||||
// if (current.buffer->buffer->isSynchronous())
|
||||
// dropCurrentBuffer();
|
||||
if (current.buffer->buffer->isSynchronous() && *PDROP) {
|
||||
dropCurrentBuffer();
|
||||
dropPendingBuffer(); // at this point current.buffer holds the same SP and we don't use pending anymore
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: we should _accumulate_ and not replace above if sync
|
||||
|
|
Loading…
Reference in a new issue