diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml
index 22421d18..65bfa1ca 100644
--- a/.github/workflows/ci.yaml
+++ b/.github/workflows/ci.yaml
@@ -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 '' >> clang-format.patch
+ echo 'clang-format.patch
' >> 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 ' ' >> 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
diff --git a/src/Compositor.cpp b/src/Compositor.cpp
index 3d159042..fcd15312 100644
--- a/src/Compositor.cpp
+++ b/src/Compositor.cpp
@@ -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
#include
@@ -25,6 +26,7 @@
#endif
#include
#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();
+ Debug::log(LOG, "Creating the DonationNag!");
+ g_pDonationNagManager = std::make_unique();
+
Debug::log(LOG, "Starting XWayland");
g_pXWayland = std::make_unique(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);
}
diff --git a/src/config/ConfigDescriptions.hpp b/src/config/ConfigDescriptions.hpp
index 686b996a..483f1f1f 100644
--- a/src/config/ConfigDescriptions.hpp
+++ b/src/config/ConfigDescriptions.hpp
@@ -1313,6 +1313,12 @@ inline static const std::vector 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:
diff --git a/src/config/ConfigManager.cpp b/src/config/ConfigManager.cpp
index def15f26..299dfbc3 100644
--- a/src/config/ConfigManager.cpp
+++ b/src/config/ConfigManager.cpp
@@ -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 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
diff --git a/src/desktop/Window.cpp b/src/desktop/Window.cpp
index 2bd82ad3..5608daef 100644
--- a/src/desktop/Window.cpp
+++ b/src/desktop/Window.cpp
@@ -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)
@@ -1382,15 +1382,26 @@ void CWindow::activate(bool force) {
}
void CWindow::onUpdateState() {
- std::optional requestsFS = m_pXDGSurface ? m_pXDGSurface->toplevel->state.requestsFullscreen : m_pXWaylandSurface->state.requestsFullscreen;
- std::optional requestsMX = m_pXDGSurface ? m_pXDGSurface->toplevel->state.requestsMaximize : m_pXWaylandSurface->state.requestsMaximize;
+ std::optional requestsFS = m_pXDGSurface ? m_pXDGSurface->toplevel->state.requestsFullscreen : m_pXWaylandSurface->state.requestsFullscreen;
+ std::optional requestsID = m_pXDGSurface ? m_pXDGSurface->toplevel->state.requestsFullscreenMonitor : MONITOR_INVALID;
+ std::optional 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 (m_bIsMapped) {
- g_pCompositor->changeWindowFullscreenModeClient(m_pSelf.lock(), FSMODE_FULLSCREEN, requestsFS.value());
+ if (requestsID.has_value() && (requestsID.value() != MONITOR_INVALID) && !(m_eSuppressedEvents & SUPPRESS_FULLSCREEN_OUTPUT)) {
+ if (m_bIsMapped) {
+ 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;
}
diff --git a/src/desktop/Window.hpp b/src/desktop/Window.hpp
index 37189a00..d503ac3b 100644
--- a/src/desktop/Window.hpp
+++ b/src/desktop/Window.hpp
@@ -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;
@@ -288,7 +289,8 @@ class CWindow {
bool m_bNoInitialFocus = false;
// Fullscreen and Maximize
- bool m_bWantsInitialFullscreen = false;
+ bool m_bWantsInitialFullscreen = false;
+ MONITORID m_iWantsInitialFullscreenMonitor = MONITOR_INVALID;
// bitfield eSuppressEvents
uint64_t m_eSuppressedEvents = SUPPRESS_NONE;
diff --git a/src/events/Windows.cpp b/src/events/Windows.cpp
index 8082cdc9..ff69e83f 100644
--- a/src/events/Windows.cpp
+++ b/src/events/Windows.cpp
@@ -131,6 +131,7 @@ void Events::listener_mapWindow(void* owner, void* data) {
std::optional 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;
diff --git a/src/helpers/MiscFunctions.cpp b/src/helpers/MiscFunctions.cpp
index e970b781..08a1106a 100644
--- a/src/helpers/MiscFunctions.cpp
+++ b/src/helpers/MiscFunctions.cpp
@@ -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;
-}
diff --git a/src/helpers/MiscFunctions.hpp b/src/helpers/MiscFunctions.hpp
index b179b3d6..64802279 100644
--- a/src/helpers/MiscFunctions.hpp
+++ b/src/helpers/MiscFunctions.hpp
@@ -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
[[deprecated("use std::format instead")]] std::string getFormat(std::format_string fmt, Args&&... args) {
diff --git a/src/helpers/fs/FsUtils.cpp b/src/helpers/fs/FsUtils.cpp
new file mode 100644
index 00000000..0bc2e685
--- /dev/null
+++ b/src/helpers/fs/FsUtils.cpp
@@ -0,0 +1,107 @@
+#include "FsUtils.hpp"
+#include "../../debug/Log.hpp"
+
+#include
+#include
+
+#include
+#include
+using namespace Hyprutils::String;
+
+std::optional 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 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(file)), (std::istreambuf_iterator())));
+}
+
+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;
+}
diff --git a/src/helpers/fs/FsUtils.hpp b/src/helpers/fs/FsUtils.hpp
new file mode 100644
index 00000000..bc3b3bf1
--- /dev/null
+++ b/src/helpers/fs/FsUtils.hpp
@@ -0,0 +1,15 @@
+#pragma once
+#include
+#include
+
+namespace NFsUtils {
+ // Returns the path to the hyprland directory in data home.
+ std::optional getDataHome();
+
+ std::optional 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);
+};
diff --git a/src/managers/AnimationManager.cpp b/src/managers/AnimationManager.cpp
index f0f6a980..ea74ffd2 100644
--- a/src/managers/AnimationManager.cpp
+++ b/src/managers/AnimationManager.cpp
@@ -215,7 +215,11 @@ void CHyprAnimationManager::tick() {
lastTick = std::chrono::high_resolution_clock::now();
static auto PANIMENABLED = CConfigValue("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;
diff --git a/src/managers/DonationNagManager.cpp b/src/managers/DonationNagManager.cpp
new file mode 100644
index 00000000..d7eab9ae
--- /dev/null
+++ b/src/managers/DonationNagManager.cpp
@@ -0,0 +1,114 @@
+#include "DonationNagManager.hpp"
+#include "../debug/Log.hpp"
+#include "VersionKeeperManager.hpp"
+#include "eventLoop/EventLoopManager.hpp"
+#include "../config/ConfigValue.hpp"
+
+#include
+#include
+
+#include "../helpers/fs/FsUtils.hpp"
+
+#include
+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 NAG_DATE_POINTS = {
+ SNagDatePoint {
+ 7, 20, 31,
+ },
+ SNagDatePoint {
+ 12, 1, 28
+ },
+};
+// clang-format on
+
+CDonationNagManager::CDonationNagManager() {
+ static auto PNONAG = CConfigValue("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::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;
+}
\ No newline at end of file
diff --git a/src/managers/DonationNagManager.hpp b/src/managers/DonationNagManager.hpp
new file mode 100644
index 00000000..e296d815
--- /dev/null
+++ b/src/managers/DonationNagManager.hpp
@@ -0,0 +1,16 @@
+#pragma once
+
+#include
+
+class CDonationNagManager {
+ public:
+ CDonationNagManager();
+
+ // whether the donation nag was shown this boot.
+ bool fired();
+
+ private:
+ bool m_bFired = false;
+};
+
+inline std::unique_ptr g_pDonationNagManager;
\ No newline at end of file
diff --git a/src/managers/KeybindManager.cpp b/src/managers/KeybindManager.cpp
index 09f6f928..4cd97913 100644
--- a/src/managers/KeybindManager.cpp
+++ b/src/managers/KeybindManager.cpp
@@ -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);
diff --git a/src/managers/KeybindManager.hpp b/src/managers/KeybindManager.hpp
index 76c72187..d01ec75b 100644
--- a/src/managers/KeybindManager.hpp
+++ b/src/managers/KeybindManager.hpp
@@ -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
diff --git a/src/managers/VersionKeeperManager.cpp b/src/managers/VersionKeeperManager.cpp
index 1ef1040e..cc03a7b9 100644
--- a/src/managers/VersionKeeperManager.cpp
+++ b/src/managers/VersionKeeperManager.cpp
@@ -6,6 +6,7 @@
#include "../helpers/varlist/VarList.hpp"
#include "eventLoop/EventLoopManager.hpp"
#include "../config/ConfigValue.hpp"
+#include "../helpers/fs/FsUtils.hpp"
#include
#include
@@ -20,12 +21,12 @@ constexpr const char* VERSION_FILE_NAME = "lastVersion";
CVersionKeeperManager::CVersionKeeperManager() {
static auto PNONOTIFY = CConfigValue("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 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 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(file)), (std::istreambuf_iterator())));
-}
-
-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;
+}
diff --git a/src/managers/VersionKeeperManager.hpp b/src/managers/VersionKeeperManager.hpp
index f0dc05ce..eb404d88 100644
--- a/src/managers/VersionKeeperManager.hpp
+++ b/src/managers/VersionKeeperManager.hpp
@@ -1,17 +1,18 @@
#pragma once
#include
-#include
class CVersionKeeperManager {
public:
CVersionKeeperManager();
+ // whether the update screen was shown this boot.
+ bool fired();
+
private:
- std::optional getDataHome();
- std::optional getDataLastVersion(const std::string& dataRoot);
- void writeVersionToVersionFile(const std::string& dataRoot);
- bool isVersionOlderThanRunning(const std::string& ver);
+ bool isVersionOlderThanRunning(const std::string& ver);
+
+ bool m_bFired = false;
};
inline std::unique_ptr g_pVersionKeeperMgr;
\ No newline at end of file
diff --git a/src/protocols/XDGShell.cpp b/src/protocols/XDGShell.cpp
index 282aec47..6b7cb3c1 100644
--- a/src/protocols/XDGShell.cpp
+++ b/src/protocols/XDGShell.cpp
@@ -5,6 +5,7 @@
#include "../managers/SeatManager.hpp"
#include "core/Seat.hpp"
#include "core/Compositor.hpp"
+#include "protocols/core/Output.hpp"
#include
#include
@@ -191,9 +192,14 @@ CXDGToplevelResource::CXDGToplevelResource(SP resource_, SPsetSetFullscreen([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 resource_, SPsetSetMinimized([this](CXdgToplevel* r) {
state.requestsMinimize = true;
events.stateChanged.emit();
- state.requestsFullscreen.reset();
+ state.requestsMinimize.reset();
});
resource->setSetParent([this](CXdgToplevel* r, wl_resource* parentR) {
diff --git a/src/protocols/XDGShell.hpp b/src/protocols/XDGShell.hpp
index ef847f3b..6eef99bb 100644
--- a/src/protocols/XDGShell.hpp
+++ b/src/protocols/XDGShell.hpp
@@ -123,9 +123,10 @@ class CXDGToplevelResource {
std::string appid;
// volatile state: is reset after the stateChanged signal fires
- std::optional requestsMaximize;
- std::optional requestsFullscreen;
- std::optional requestsMinimize;
+ std::optional requestsMaximize;
+ std::optional requestsFullscreen;
+ std::optional requestsFullscreenMonitor;
+ std::optional requestsMinimize;
} state;
struct {
diff --git a/src/protocols/core/Compositor.cpp b/src/protocols/core/Compositor.cpp
index 6213f987..e1d6ef7d 100644
--- a/src/protocols/core/Compositor.cpp
+++ b/src/protocols/core/Compositor.cpp
@@ -11,6 +11,7 @@
#include "../PresentationTime.hpp"
#include "../DRMSyncobj.hpp"
#include "../../render/Renderer.hpp"
+#include "config/ConfigValue.hpp"
#include
class CDefaultSurfaceRole : public ISurfaceRole {
@@ -423,12 +424,14 @@ void CWLSurfaceResource::unlockPendingState() {
}
void CWLSurfaceResource::commitPendingState() {
- auto const previousBuffer = current.buffer;
- current = pending;
+ static auto PDROP = CConfigValue("render:allow_early_buffer_release");
+ auto const previousBuffer = current.buffer;
+ current = pending;
pending.damage.clear();
pending.bufferDamage.clear();
pending.newBuffer = false;
- dropPendingBuffer(); // at this point current.buffer holds the same SP and we don't use pending anymore
+ 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