add kbselecti and update README

This commit is contained in:
sandwich 2025-10-10 14:59:05 +02:00
parent faa6b41a73
commit 46ca043fbe
3 changed files with 150 additions and 34 deletions

View file

@ -14,7 +14,7 @@ plugin {
bg_col = rgb(111111)
workspace_method = center current # [center/first] [workspace] e.g. first 1 or center m+1
gesture_distance = 300 # how far is the "max" for the gesture
gesture_distance = 200 # how far is the "max" for the gesture
gaps_out = 0 # outer margin (px)
}
}
@ -39,6 +39,8 @@ plugin {
# numbers (labels)
label_enable = 1
label_font_size = 16
# content mode: token (default) | id | index
label_text_mode = token
# positioning
label_position = top-left # top-left|top-right|bottom-left|bottom-right|center
label_offset_x = 6
@ -54,10 +56,20 @@ plugin {
label_scale_hover = 1.0
label_scale_focus = 1.0
# label background bubble
label_bg_enable = 0
label_bg_enable = 1
label_bg_color = rgba(00000088)
label_bg_rounding = 4
label_bg_shape = circle # circle|square|rounded
label_bg_rounding = 8 # used for rounded
label_padding = 4
# font + precision
label_font_family = Sans
label_font_bold = 0
label_font_italic = 0
label_text_underline = 0
label_text_strikethrough = 0
label_pixel_snap = 1
# optional: override tokens (up to 50, comma-separated). Empty entries allowed.
# label_token_map = "1,2,3,4,5,6,7,8,9,0,!,@,#,$,%,^,&,*,(,),a,b,c,..."
}
}
```
@ -69,10 +81,10 @@ plugin {
| columns | number | how many desktops are displayed on one line | `3` |
| gaps_in | number | inner gaps between tiles | `5` |
| gaps_out | number | outer margin around the grid | `0` |
| bg_col | color | color in gaps (between desktops) | `rgb(000000)` |
| bg_col | color | color in gaps (between desktops) | `rgb(111111)` |
| workspace_method | [center/first] [workspace] | position of the desktops | `center current` |
| skip_empty | boolean | whether the grid displays workspaces sequentially by id using selector "r" (`false`) or skips empty workspaces using selector "m" (`true`) | `false` |
| gesture_distance | number | how far is the max for the gesture | `300` |
| gesture_distance | number | how far is the max for the gesture | `200` |
#### Subcategory `scrolling`
@ -94,29 +106,70 @@ Applies to the scrolling layout overview
bind = MODIFIER, KEY, hyprexpo:expo, OPTION
```
Example:
Example:
```bash
# This will toggle HyprExpo when SUPER+g is pressed
bind = SUPER, g, hyprexpo:expo, toggle
# Optional submap for keyboard selection when Hyprexpo is active
# Optional submap for keyboard selection when Hyprexpo is active (index-based, 1..46)
submap = hyprexpo
binde = , left, hyprexpo:kb_focus, left
binde = , right, hyprexpo:kb_focus, right
binde = , up, hyprexpo:kb_focus, up
binde = , down, hyprexpo:kb_focus, down
binde = , return, hyprexpo:kb_confirm
# number selection by workspace id
binde = , 1, hyprexpo:kb_selectn, 1
binde = , 2, hyprexpo:kb_selectn, 2
binde = , 3, hyprexpo:kb_selectn, 3
binde = , 4, hyprexpo:kb_selectn, 4
binde = , 5, hyprexpo:kb_selectn, 5
binde = , 6, hyprexpo:kb_selectn, 6
binde = , 7, hyprexpo:kb_selectn, 7
binde = , 8, hyprexpo:kb_selectn, 8
binde = , 9, hyprexpo:kb_selectn, 9
binde = , 0, hyprexpo:kb_selectn, 0 # 0 -> 10
bind = , left, hyprexpo:kb_focus, left
bind = , right, hyprexpo:kb_focus, right
bind = , up, hyprexpo:kb_focus, up
bind = , down, hyprexpo:kb_focus, down
bind = , return, hyprexpo:kb_confirm
# 1..10 via digits (0 -> 10)
bind = , 1, hyprexpo:kb_selecti, 1
bind = , 2, hyprexpo:kb_selecti, 2
bind = , 3, hyprexpo:kb_selecti, 3
bind = , 4, hyprexpo:kb_selecti, 4
bind = , 5, hyprexpo:kb_selecti, 5
bind = , 6, hyprexpo:kb_selecti, 6
bind = , 7, hyprexpo:kb_selecti, 7
bind = , 8, hyprexpo:kb_selecti, 8
bind = , 9, hyprexpo:kb_selecti, 9
bind = , 0, hyprexpo:kb_selecti, 10
# 11..20 via SHIFT+digits
bind = SHIFT, 1, hyprexpo:kb_selecti, 11
bind = SHIFT, 2, hyprexpo:kb_selecti, 12
bind = SHIFT, 3, hyprexpo:kb_selecti, 13
bind = SHIFT, 4, hyprexpo:kb_selecti, 14
bind = SHIFT, 5, hyprexpo:kb_selecti, 15
bind = SHIFT, 6, hyprexpo:kb_selecti, 16
bind = SHIFT, 7, hyprexpo:kb_selecti, 17
bind = SHIFT, 8, hyprexpo:kb_selecti, 18
bind = SHIFT, 9, hyprexpo:kb_selecti, 19
bind = SHIFT, 0, hyprexpo:kb_selecti, 20
# 21..46 via alpha
bind = , a, hyprexpo:kb_selecti, 21
bind = , b, hyprexpo:kb_selecti, 22
bind = , c, hyprexpo:kb_selecti, 23
bind = , d, hyprexpo:kb_selecti, 24
bind = , e, hyprexpo:kb_selecti, 25
bind = , f, hyprexpo:kb_selecti, 26
bind = , g, hyprexpo:kb_selecti, 27
bind = , h, hyprexpo:kb_selecti, 28
bind = , i, hyprexpo:kb_selecti, 29
bind = , j, hyprexpo:kb_selecti, 30
bind = , k, hyprexpo:kb_selecti, 31
bind = , l, hyprexpo:kb_selecti, 32
bind = , m, hyprexpo:kb_selecti, 33
bind = , n, hyprexpo:kb_selecti, 34
bind = , o, hyprexpo:kb_selecti, 35
bind = , p, hyprexpo:kb_selecti, 36
bind = , q, hyprexpo:kb_selecti, 37
bind = , r, hyprexpo:kb_selecti, 38
bind = , s, hyprexpo:kb_selecti, 39
bind = , t, hyprexpo:kb_selecti, 40
bind = , u, hyprexpo:kb_selecti, 41
bind = , v, hyprexpo:kb_selecti, 42
bind = , w, hyprexpo:kb_selecti, 43
bind = , x, hyprexpo:kb_selecti, 44
bind = , y, hyprexpo:kb_selecti, 45
bind = , z, hyprexpo:kb_selecti, 46
submap = reset
```
@ -135,7 +188,9 @@ Keyboard navigation dispatchers (when overview is active):
- Wrapping can be configured with `keynav_wrap_h` and `keynav_wrap_v`.
- Reading order (row-major) for horizontal movement can be enabled with `keynav_reading_order`. At grid ends it will wrap to start/end only if both `keynav_wrap_h` and `keynav_wrap_v` are enabled.
- `hyprexpo:kb_confirm`: selects the focused tile.
- `hyprexpo:kb_selectn, <digit>`: selects the workspace with that id if visible in the grid (0 → 10).
- `hyprexpo:kb_selectn, <id>`: selects by workspace id (legacy; `0``10`).
- `hyprexpo:kb_selecti, <index>`: selects by 1-based visual index (recommended for single-keystroke mapping).
- `hyprexpo:kb_select, <token>`: selects by a single token (1..9, 0, a..z), mainly for symbol-based configs.
Border styles:
- `simple`: single-color border using `border_width`, `border_color_current`, `border_color_focus`.
@ -145,8 +200,8 @@ Border styles:
- `plugin:hyprexpo:border_grad_focus = rgba(ffdd44ee) rgba(22aaffee) 30deg`
Gaps:
- `gaps_in` controls the spacing between tiles.
- `gaps_out` adds the same spacing around the outer edge of the grid.
- `gaps_in` controls the inner spacing between tiles.
- `gaps_out` adds an outer margin (px) around the grid (animated during open/close).
Labels (numbers):
- `label_position`, `label_offset_x`, `label_offset_y` control placement per tile.
@ -154,3 +209,12 @@ Labels (numbers):
- Per-state colors: `label_color_default|hover|focus|current`.
- Per-state scale multipliers: `label_scale_hover|focus`.
- Optional background bubble behind text: `label_bg_*`, `label_padding`.
- Content:
- `label_text_mode = token` (default) uses the token sequence (1..9,0,!,@,#,…,a..z) or `label_token_map` if provided.
- `label_text_mode = index` shows the 1-based visual index.
- `label_text_mode = id` shows the workspace ID.
- Token overrides:
- `label_token_map` accepts up to 50 comma-separated entries to override tokens. Empty entries allowed (skip rendering).
- Font & precision:
- `label_font_family`, `label_font_bold`, `label_font_italic`, `label_text_underline`, `label_text_strikethrough`
- `label_pixel_snap` (default 1) rounds positions to integer pixels for crisper text.

View file

@ -44,6 +44,7 @@ static SDispatchResult onKbFocusDispatcher(std::string arg);
static SDispatchResult onKbConfirmDispatcher(std::string arg);
static SDispatchResult onKbSelectNumberDispatcher(std::string arg);
static SDispatchResult onKbSelectTokenDispatcher(std::string arg);
static SDispatchResult onKbSelectIndexDispatcher(std::string arg);
//
static void hkRenderWorkspace(void* thisptr, PHLMONITOR pMonitor, PHLWORKSPACE pWorkspace, timespec* now, const CBox& geometry) {
@ -254,6 +255,7 @@ APICALL EXPORT PLUGIN_DESCRIPTION_INFO PLUGIN_INIT(HANDLE handle) {
HyprlandAPI::addDispatcherV2(PHANDLE, "hyprexpo:kb_confirm", ::onKbConfirmDispatcher);
HyprlandAPI::addDispatcherV2(PHANDLE, "hyprexpo:kb_selectn", ::onKbSelectNumberDispatcher);
HyprlandAPI::addDispatcherV2(PHANDLE, "hyprexpo:kb_select", ::onKbSelectTokenDispatcher);
HyprlandAPI::addDispatcherV2(PHANDLE, "hyprexpo:kb_selecti", ::onKbSelectIndexDispatcher);
HyprlandAPI::addConfigKeyword(PHANDLE, "hyprexpo-gesture", ::expoGestureKeyword, {});
@ -276,7 +278,11 @@ APICALL EXPORT PLUGIN_DESCRIPTION_INFO PLUGIN_INIT(HANDLE handle) {
HyprlandAPI::addConfigValue(PHANDLE, "plugin:hyprexpo:label_enable", Hyprlang::INT{1});
HyprlandAPI::addConfigValue(PHANDLE, "plugin:hyprexpo:label_color", Hyprlang::INT{0xFFFFFFFF});
HyprlandAPI::addConfigValue(PHANDLE, "plugin:hyprexpo:label_font_size", Hyprlang::INT{16});
HyprlandAPI::addConfigValue(PHANDLE, "plugin:hyprexpo:label_text_mode", Hyprlang::STRING{"id"});
// label_text_mode: token (default) | id | index
HyprlandAPI::addConfigValue(PHANDLE, "plugin:hyprexpo:label_text_mode", Hyprlang::STRING{"token"});
// Optional override map for up to 50 tokens, comma-separated. Empty entries allowed.
// Example: "1,2,3,4,5,6,7,8,9,0,!,@,#,$,%,^,&,*,(,),a,..."
HyprlandAPI::addConfigValue(PHANDLE, "plugin:hyprexpo:label_token_map", Hyprlang::STRING{""});
// defaults: center/middle within the label container
HyprlandAPI::addConfigValue(PHANDLE, "plugin:hyprexpo:label_position", Hyprlang::STRING{"center"});
HyprlandAPI::addConfigValue(PHANDLE, "plugin:hyprexpo:label_offset_x", Hyprlang::INT{0});
@ -399,3 +405,18 @@ static SDispatchResult onKbSelectTokenDispatcher(std::string arg) {
g_pOverview->onKbSelectToken(*idx);
return {};
}
static SDispatchResult onKbSelectIndexDispatcher(std::string arg) {
if (!g_pOverview)
return {};
// trim
while (!arg.empty() && std::isspace(arg.front())) arg.erase(arg.begin());
while (!arg.empty() && std::isspace(arg.back())) arg.pop_back();
int idx = -1;
try { idx = std::stoi(arg); } catch (...) { idx = -1; }
if (idx <= 0)
return {.success = false, .error = "invalid index (expected >= 1)"};
// convert to 0-based visible index
g_pOverview->onKbSelectToken(idx - 1);
return {};
}

View file

@ -840,6 +840,7 @@ void COverview::fullRender() {
static auto* const* PLABELSIZE = (Hyprlang::INT* const*)HyprlandAPI::getConfigValue(PHANDLE, "plugin:hyprexpo:label_font_size")->getDataStaticPtr();
static auto const* PLABELPOS = (Hyprlang::STRING const*)HyprlandAPI::getConfigValue(PHANDLE, "plugin:hyprexpo:label_position")->getDataStaticPtr();
static auto const* PLABELMODE = (Hyprlang::STRING const*)HyprlandAPI::getConfigValue(PHANDLE, "plugin:hyprexpo:label_text_mode")->getDataStaticPtr();
static auto const* PTOKENMAP = (Hyprlang::STRING const*)HyprlandAPI::getConfigValue(PHANDLE, "plugin:hyprexpo:label_token_map")->getDataStaticPtr();
static auto* const* PLABELOX = (Hyprlang::INT* const*)HyprlandAPI::getConfigValue(PHANDLE, "plugin:hyprexpo:label_offset_x")->getDataStaticPtr();
static auto* const* PLABELOY = (Hyprlang::INT* const*)HyprlandAPI::getConfigValue(PHANDLE, "plugin:hyprexpo:label_offset_y")->getDataStaticPtr();
static auto const* PLABELSHOW = (Hyprlang::STRING const*)HyprlandAPI::getConfigValue(PHANDLE, "plugin:hyprexpo:label_show")->getDataStaticPtr();
@ -933,11 +934,41 @@ void COverview::fullRender() {
tile.round();
std::string label;
if (std::string{*PLABELMODE} == "token") {
int k = tokenCounter;
if (k <= 8) label = std::to_string(k + 1);
else if (k == 9) label = "0";
else label = std::string(1, char('a' + (k - 10)));
const std::string mode{*PLABELMODE};
if (mode == "token") {
// override map (comma-separated) up to 50
std::vector<std::string> tokens;
if (!std::string{*PTOKENMAP}.empty()) {
const std::string mapStr{*PTOKENMAP};
size_t start = 0;
for (size_t cur = 0; cur <= mapStr.size(); ++cur) {
if (cur == mapStr.size() || mapStr[cur] == ',') {
std::string t = mapStr.substr(start, cur - start);
while (!t.empty() && std::isspace((unsigned char)t.front())) t.erase(t.begin());
while (!t.empty() && std::isspace((unsigned char)t.back())) t.pop_back();
tokens.push_back(t);
start = cur + 1;
}
}
}
const int k = tokenCounter; // 0-based visible index
if (k < (int)tokens.size() && !tokens[k].empty())
label = tokens[k];
else {
if (k <= 8) label = std::to_string(k + 1); // 1..9
else if (k == 9) label = "0"; // 10 -> 0
else if (k >= 10 && k <= 19) { // 11..20
static const char* SYM[10] = {"!","@","#","$","%","^","&","*","(",")"};
label = SYM[k - 10];
} else if (k >= 20 && k <= 45) {
label = std::string(1, char('a' + (k - 20))); // 21..46 -> a..z
} else {
label = ""; // 47..50 blank by default
}
}
} else if (mode == "index") {
label = std::to_string(tokenCounter + 1);
} else {
label = std::to_string(images[id].workspaceID);
}
@ -955,8 +986,8 @@ void COverview::fullRender() {
}
};
// if label isn't shown per mode, still advance token index
if (!shouldShow(id)) { tokenCounter++; continue; }
// if label isn't shown per mode or label is empty, still advance token index
if (!shouldShow(id) || label.empty()) { tokenCounter++; continue; }
CHyprColor col = CHyprColor{(uint64_t)**PLCOLDEF};
float scl = 1.0f;