Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
127 changes: 127 additions & 0 deletions hyprtester/src/tests/main/misc.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,128 @@ using namespace Hyprutils::Memory;
#define UP CUniquePointer
#define SP CSharedPointer

static void testAnrDialogs() {
NLog::log("{}Testing anrdialogs", Colors::YELLOW);

OK(getFromSocket("/keyword misc:enable_anr_dialog true"));
OK(getFromSocket("/keyword misc:anr_missed_pings 1"));

NLog::log("{}anrdialogs Should pop up on parent's workspace", Colors::YELLOW);

NLog::log("{}anrdialog: regular workspaces", Colors::YELLOW);
{
OK(getFromSocket("/dispatch workspace 2"));

auto kitty = Tests::spawnKitty("bad_kitty");

if (!kitty) {
ret = 1;
return;
}

{
auto str = getFromSocket("/activewindow");
EXPECT_CONTAINS(str, "workspace: 2");
}

OK(getFromSocket("/dispatch workspace 1"));

::kill(kitty->pid(), SIGSTOP);

std::this_thread::sleep_for(std::chrono::milliseconds(2000));
Tests::waitUntilWindowsN(2);

{
auto str = getFromSocket("/activeworkspace");
EXPECT_CONTAINS(str, "windows: 0");
}

{
OK(getFromSocket("/dispatch focuswindow class:hyprland-dialog"))
auto str = getFromSocket("/activewindow");
EXPECT_CONTAINS(str, "workspace: 2");
}
}

Tests::killAllWindows();

NLog::log("{}anrdialog: named workspaces", Colors::YELLOW);
{
OK(getFromSocket("/dispatch workspace name:yummy"));

auto kitty = Tests::spawnKitty("bad_kitty");

if (!kitty) {
ret = 1;
return;
}

{
auto str = getFromSocket("/activewindow");
EXPECT_CONTAINS(str, "yummy"); // can't predetermine workspace id
}

OK(getFromSocket("/dispatch workspace 1"));

::kill(kitty->pid(), SIGSTOP);

std::this_thread::sleep_for(std::chrono::milliseconds(2000));
Tests::waitUntilWindowsN(2);

{
auto str = getFromSocket("/activeworkspace");
EXPECT_CONTAINS(str, "windows: 0");
}

{
OK(getFromSocket("/dispatch focuswindow class:hyprland-dialog"))
auto str = getFromSocket("/activewindow");
EXPECT_CONTAINS(str, "yummy");
}
}

Tests::killAllWindows();

NLog::log("{}anrdialog: special workspaces", Colors::YELLOW);
{
OK(getFromSocket("/dispatch workspace special:apple"));

auto kitty = Tests::spawnKitty("bad_kitty");

if (!kitty) {
ret = 1;
return;
}

{
auto str = getFromSocket("/activewindow");
EXPECT_CONTAINS(str, "special:apple");
}

OK(getFromSocket("/dispatch togglespecialworkspace apple"));
OK(getFromSocket("/dispatch workspace 1"));

::kill(kitty->pid(), SIGSTOP);

std::this_thread::sleep_for(std::chrono::milliseconds(2000));
Tests::waitUntilWindowsN(2);

{
auto str = getFromSocket("/activeworkspace");
EXPECT_CONTAINS(str, "windows: 0");
}

{
OK(getFromSocket("/dispatch focuswindow class:hyprland-dialog"))
auto str = getFromSocket("/activewindow");
EXPECT_CONTAINS(str, "special:apple");
}
}

OK(getFromSocket("/reload"));
Tests::killAllWindows();
}

