Skip to content

widgets: add onclick feature #736

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 20 commits into from
May 5, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
7c13816
widget: add click handling and point containment methods to IWidget i…
Memoraike Jan 17, 2025
552ca47
core: add onClick method to handle mouse click events
Memoraike Jan 17, 2025
78e6adc
core: update mouse event handling to track mouse location and button …
Memoraike Jan 17, 2025
aefcaf7
widget: add onclick command handling and point containment to CLabel
Memoraike Jan 17, 2025
d735f63
assets: add label configuration for keyboard layout switching
Memoraike Jan 17, 2025
e261b7d
config: add onclick configuration for label widgets
Memoraike Jan 22, 2025
f46a0b7
core: fix cursor shape initialization and pointer handling
Memoraike Jan 22, 2025
fafef36
core: add hover handling and cursor shape updates
Memoraike Jan 22, 2025
2557a8d
widgets: add hover state management and bounding box calculations
Memoraike Jan 22, 2025
418ca9b
core: add hover handling in pointer motion
Memoraike Jan 22, 2025
e291220
widgets: add hover handling and bounding box for password input field
Memoraike Jan 22, 2025
9893990
widgets: update hover behavior for label widget
Memoraike Jan 24, 2025
e5faefb
core: optimize hover handling and rendering for lock surfaces
Memoraike Jan 24, 2025
45f5e22
pull in changes and fix conflicts
PaideiaDilemma Apr 10, 2025
ca073ed
widgets: add onclick and hover to shape and image
PaideiaDilemma Apr 11, 2025
1fdb336
core: trigger hover and onclick only for the currently focused surface
PaideiaDilemma Apr 11, 2025
b392f71
core: handle fractionalScale in onclick and hover
PaideiaDilemma Apr 11, 2025
216015c
core: don't trigger onclick or hover when hide_cursor is set
PaideiaDilemma Apr 11, 2025
d28d2d8
misc: remove braces
PaideiaDilemma Apr 16, 2025
59f6cb5
core: run onclick commands asnychronously
PaideiaDilemma Apr 28, 2025
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
13 changes: 12 additions & 1 deletion assets/example.conf
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
$font = Monospace

general {
hide_cursor = true
hide_cursor = false
}

# uncomment to enable fingerprint authentication
Expand Down Expand Up @@ -90,3 +90,14 @@ label {
halign = right
valign = top
}

