Skip to content

Commit e3bd47e

Browse files
widgets: add onclick feature (#736)
* widget: add click handling and point containment methods to IWidget interface * core: add onClick method to handle mouse click events - renderer: move getOrCreateWidgetsFor method declaration to public section * core: update mouse event handling to track mouse location and button clicks * widget: add onclick command handling and point containment to CLabel - config: add onclick special config value to label * assets: add label configuration for keyboard layout switching * config: add onclick configuration for label widgets - add CLICKABLE macro for onclick configuration - replace direct onclick assignment with CLICKABLE macro * core: fix cursor shape initialization and pointer handling - ensure pointer is available before setting cursor shape - initialize cursor shape device if not already done * core: add hover handling and cursor shape updates - implement onHover method to manage widget hover states - update cursor shape based on hover status - ensure all outputs are redrawn after state changes * widgets: add hover state management and bounding box calculations - add setHover and isHovered methods to manage hover state - implement containsPoint method for hit testing - override getBoundingBox in CLabel for accurate positioning - add onHover method in CLabel to change cursor shape * core: add hover handling in pointer motion - invoke onHover method with current mouse location * widgets: add hover handling and bounding box for password input field - add getBoundingBox method to calculate the widget's bounding box - implement onHover method to update cursor shape on hover * widgets: update hover behavior for label widget - modify cursor shape setting to only apply when onclickCommand is not empty * core: optimize hover handling and rendering for lock surfaces - Improve hover state tracking for widgets - reduce unnecessary redraw calls by tracking hover changes - remove redundant renderAllOutputs() call * widgets: add onclick and hover to shape and image * core: trigger hover and onclick only for the currently focused surface * core: handle fractionalScale in onclick and hover * core: don't trigger onclick or hover when hide_cursor is set * misc: remove braces * core: run onclick commands asnychronously --------- Co-authored-by: Memoraike <[email protected]>
1 parent 6c64630 commit e3bd47e

25 files changed

+313
-75
lines changed

assets/example.conf

+12-1
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
$font = Monospace
1010

1111
general {
12-
hide_cursor = true
12+
hide_cursor = false
1313
}
1414

1515
# uncomment to enable fingerprint authentication
@@ -90,3 +90,14 @@ label {
9090
halign = right
9191
valign = top
9292
}
93+
94+
label {
95+
monitor =
96+
text = $LAYOUT[en,ru]
97+
font_size = 24
98+
onclick = hyprctl switchxkblayout all next
99+
100+
position = 250, -20
101+
halign = center
102+
valign = center
103+
}

src/config/ConfigManager.cpp

+12
Original file line numberDiff line numberDiff line change
@@ -209,6 +209,9 @@ void CConfigManager::init() {
209209
m_config.addSpecialConfigValue(name, "shadow_passes", Hyprlang::INT{0}); \
210210
m_config.addSpecialConfigValue(name, "shadow_color", Hyprlang::INT{0xFF000000}); \
211211
m_config.addSpecialConfigValue(name, "shadow_boost", Hyprlang::FLOAT{1.2});
212+
213+
#define CLICKABLE(name) m_config.addSpecialConfigValue(name, "onclick", Hyprlang::STRING{""});
214+
212215
m_config.addConfigValue("general:text_trim", Hyprlang::INT{1});
213216
m_config.addConfigValue("general:hide_cursor", Hyprlang::INT{0});
214217
m_config.addConfigValue("general:grace", Hyprlang::INT{0});
@@ -257,6 +260,7 @@ void CConfigManager::init() {
257260
m_config.addSpecialConfigValue("shape", "xray", Hyprlang::INT{0});
258261
m_config.addSpecialConfigValue("shape", "zindex", Hyprlang::INT{0});
259262
SHADOWABLE("shape");
263+
CLICKABLE("shape");
260264

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

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

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

358364
#undef SHADOWABLE
365+
#undef CLICKABLE
359366
}
360367

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

377+
#define CLICKABLE(name) {"onclick", m_config.getSpecialConfigValue(name, "onclick", k.c_str())}
378+
370379
//
371380
auto keys = m_config.listKeysForSpecialCategory("background");
372381
result.reserve(keys.size());
@@ -414,6 +423,7 @@ std::vector<CConfigManager::SWidgetConfig> CConfigManager::getWidgetConfigs() {
414423
{"xray", m_config.getSpecialConfigValue("shape", "xray", k.c_str())},
415424
{"zindex", m_config.getSpecialConfigValue("shape", "zindex", k.c_str())},
416425
SHADOWABLE("shape"),
426+
CLICKABLE("shape"),
417427
}
418428
});
419429
// clang-format on
@@ -440,6 +450,7 @@ std::vector<CConfigManager::SWidgetConfig> CConfigManager::getWidgetConfigs() {
440450
{"reload_cmd", m_config.getSpecialConfigValue("image", "reload_cmd", k.c_str())},
441451
{"zindex", m_config.getSpecialConfigValue("image", "zindex", k.c_str())},
442452
SHADOWABLE("image"),
453+
CLICKABLE("image"),
443454
}
444455
});
445456
// clang-format on
@@ -505,6 +516,7 @@ std::vector<CConfigManager::SWidgetConfig> CConfigManager::getWidgetConfigs() {
505516
{"text_align", m_config.getSpecialConfigValue("label", "text_align", k.c_str())},
506517
{"zindex", m_config.getSpecialConfigValue("label", "zindex", k.c_str())},
507518
SHADOWABLE("label"),
519+
CLICKABLE("label"),
508520
}
509521
});
510522
// clang-format on