static bool test() {
NLog::log("{}Testing config: misc:", Colors::GREEN);

Expand Down Expand Up @@ -204,6 +326,11 @@ static bool test() {
EXPECT_CONTAINS(str, "fullscreen: 2");
}

OK(getFromSocket("/reload"));
Tests::killAllWindows();

testAnrDialogs();

// Ensure that the process autostarted in the config does not
// become a zombie even if it terminates very quickly.
EXPECT(Tests::execAndGet("pgrep -f 'sleep 0'").empty(), true);
Expand Down
11 changes: 11 additions & 0 deletions src/helpers/AsyncDialogBox.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
#include <algorithm>
#include <unistd.h>
#include "../managers/eventLoop/EventLoopManager.hpp"
#include "../desktop/rule/windowRule/WindowRule.hpp"
#include "../desktop/rule/Engine.hpp"

using namespace Hyprutils::OS;

Expand Down Expand Up @@ -119,6 +121,9 @@ SP<CPromise<std::string>> CAsyncDialogBox::open() {

m_selfReference = m_selfWeakReference.lock();

if (!m_execRuleToken.empty())
proc.addEnv(Desktop::Rule::EXEC_RULE_ENV_NAME, m_execRuleToken);

if (!proc.runAsync()) {
Debug::log(ERR, "CAsyncDialogBox::open: failed to run async");
wl_event_source_remove(m_readEventSource);
Expand Down Expand Up @@ -150,3 +155,9 @@ bool CAsyncDialogBox::isRunning() const {
SP<CAsyncDialogBox> CAsyncDialogBox::lockSelf() {
return m_selfWeakReference.lock();
}

void CAsyncDialogBox::setExecRule(std::string&& s) {
auto rule = Desktop::Rule::CWindowRule::buildFromExecString(std::move(s));
m_execRuleToken = rule->execToken();
Desktop::Rule::ruleEngine()->registerRule(std::move(rule));
}
6 changes: 4 additions & 2 deletions src/helpers/AsyncDialogBox.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ class CAsyncDialogBox {
SP<CPromise<std::string>> open();
void kill();
bool isRunning() const;
void setExecRule(std::string&& s);

SP<CAsyncDialogBox> lockSelf();

Expand All @@ -40,7 +41,8 @@ class CAsyncDialogBox {
pid_t m_dialogPid = 0;
wl_event_source* m_readEventSource = nullptr;
Hyprutils::OS::CFileDescriptor m_pipeReadFd;
std::string m_stdout = "";
std::string m_stdout = "";
std::string m_execRuleToken = "";

const std::string m_title;
const std::string m_description;
Expand All @@ -51,4 +53,4 @@ class CAsyncDialogBox {
// WARNING: cyclic reference. This will be removed once the event source is removed to avoid dangling pointers
SP<CAsyncDialogBox> m_selfReference;
WP<CAsyncDialogBox> m_selfWeakReference;
};
};
42 changes: 28 additions & 14 deletions src/managers/ANRManager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -183,21 +183,35 @@ void CANRManager::SANRData::runDialog(const std::string& appName, const std::str

const auto OPTION_TERMINATE_STR = I18n::i18nEngine()->localize(I18n::TXT_KEY_ANR_OPTION_TERMINATE, {});
const auto OPTION_WAIT_STR = I18n::i18nEngine()->localize(I18n::TXT_KEY_ANR_OPTION_WAIT, {});
const auto OPTIONS = std::vector{OPTION_TERMINATE_STR, OPTION_WAIT_STR};
const auto CLASS_STR = appClass.empty() ? I18n::i18nEngine()->localize(I18n::TXT_KEY_ANR_PROP_UNKNOWN, {}) : appClass;
const auto TITLE_STR = appName.empty() ? I18n::i18nEngine()->localize(I18n::TXT_KEY_ANR_PROP_UNKNOWN, {}) : appName;
const auto DESCRIPTION_STR = I18n::i18nEngine()->localize(I18n::TXT_KEY_ANR_CONTENT, {{"title", TITLE_STR}, {"class", CLASS_STR}});

PHLWINDOW parentWindow = nullptr;
for (const auto& w : g_pCompositor->m_windows) {
if (!w->m_isMapped)
continue;

if (!fitsWindow(w))
continue;

parentWindow = w;
break;
}

dialogBox = CAsyncDialogBox::create(I18n::i18nEngine()->localize(I18n::TXT_KEY_ANR_TITLE, {}), DESCRIPTION_STR, OPTIONS);

dialogBox =
CAsyncDialogBox::create(I18n::i18nEngine()->localize(I18n::TXT_KEY_ANR_TITLE, {}),
I18n::i18nEngine()->localize(I18n::TXT_KEY_ANR_CONTENT,
{
//
{"class", appClass.empty() ? I18n::i18nEngine()->localize(I18n::TXT_KEY_ANR_PROP_UNKNOWN, {}) : appClass}, //
{"title", appName.empty() ? I18n::i18nEngine()->localize(I18n::TXT_KEY_ANR_PROP_UNKNOWN, {}) : appName} //
}),
std::vector<std::string>{
//
OPTION_TERMINATE_STR, //
OPTION_WAIT_STR //
} //
);
if (parentWindow) {
const auto WORKSPACEID = parentWindow->workspaceID();

if (parentWindow->onSpecialWorkspace()) {
PHLWORKSPACE parentWorkspace = g_pCompositor->getWorkspaceByID(WORKSPACEID);
if (parentWorkspace)
dialogBox->setExecRule(std::format("workspace {} silent", parentWorkspace->m_name));
} else
dialogBox->setExecRule(std::format("workspace e~{} silent", WORKSPACEID));
}

dialogBox->open()->then([dialogWmPID, this, OPTION_TERMINATE_STR, OPTION_WAIT_STR](SP<CPromiseResult<std::string>> r) {
if (r->hasError()) {
Expand Down
Loading