label {
monitor =
text = $LAYOUT[en,ru]
font_size = 24
onclick = hyprctl switchxkblayout all next

position = 250, -20
halign = center
valign = center
}
12 changes: 12 additions & 0 deletions src/config/ConfigManager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,9 @@ void CConfigManager::init() {
m_config.addSpecialConfigValue(name, "shadow_passes", Hyprlang::INT{0}); \
m_config.addSpecialConfigValue(name, "shadow_color", Hyprlang::INT{0xFF000000}); \
m_config.addSpecialConfigValue(name, "shadow_boost", Hyprlang::FLOAT{1.2});

#define CLICKABLE(name) m_config.addSpecialConfigValue(name, "onclick", Hyprlang::STRING{""});

m_config.addConfigValue("general:text_trim", Hyprlang::INT{1});
m_config.addConfigValue("general:hide_cursor", Hyprlang::INT{0});
m_config.addConfigValue("general:grace", Hyprlang::INT{0});
Expand Down Expand Up @@ -257,6 +260,7 @@ void CConfigManager::init() {
m_config.addSpecialConfigValue("shape", "xray", Hyprlang::INT{0});
m_config.addSpecialConfigValue("shape", "zindex", Hyprlang::INT{0});
SHADOWABLE("shape");
CLICKABLE("shape");

m_config.addSpecialCategory("image", Hyprlang::SSpecialCategoryOptions{.key = nullptr, .anonymousKeyBased = true});
m_config.addSpecialConfigValue("image", "monitor", Hyprlang::STRING{""});
Expand All @@ -273,6 +277,7 @@ void CConfigManager::init() {
m_config.addSpecialConfigValue("image", "reload_cmd", Hyprlang::STRING{""});
m_config.addSpecialConfigValue("image", "zindex", Hyprlang::INT{0});
SHADOWABLE("image");
CLICKABLE("image");

m_config.addSpecialCategory("input-field", Hyprlang::SSpecialCategoryOptions{.key = nullptr, .anonymousKeyBased = true});
m_config.addSpecialConfigValue("input-field", "monitor", Hyprlang::STRING{""});
Expand Down Expand Up @@ -320,6 +325,7 @@ void CConfigManager::init() {
m_config.addSpecialConfigValue("label", "text_align", Hyprlang::STRING{""});
m_config.addSpecialConfigValue("label", "zindex", Hyprlang::INT{0});
SHADOWABLE("label");
CLICKABLE("label");

m_config.registerHandler(&::handleSource, "source", {.allowFlags = false});
m_config.registerHandler(&::handleBezier, "bezier", {.allowFlags = false});
Expand Down Expand Up @@ -356,6 +362,7 @@ void CConfigManager::init() {
Debug::log(ERR, "Config has errors:\n{}\nProceeding ignoring faulty entries", result.getError());

#undef SHADOWABLE
#undef CLICKABLE
}

std::vector<CConfigManager::SWidgetConfig> CConfigManager::getWidgetConfigs() {
Expand All @@ -367,6 +374,8 @@ std::vector<CConfigManager::SWidgetConfig> CConfigManager::getWidgetConfigs() {
"shadow_boost", m_config.getSpecialConfigValue(name, "shadow_boost", k.c_str()) \
}

#define CLICKABLE(name) {"onclick", m_config.getSpecialConfigValue(name, "onclick", k.c_str())}

//
auto keys = m_config.listKeysForSpecialCategory("background");
result.reserve(keys.size());
Expand Down Expand Up @@ -414,6 +423,7 @@ std::vector<CConfigManager::SWidgetConfig> CConfigManager::getWidgetConfigs() {
{"xray", m_config.getSpecialConfigValue("shape", "xray", k.c_str())},
{"zindex", m_config.getSpecialConfigValue("shape", "zindex", k.c_str())},
SHADOWABLE("shape"),
CLICKABLE("shape"),
}
});
// clang-format on
Expand All @@ -440,6 +450,7 @@ std::vector<CConfigManager::SWidgetConfig> CConfigManager::getWidgetConfigs() {
{"reload_cmd", m_config.getSpecialConfigValue("image", "reload_cmd", k.c_str())},
{"zindex", m_config.getSpecialConfigValue("image", "zindex", k.c_str())},
SHADOWABLE("image"),
CLICKABLE("image"),
}
});
// clang-format on
Expand Down Expand Up @@ -505,6 +516,7 @@ std::vector<CConfigManager::SWidgetConfig> CConfigManager::getWidgetConfigs() {
{"text_align", m_config.getSpecialConfigValue("label", "text_align", k.c_str())},
{"zindex", m_config.getSpecialConfigValue("label", "zindex", k.c_str())},
SHADOWABLE("label"),
CLICKABLE("label"),
}
});
// clang-format on
Expand Down
7 changes: 5 additions & 2 deletions src/core/CursorShape.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,15 @@ CCursorShape::CCursorShape(SP<CCWpCursorShapeManagerV1> mgr) : mgr(mgr) {
}

void CCursorShape::setShape(const wpCursorShapeDeviceV1Shape shape) {
if (!dev)
if (!g_pSeatManager->m_pPointer)
return;

if (!dev)
dev = makeShared<CCWpCursorShapeDeviceV1>(mgr->sendGetPointer(g_pSeatManager->m_pPointer->resource()));

dev->sendSetShape(lastCursorSerial, shape);
}

void CCursorShape::hideCursor() {
g_pSeatManager->m_pPointer->sendSetCursor(lastCursorSerial, nullptr, 0, 0);
}
}
4 changes: 4 additions & 0 deletions src/core/LockSurface.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -139,3 +139,7 @@ void CSessionLockSurface::onCallback() {
render();
}
}