src/core/CursorShape.cpp

+5-2
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,15 @@ CCursorShape::CCursorShape(SP<CCWpCursorShapeManagerV1> mgr) : mgr(mgr) {
99
}
1010

1111
void CCursorShape::setShape(const wpCursorShapeDeviceV1Shape shape) {
12-
if (!dev)
12+
if (!g_pSeatManager->m_pPointer)
1313
return;
1414

15+
if (!dev)
16+
dev = makeShared<CCWpCursorShapeDeviceV1>(mgr->sendGetPointer(g_pSeatManager->m_pPointer->resource()));
17+
1518
dev->sendSetShape(lastCursorSerial, shape);
1619
}
1720

1821
void CCursorShape::hideCursor() {
1922
g_pSeatManager->m_pPointer->sendSetCursor(lastCursorSerial, nullptr, 0, 0);
20-
}
23+
}

src/core/LockSurface.cpp

+4
Original file line numberDiff line numberDiff line change
@@ -139,3 +139,7 @@ void CSessionLockSurface::onCallback() {
139139
render();
140140
}
141141
}
142+
143+
SP<CCWlSurface> CSessionLockSurface::getWlSurface() {
144+
return surface;
145+
}

src/core/LockSurface.hpp

+7-6
Original file line numberDiff line numberDiff line change
@@ -17,15 +17,16 @@ class CSessionLockSurface {
1717
CSessionLockSurface(const SP<COutput>& pOutput);
1818
~CSessionLockSurface();
1919

20-
void configure(const Vector2D& size, uint32_t serial);
20+
void configure(const Vector2D& size, uint32_t serial);
2121

22-
bool readyForFrame = false;
22+
bool readyForFrame = false;
2323

24-
float fractionalScale = 1.0;
24+
float fractionalScale = 1.0;
2525

26-
void render();
27-
void onCallback();
28-
void onScaleUpdate();
26+
void render();
27+
void onCallback();
28+
void onScaleUpdate();
29+
SP<CCWlSurface> getWlSurface();
2930

3031
private:
3132
WP<COutput> m_outputRef;

src/core/Seat.cpp

+27-3
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,13 @@ void CSeatManager::registerSeat(SP<CCWlSeat> seat) {
2626
if (caps & WL_SEAT_CAPABILITY_POINTER) {
2727
m_pPointer = makeShared<CCWlPointer>(r->sendGetPointer());
2828

29+
static const auto HIDECURSOR = g_pConfigManager->getValue<Hyprlang::INT>("general:hide_cursor");
2930
m_pPointer->setMotion([](CCWlPointer* r, uint32_t time, wl_fixed_t surface_x, wl_fixed_t surface_y) {
31+
g_pHyprlock->m_vMouseLocation = {wl_fixed_to_double(surface_x), wl_fixed_to_double(surface_y)};
32+
33+
if (!*HIDECURSOR)
34+
g_pHyprlock->onHover(g_pHyprlock->m_vMouseLocation);
35+
3036
if (std::chrono::system_clock::now() > g_pHyprlock->m_tGraceEnds)
3137
return;
3238

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

43-
static const auto HIDE = g_pConfigManager->getValue<Hyprlang::INT>("general:hide_cursor");
44-
4549
m_pCursorShape->lastCursorSerial = serial;
4650

47-
if (*HIDE)
51+
if (*HIDECURSOR)
4852
m_pCursorShape->hideCursor();
4953
else
5054
m_pCursorShape->setShape(wpCursorShapeDeviceV1Shape::WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_DEFAULT);
5155

5256
g_pHyprlock->m_vLastEnterCoords = {wl_fixed_to_double(surface_x), wl_fixed_to_double(surface_y)};
57+
58+
if (*HIDECURSOR)
59+
return;
60+
61+
for (const auto& POUTPUT : g_pHyprlock->m_vOutputs) {
62+
if (!POUTPUT->m_sessionLockSurface)
63+
continue;
64+
65+
const auto& PWLSURFACE = POUTPUT->m_sessionLockSurface->getWlSurface();
66+
if (PWLSURFACE->resource() == surf)
67+
g_pHyprlock->m_focusedOutput = POUTPUT;
68+
}
69+
});
70+
71+
m_pPointer->setLeave([](CCWlPointer* r, uint32_t serial, wl_proxy* surf) { g_pHyprlock->m_focusedOutput.reset(); });
72+
73+
m_pPointer->setButton([](CCWlPointer* r, uint32_t serial, uint32_t time, uint32_t button, wl_pointer_button_state state) {
74+
if (*HIDECURSOR)
75+
return;
76+
g_pHyprlock->onClick(button, state == WL_POINTER_BUTTON_STATE_PRESSED, g_pHyprlock->m_vMouseLocation);
5377
});
5478
}
5579

src/core/hyprlock.cpp

+55-13
Original file line numberDiff line numberDiff line change
@@ -669,6 +669,61 @@ void CHyprlock::handleKeySym(xkb_keysym_t sym, bool composed) {
669669
}
670670
}
671671

672+
void CHyprlock::onClick(uint32_t button, bool down, const Vector2D& pos) {
673+
if (!m_focusedOutput.lock())
674+
return;
675+
676+
// TODO: add the UNLIKELY marco from Hyprland
677+
if (!m_focusedOutput->m_sessionLockSurface)
678+
return;
679+
680+
const auto SCALEDPOS = pos * m_focusedOutput->m_sessionLockSurface->fractionalScale;
681+
const auto widgets = g_pRenderer->getOrCreateWidgetsFor(*m_focusedOutput->m_sessionLockSurface);
682+
for (const auto& widget : widgets) {
683+
if (widget->containsPoint(SCALEDPOS))
684+
widget->onClick(button, down, pos);
685+
}
686+
}
687+
688+
void CHyprlock::onHover(const Vector2D& pos) {
689+
if (!m_focusedOutput.lock())
690+
return;
691+
692+
if (!m_focusedOutput->m_sessionLockSurface)
693+
return;
694+
695+
bool outputNeedsRedraw = false;
696+
bool cursorChanged = false;
697+
698+
const auto SCALEDPOS = pos * m_focusedOutput->m_sessionLockSurface->fractionalScale;
699+
const auto widgets = g_pRenderer->getOrCreateWidgetsFor(*m_focusedOutput->m_sessionLockSurface);
700+
for (const auto& widget : widgets) {
701+
const bool CONTAINSPOINT = widget->containsPoint(SCALEDPOS);
702+
const bool HOVERED = widget->isHovered();
703+
704+
if (CONTAINSPOINT) {
705+
if (!HOVERED) {
706+
widget->setHover(true);
707+
widget->onHover(pos);
708+
outputNeedsRedraw = true;
709+
}
710+
711+
if (!cursorChanged)
712+
cursorChanged = true;
713+
714+
} else if (HOVERED) {
715+
widget->setHover(false);
716+
outputNeedsRedraw = true;
717+
}
718+
}
719+
720+
if (!cursorChanged)
721+
g_pSeatManager->m_pCursorShape->setShape(WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_DEFAULT);
722+
723+
if (outputNeedsRedraw)
724+
m_focusedOutput->m_sessionLockSurface->render();
725+
}
726+
672727
bool CHyprlock::acquireSessionLock() {
673728
Debug::log(LOG, "Locking session");
674729
m_sLockState.lock = makeShared<CCExtSessionLockV1>(m_sWaylandState.sessionLock->sendLock());
@@ -817,19 +872,6 @@ void CHyprlock::enqueueForceUpdateTimers() {
817872
nullptr, false);
818873
}
819874

820-
std::string CHyprlock::spawnSync(const std::string& cmd) {
821-
CProcess proc("/bin/sh", {"-c", cmd});
822-
if (!proc.runSync()) {
823-
Debug::log(ERR, "Failed to run \"{}\"", cmd);
824-
return "";
825-
}
826-
827-
if (!proc.stdErr().empty())
828-
Debug::log(ERR, "Shell command \"{}\" STDERR:\n{}", cmd, proc.stdErr());
829-
830-
return proc.stdOut();
831-
}
832-
833875
SP<CCZwlrScreencopyManagerV1> CHyprlock::getScreencopy() {
834876
return m_sWaylandState.screencopy;
835877
}

src/core/hyprlock.hpp

+5-2
Original file line numberDiff line numberDiff line change
@@ -48,9 +48,9 @@ class CHyprlock {
4848
bool acquireSessionLock();
4949
void releaseSessionLock();
5050

51-
std::string spawnSync(const std::string& cmd);
52-
5351
void onKey(uint32_t key, bool down);
52+
void onClick(uint32_t button, bool down, const Vector2D& pos);
53+
void onHover(const Vector2D& pos);
5454
void startKeyRepeat(xkb_keysym_t sym);
5555
void repeatKey(xkb_keysym_t sym);
5656
void handleKeySym(xkb_keysym_t sym, bool compose);
@@ -95,6 +95,9 @@ class CHyprlock {
9595
//
9696
std::chrono::system_clock::time_point m_tGraceEnds;
9797
Vector2D m_vLastEnterCoords = {};
98+
WP<COutput> m_focusedOutput;
99+
100+
Vector2D m_vMouseLocation = {};
98101

99102
std::shared_ptr<CTimer> m_pKeyRepeatTimer = nullptr;
100103

src/helpers/MiscFunctions.cpp

+21
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,11 @@
55
#include "MiscFunctions.hpp"
66
#include "Log.hpp"
77
#include <hyprutils/string/String.hpp>
8+
#include <hyprutils/os/Process.hpp>
89
#include <unistd.h>
910

1011
using namespace Hyprutils::String;
12+
using namespace Hyprutils::OS;
1113

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

138140
return FD;
139141
}
142+
143+
std::string spawnSync(const std::string& cmd) {
144+
CProcess proc("/bin/sh", {"-c", cmd});
145+
if (!proc.runSync()) {
146+
Debug::log(ERR, "Failed to run \"{}\"", cmd);
147+
return "";
148+
}
149+
150+
if (!proc.stdErr().empty())
151+
Debug::log(ERR, "Shell command \"{}\" STDERR:\n{}", cmd, proc.stdErr());
152+
153+
return proc.stdOut();
154+
}
155+
156+
void spawnAsync(const std::string& cmd) {
157+
CProcess proc("/bin/sh", {"-c", cmd});
158+
if (!proc.runAsync())
159+
Debug::log(ERR, "Failed to start \"{}\"", cmd);
160+
}

src/helpers/MiscFunctions.hpp

+2
Original file line numberDiff line numberDiff line change
@@ -7,3 +7,5 @@
77
std::string absolutePath(const std::string&, const std::string&);
88
int64_t configStringToInt(const std::string& VALUE);
99
int createPoolFile(size_t size, std::string& name);
10+
std::string spawnSync(const std::string& cmd);
11+
void spawnAsync(const std::string& cmd);

src/renderer/AsyncResourceGatherer.cpp

+1-1
Original file line numberDiff line numberDiff line change
@@ -218,7 +218,7 @@ void CAsyncResourceGatherer::renderText(const SPreloadRequest& rq) {
218218
const bool ISCMD = rq.props.contains("cmd") ? std::any_cast<bool>(rq.props.at("cmd")) : false;
219219

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

223223
if (*TRIM) {
224224
text.erase(0, text.find_first_not_of(" \n\r\t"));

src/renderer/Framebuffer.cpp

+2-2
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,6 @@ CFramebuffer::~CFramebuffer() {
117117
release();
118118
}
119119

120-
bool CFramebuffer::isAllocated() {
120+
bool CFramebuffer::isAllocated() const {
121121
return m_iFb != (GLuint)-1;
122-
}
122+
}

src/renderer/Framebuffer.hpp

+2-2
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,12 @@ class CFramebuffer {
1313
void bind() const;
1414
void release();
1515
void reset();
16-
bool isAllocated();
16+
bool isAllocated() const;
1717

1818
Vector2D m_vSize;
1919

2020
CTexture m_cTex;
2121
GLuint m_iFb = -1;
2222

2323
CTexture* m_pStencilTex = nullptr;
24-
};
24+
};

0 commit comments

Comments
 (0)