SP<CCWlSurface> CSessionLockSurface::getWlSurface() {
return surface;
}
13 changes: 7 additions & 6 deletions src/core/LockSurface.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -17,15 +17,16 @@ class CSessionLockSurface {
CSessionLockSurface(const SP<COutput>& pOutput);
~CSessionLockSurface();

void configure(const Vector2D& size, uint32_t serial);
void configure(const Vector2D& size, uint32_t serial);

bool readyForFrame = false;
bool readyForFrame = false;

float fractionalScale = 1.0;
float fractionalScale = 1.0;

void render();
void onCallback();
void onScaleUpdate();
void render();
void onCallback();
void onScaleUpdate();
SP<CCWlSurface> getWlSurface();

private:
WP<COutput> m_outputRef;
Expand Down
30 changes: 27 additions & 3 deletions src/core/Seat.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,13 @@ void CSeatManager::registerSeat(SP<CCWlSeat> seat) {
if (caps & WL_SEAT_CAPABILITY_POINTER) {
m_pPointer = makeShared<CCWlPointer>(r->sendGetPointer());

static const auto HIDECURSOR = g_pConfigManager->getValue<Hyprlang::INT>("general:hide_cursor");
m_pPointer->setMotion([](CCWlPointer* r, uint32_t time, wl_fixed_t surface_x, wl_fixed_t surface_y) {
g_pHyprlock->m_vMouseLocation = {wl_fixed_to_double(surface_x), wl_fixed_to_double(surface_y)};

if (!*HIDECURSOR)
g_pHyprlock->onHover(g_pHyprlock->m_vMouseLocation);

if (std::chrono::system_clock::now() > g_pHyprlock->m_tGraceEnds)
return;

Expand All @@ -40,16 +46,34 @@ void CSeatManager::registerSeat(SP<CCWlSeat> seat) {
if (!m_pCursorShape)
return;

static const auto HIDE = g_pConfigManager->getValue<Hyprlang::INT>("general:hide_cursor");

m_pCursorShape->lastCursorSerial = serial;

if (*HIDE)
if (*HIDECURSOR)
m_pCursorShape->hideCursor();
else
m_pCursorShape->setShape(wpCursorShapeDeviceV1Shape::WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_DEFAULT);

g_pHyprlock->m_vLastEnterCoords = {wl_fixed_to_double(surface_x), wl_fixed_to_double(surface_y)};

if (*HIDECURSOR)
return;

for (const auto& POUTPUT : g_pHyprlock->m_vOutputs) {
if (!POUTPUT->m_sessionLockSurface)
continue;

const auto& PWLSURFACE = POUTPUT->m_sessionLockSurface->getWlSurface();
if (PWLSURFACE->resource() == surf)
g_pHyprlock->m_focusedOutput = POUTPUT;
}
});

m_pPointer->setLeave([](CCWlPointer* r, uint32_t serial, wl_proxy* surf) { g_pHyprlock->m_focusedOutput.reset(); });

m_pPointer->setButton([](CCWlPointer* r, uint32_t serial, uint32_t time, uint32_t button, wl_pointer_button_state state) {
if (*HIDECURSOR)
return;
g_pHyprlock->onClick(button, state == WL_POINTER_BUTTON_STATE_PRESSED, g_pHyprlock->m_vMouseLocation);
});
}

Expand Down
68 changes: 55 additions & 13 deletions src/core/hyprlock.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -668,6 +668,61 @@ void CHyprlock::handleKeySym(xkb_keysym_t sym, bool composed) {
}
}

void CHyprlock::onClick(uint32_t button, bool down, const Vector2D& pos) {
if (!m_focusedOutput.lock())
return;

// TODO: add the UNLIKELY marco from Hyprland
if (!m_focusedOutput->m_sessionLockSurface)
return;

const auto SCALEDPOS = pos * m_focusedOutput->m_sessionLockSurface->fractionalScale;
const auto widgets = g_pRenderer->getOrCreateWidgetsFor(*m_focusedOutput->m_sessionLockSurface);
for (const auto& widget : widgets) {
if (widget->containsPoint(SCALEDPOS))
widget->onClick(button, down, pos);
}
}

void CHyprlock::onHover(const Vector2D& pos) {
if (!m_focusedOutput.lock())
return;

if (!m_focusedOutput->m_sessionLockSurface)
return;

bool outputNeedsRedraw = false;
bool cursorChanged = false;

const auto SCALEDPOS = pos * m_focusedOutput->m_sessionLockSurface->fractionalScale;
const auto widgets = g_pRenderer->getOrCreateWidgetsFor(*m_focusedOutput->m_sessionLockSurface);
for (const auto& widget : widgets) {
const bool CONTAINSPOINT = widget->containsPoint(SCALEDPOS);
const bool HOVERED = widget->isHovered();

if (CONTAINSPOINT) {
if (!HOVERED) {
widget->setHover(true);
widget->onHover(pos);
outputNeedsRedraw = true;
}

if (!cursorChanged)
cursorChanged = true;

} else if (HOVERED) {
widget->setHover(false);
outputNeedsRedraw = true;
}
}

if (!cursorChanged)
g_pSeatManager->m_pCursorShape->setShape(WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_DEFAULT);

if (outputNeedsRedraw)
m_focusedOutput->m_sessionLockSurface->render();
}

bool CHyprlock::acquireSessionLock() {
Debug::log(LOG, "Locking session");
m_sLockState.lock = makeShared<CCExtSessionLockV1>(m_sWaylandState.sessionLock->sendLock());
Expand Down Expand Up @@ -816,19 +871,6 @@ void CHyprlock::enqueueForceUpdateTimers() {
nullptr, false);
}

std::string CHyprlock::spawnSync(const std::string& cmd) {
CProcess proc("/bin/sh", {"-c", cmd});
if (!proc.runSync()) {
Debug::log(ERR, "Failed to run \"{}\"", cmd);
return "";
}

if (!proc.stdErr().empty())
Debug::log(ERR, "Shell command \"{}\" STDERR:\n{}", cmd, proc.stdErr());

return proc.stdOut();
}

SP<CCZwlrScreencopyManagerV1> CHyprlock::getScreencopy() {
return m_sWaylandState.screencopy;
}
Expand Down
7 changes: 5 additions & 2 deletions src/core/hyprlock.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -48,9 +48,9 @@ class CHyprlock {
bool acquireSessionLock();
void releaseSessionLock();

std::string spawnSync(const std::string& cmd);

void onKey(uint32_t key, bool down);
void onClick(uint32_t button, bool down, const Vector2D& pos);
void onHover(const Vector2D& pos);
void startKeyRepeat(xkb_keysym_t sym);
void repeatKey(xkb_keysym_t sym);
void handleKeySym(xkb_keysym_t sym, bool compose);
Expand Down Expand Up @@ -97,6 +97,9 @@ class CHyprlock {
//
std::chrono::system_clock::time_point m_tGraceEnds;
Vector2D m_vLastEnterCoords = {};
WP<COutput> m_focusedOutput;

Vector2D m_vMouseLocation = {};

std::shared_ptr<CTimer> m_pKeyRepeatTimer = nullptr;

Expand Down
21 changes: 21 additions & 0 deletions src/helpers/MiscFunctions.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,11 @@
#include "MiscFunctions.hpp"
#include "Log.hpp"
#include <hyprutils/string/String.hpp>
#include <hyprutils/os/Process.hpp>
#include <unistd.h>

using namespace Hyprutils::String;
using namespace Hyprutils::OS;

std::string absolutePath(const std::string& rawpath, const std::string& currentDir) {
std::filesystem::path path(rawpath);
Expand Down Expand Up @@ -137,3 +139,22 @@ int createPoolFile(size_t size, std::string& name) {

return FD;
}

std::string spawnSync(const std::string& cmd) {
CProcess proc("/bin/sh", {"-c", cmd});
if (!proc.runSync()) {
Debug::log(ERR, "Failed to run \"{}\"", cmd);
return "";
}

if (!proc.stdErr().empty())
Debug::log(ERR, "Shell command \"{}\" STDERR:\n{}", cmd, proc.stdErr());

return proc.stdOut();
}

void spawnAsync(const std::string& cmd) {
CProcess proc("/bin/sh", {"-c", cmd});
if (!proc.runAsync())
Debug::log(ERR, "Failed to start \"{}\"", cmd);
}
2 changes: 2 additions & 0 deletions src/helpers/MiscFunctions.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,5 @@
std::string absolutePath(const std::string&, const std::string&);
int64_t configStringToInt(const std::string& VALUE);
int createPoolFile(size_t size, std::string& name);
std::string spawnSync(const std::string& cmd);
void spawnAsync(const std::string& cmd);
2 changes: 1 addition & 1 deletion src/renderer/AsyncResourceGatherer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -218,7 +218,7 @@ void CAsyncResourceGatherer::renderText(const SPreloadRequest& rq) {
const bool ISCMD = rq.props.contains("cmd") ? std::any_cast<bool>(rq.props.at("cmd")) : false;

static const auto TRIM = g_pConfigManager->getValue<Hyprlang::INT>("general:text_trim");
std::string text = ISCMD ? g_pHyprlock->spawnSync(rq.asset) : rq.asset;
std::string text = ISCMD ? spawnSync(rq.asset) : rq.asset;

if (*TRIM) {
text.erase(0, text.find_first_not_of(" \n\r\t"));
Expand Down
4 changes: 2 additions & 2 deletions src/renderer/Framebuffer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,6 @@ CFramebuffer::~CFramebuffer() {
release();
}

bool CFramebuffer::isAllocated() {
bool CFramebuffer::isAllocated() const {
return m_iFb != (GLuint)-1;
}
}
4 changes: 2 additions & 2 deletions src/renderer/Framebuffer.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,12 @@ class CFramebuffer {
void bind() const;
void release();
void reset();
bool isAllocated();
bool isAllocated() const;

Vector2D m_vSize;

CTexture m_cTex;
GLuint m_iFb = -1;

CTexture* m_pStencilTex = nullptr;
};
};
Loading