=> a6314e152b2d2f306bcbb880356d3890efbfc89e
[1mdiff --git a/res/about/version.gmi b/res/about/version.gmi[m [1mindex 1b8e502a..48b4cbd1 100644[m [1m--- a/res/about/version.gmi[m [1m+++ b/res/about/version.gmi[m [36m@@ -7,9 +7,10 @@[m # Release notes[m [m ## 0.10[m [31m-* Added option to load inline images when pressing Space or ↓ for a more focused reading experience — just keep tapping on a single key to proceed. If an image link is visible, it will be loaded instead of scrolling. This option is disabled by default.[m [32m+[m[32m* Added option to load inline images when pressing Space or ↓ for a more focused reading experience — just keep tapping a single key to proceed. If an image link is visible, it will be loaded instead of scrolling. This option is disabled by default.[m * Added an option to use a proxy server for Gemini requests.[m [31m-* Added a keybinding to activate keyboard link navigation mode (default is "F").[m [32m+[m[32m* Added a new keyboard link navigation mode focusing on the home row keys. The default keybinding for this is "F".[m [32m+[m[32m* Added a keybinding to activate keyboard link modifier mode. The keyboard link keys are active while the modifier is held down. The default is ${ALT}.[m * Clearing and resetting keybindings via a context menu.[m * Added a Window tab in the Preferences dialog; moved some of the settings around for better organization.[m * Improved page search visualization: if the match is inside a link URL, the link icon is now highlighted. Previously these matches were not visualized in any way.[m [1mdiff --git a/src/ui/bindingswidget.c b/src/ui/bindingswidget.c[m [1mindex ff68ea7b..dee844db 100644[m [1m--- a/src/ui/bindingswidget.c[m [1m+++ b/src/ui/bindingswidget.c[m [36m@@ -195,6 +195,12 @@[m [mstatic iBool processEvent_BindingsWidget_(iBindingsWidget *d, const SDL_Event *e[m postCommand_App("bindings.changed");[m return iTrue;[m }[m [32m+[m[32m else if (ev->type == SDL_KEYUP && isMod_Sym(ev->key.keysym.sym)) {[m [32m+[m[32m setKey_BindingItem_(item_ListWidget(d->list, d->activePos), ev->key.keysym.sym, 0);[m [32m+[m[32m setActiveItem_BindingsWidget_(d, iInvalidPos);[m [32m+[m[32m postCommand_App("bindings.changed");[m [32m+[m[32m return iTrue;[m [32m+[m[32m }[m }[m return processEvent_Widget(w, ev);[m }[m [1mdiff --git a/src/ui/documentwidget.c b/src/ui/documentwidget.c[m [1mindex b1c166aa..3cf564ac 100644[m [1m--- a/src/ui/documentwidget.c[m [1m+++ b/src/ui/documentwidget.c[m [36m@@ -125,11 +125,17 @@[m [menum iDocumentWidgetFlag {[m showLinkNumbers_DocumentWidgetFlag = iBit(3),[m };[m [m [32m+[m[32menum iDocumentLinkOrdinalMode {[m [32m+[m[32m numbersAndAlphabet_DocumentLinkOrdinalMode,[m [32m+[m[32m homeRow_DocumentLinkOrdinalMode,[m [32m+[m[32m};[m [32m+[m struct Impl_DocumentWidget {[m iWidget widget;[m enum iRequestState state;[m iPersistentDocumentState mod;[m int flags;[m [32m+[m[32m enum iDocumentLinkOrdinalMode ordinalMode;[m iString * titleUser;[m iGmRequest * request;[m iAtomicInt isRequestUpdated; /* request has new content, need to parse it */[m [36m@@ -228,7 +234,6 @@[m [mvoid init_DocumentWidget(iDocumentWidget *d) {[m addAction_Widget(w, navigateForward_KeyShortcut, "navigate.forward");[m addAction_Widget(w, navigateParent_KeyShortcut, "navigate.parent");[m addAction_Widget(w, navigateRoot_KeyShortcut, "navigate.root");[m [31m- addAction_Widget(w, 'f', 0, "document.linkkeys");[m }[m [m void deinit_DocumentWidget(iDocumentWidget *d) {[m [36m@@ -371,6 +376,15 @@[m [mstatic void invalidateLink_DocumentWidget_(iDocumentWidget *d, iGmLinkId id) {[m }[m }[m [m [32m+[m[32mstatic void invalidateVisibleLinks_DocumentWidget_(iDocumentWidget *d) {[m [32m+[m[32m iConstForEach(PtrArray, i, &d->visibleLinks) {[m [32m+[m[32m const iGmRun *run = i.ptr;[m [32m+[m[32m if (run->linkId) {[m [32m+[m[32m insert_PtrSet(d->invalidRuns, run);[m [32m+[m[32m }[m [32m+[m[32m }[m [32m+[m[32m}[m [32m+[m static void updateHover_DocumentWidget_(iDocumentWidget *d, iInt2 mouse) {[m const iWidget *w = constAs_Widget(d);[m const iRect docBounds = documentBounds_DocumentWidget_(d);[m [36m@@ -969,7 +983,7 @@[m [mstatic void smoothScroll_DocumentWidget_(iDocumentWidget *d, int offset, int dur[m /* Get rid of link numbers when scrolling. */[m if (offset && d->flags & showLinkNumbers_DocumentWidgetFlag) {[m d->flags &= ~showLinkNumbers_DocumentWidgetFlag;[m [31m- invalidate_DocumentWidget_(d);[m [32m+[m[32m invalidateVisibleLinks_DocumentWidget_(d);[m }[m if (!prefs_App()->smoothScrolling) {[m duration = 0; /* always instant */[m [36m@@ -1542,8 +1556,14 @@[m [mstatic iBool handleCommand_DocumentWidget_(iDocumentWidget *d, const char *cmd)[m return iTrue;[m }[m else if (equal_Command(cmd, "document.linkkeys") && document_App() == d) {[m [31m- iChangeFlags(d->flags, showLinkNumbers_DocumentWidgetFlag, iTrue);[m [31m- invalidate_DocumentWidget_(d);[m [32m+[m[32m if (argLabel_Command(cmd, "release")) {[m [32m+[m[32m iChangeFlags(d->flags, showLinkNumbers_DocumentWidgetFlag, iFalse);[m [32m+[m[32m }[m [32m+[m[32m else {[m [32m+[m[32m d->ordinalMode = arg_Command(cmd);[m [32m+[m[32m iChangeFlags(d->flags, showLinkNumbers_DocumentWidgetFlag, iTrue);[m [32m+[m[32m }[m [32m+[m[32m invalidateVisibleLinks_DocumentWidget_(d);[m refresh_Widget(d);[m return iTrue;[m }[m [36m@@ -1815,45 +1835,75 @@[m [mstatic iBool processPlayerEvents_DocumentWidget_(iDocumentWidget *d, const SDL_E[m return iFalse;[m }[m [m [31m-static size_t linkOrdinalFromKey_(int key) {[m [31m- if (key >= '1' && key <= '9') {[m [31m- return key - '1';[m [31m- }[m [31m- if (key < 'a' || key > 'z') {[m [31m- return iInvalidPos;[m [31m- }[m [31m- int ord = key - 'a' + 9;[m [32m+[m[32m/* Sorted by proximity to F and J. */[m [32m+[m[32mstatic const int homeRowKeys_[] = {[m [32m+[m[32m 'f', 'd', 's', 'a',[m [32m+[m[32m 'j', 'k', 'l',[m [32m+[m[32m 'r', 'e', 'w', 'q',[m [32m+[m[32m 'u', 'i', 'o', 'p',[m [32m+[m[32m 'v', 'c', 'x', 'z',[m [32m+[m[32m 'm', 'n',[m [32m+[m[32m 'g', 'h',[m [32m+[m[32m 'b',[m [32m+[m[32m 't', 'y', 'u',[m [32m+[m[32m};[m [32m+[m [32m+[m[32mstatic size_t linkOrdinalFromKey_DocumentWidget_(const iDocumentWidget *d, int key) {[m [32m+[m[32m size_t ord = iInvalidPos;[m [32m+[m[32m if (d->ordinalMode == numbersAndAlphabet_DocumentLinkOrdinalMode) {[m [32m+[m[32m if (key >= '1' && key <= '9') {[m [32m+[m[32m return key - '1';[m [32m+[m[32m }[m [32m+[m[32m if (key < 'a' || key > 'z') {[m [32m+[m[32m return iInvalidPos;[m [32m+[m[32m }[m [32m+[m[32m ord = key - 'a' + 9;[m #if defined (iPlatformApple)[m [31m- /* Skip keys that would conflict with default system shortcuts: hide, minimize, quit, close. */[m [31m- if (key == 'h' || key == 'm' || key == 'q' || key == 'w') {[m [31m- return iInvalidPos;[m [31m- }[m [31m- if (key > 'h') ord--;[m [31m- if (key > 'm') ord--;[m [31m- if (key > 'q') ord--;[m [31m- if (key > 'w') ord--;[m [32m+[m[32m /* Skip keys that would conflict with default system shortcuts: hide, minimize, quit, close. */[m [32m+[m[32m if (key == 'h' || key == 'm' || key == 'q' || key == 'w') {[m [32m+[m[32m return iInvalidPos;[m [32m+[m[32m }[m [32m+[m[32m if (key > 'h') ord--;[m [32m+[m[32m if (key > 'm') ord--;[m [32m+[m[32m if (key > 'q') ord--;[m [32m+[m[32m if (key > 'w') ord--;[m #endif[m [32m+[m[32m }[m [32m+[m[32m else {[m [32m+[m[32m iForIndices(i, homeRowKeys_) {[m [32m+[m[32m if (homeRowKeys_[i] == key) {[m [32m+[m[32m return i;[m [32m+[m[32m }[m [32m+[m[32m }[m [32m+[m[32m }[m return ord;[m }[m [m [31m-static iChar linkOrdinalChar_(size_t ord) {[m [31m- if (ord < 9) {[m [31m- return 0x278a + ord;[m [31m- }[m [32m+[m[32mstatic iChar linkOrdinalChar_DocumentWidget_(const iDocumentWidget *d, size_t ord) {[m [32m+[m[32m if (d->ordinalMode == numbersAndAlphabet_DocumentLinkOrdinalMode) {[m [32m+[m[32m if (ord < 9) {[m [32m+[m[32m return 0x278a + ord;[m [32m+[m[32m }[m #if defined (iPlatformApple)[m [31m- if (ord < 9 + 22) {[m [31m- int key = 'a' + ord - 9;[m [31m- if (key >= 'h') key++;[m [31m- if (key >= 'm') key++;[m [31m- if (key >= 'q') key++;[m [31m- if (key >= 'w') key++;[m [31m- return 0x24b6 + key - 'a';[m [31m- }[m [32m+[m[32m if (ord < 9 + 22) {[m [32m+[m[32m int key = 'a' + ord - 9;[m [32m+[m[32m if (key >= 'h') key++;[m [32m+[m[32m if (key >= 'm') key++;[m [32m+[m[32m if (key >= 'q') key++;[m [32m+[m[32m if (key >= 'w') key++;[m [32m+[m[32m return 0x24b6 + key - 'a';[m [32m+[m[32m }[m #else[m [31m- if (ord < 9 + 26) {[m [31m- return 0x24b6 + ord - 9;[m [31m- }[m [32m+[m[32m if (ord < 9 + 26) {[m [32m+[m[32m return 0x24b6 + ord - 9;[m [32m+[m[32m }[m #endif[m [32m+[m[32m }[m [32m+[m[32m else {[m [32m+[m[32m if (ord < iElemCount(homeRowKeys_)) {[m [32m+[m[32m return 0x24b6 + homeRowKeys_[ord] - 'a';[m [32m+[m[32m }[m [32m+[m[32m }[m return 0;[m }[m [m [36m@@ -1866,31 +1916,11 @@[m [mstatic iBool processEvent_DocumentWidget_(iDocumentWidget *d, const SDL_Event *e[m }[m return iTrue;[m }[m [31m- if (ev->type == SDL_KEYUP) {[m [31m- const int key = ev->key.keysym.sym;[m [31m- switch (key) {[m [31m- case SDLK_LALT:[m [31m- case SDLK_RALT:[m [31m- if (document_App() == d) {[m [31m- iChangeFlags(d->flags, showLinkNumbers_DocumentWidgetFlag, iFalse);[m [31m- invalidate_DocumentWidget_(d);[m [31m- refresh_Widget(w);[m [31m- }[m [31m- break;[m [31m- case SDLK_PAGEUP:[m [31m- case SDLK_PAGEDOWN:[m [31m- case SDLK_SPACE:[m [31m- case SDLK_UP:[m [31m- case SDLK_DOWN:[m [31m-// d->smoothContinue = iFalse;[m [31m- break;[m [31m- }[m [31m- }[m if (ev->type == SDL_KEYDOWN) {[m const int key = ev->key.keysym.sym;[m if ((d->flags & showLinkNumbers_DocumentWidgetFlag) &&[m ((key >= '1' && key <= '9') || (key >= 'a' && key <= 'z'))) {[m [31m- const size_t ord = linkOrdinalFromKey_(key);[m [32m+[m[32m const size_t ord = linkOrdinalFromKey_DocumentWidget_(d, key);[m iConstForEach(PtrArray, i, &d->visibleLinks) {[m if (ord == iInvalidPos) break;[m const iGmRun *run = i.ptr;[m [36m@@ -1904,6 +1934,8 @@[m [mstatic iBool processEvent_DocumentWidget_(iDocumentWidget *d, const SDL_Event *e[m cstr_String(absoluteUrl_String([m d->mod.url, linkUrl_GmDocument(d->doc, run->linkId))));[m iChangeFlags(d->flags, showLinkNumbers_DocumentWidgetFlag, iFalse);[m [32m+[m[32m invalidateVisibleLinks_DocumentWidget_(d);[m [32m+[m[32m refresh_Widget(d);[m return iTrue;[m }[m }[m [36m@@ -1912,19 +1944,11 @@[m [mstatic iBool processEvent_DocumentWidget_(iDocumentWidget *d, const SDL_Event *e[m case SDLK_ESCAPE:[m if (d->flags & showLinkNumbers_DocumentWidgetFlag && document_App() == d) {[m iChangeFlags(d->flags, showLinkNumbers_DocumentWidgetFlag, iFalse);[m [31m- invalidate_DocumentWidget_(d);[m [32m+[m[32m invalidateVisibleLinks_DocumentWidget_(d);[m refresh_Widget(d);[m return iTrue;[m }[m break;[m [31m- case SDLK_LALT:[m [31m- case SDLK_RALT:[m [31m- if (document_App() == d) {[m [31m- iChangeFlags(d->flags, showLinkNumbers_DocumentWidgetFlag, iTrue);[m [31m- invalidate_DocumentWidget_(d);[m [31m- refresh_Widget(w);[m [31m- }[m [31m- break;[m #if 1[m case SDLK_KP_1:[m case '`': {[m [36m@@ -2374,11 +2398,11 @@[m [mstatic void drawRun_DrawContext_(void *context, const iGmRun *run) {[m else {[m if (d->showLinkNumbers && run->linkId && run->flags & decoration_GmRunFlag) {[m const size_t ord = visibleLinkOrdinal_DocumentWidget_(d->widget, run->linkId);[m [31m- const iChar ordChar = linkOrdinalChar_(ord);[m [32m+[m[32m const iChar ordChar = linkOrdinalChar_DocumentWidget_(d->widget, ord);[m if (ordChar) {[m drawString_Text(run->font,[m init_I2(d->viewPos.x - gap_UI / 3, visPos.y),[m [31m- fg,[m [32m+[m[32m tmQuote_ColorId,[m collect_String(newUnicodeN_String(&ordChar, 1)));[m goto runDrawn;[m }[m [1mdiff --git a/src/ui/keys.c b/src/ui/keys.c[m [1mindex ea874343..d42ecfba 100644[m [1m--- a/src/ui/keys.c[m [1m+++ b/src/ui/keys.c[m [36m@@ -57,7 +57,8 @@[m [mstatic void clear_Keys_(iKeys *d) {[m }[m [m enum iBindFlag {[m [31m- argRepeat_BindFlag = iBit(1),[m [32m+[m[32m argRepeat_BindFlag = iBit(1),[m [32m+[m[32m argRelease_BindFlag = iBit(2),[m };[m [m /* TODO: This indirection could be used for localization, although all UI strings[m [36m@@ -73,7 +74,8 @@[m [mstatic const struct { int id; iMenuItem bind; int flags; } defaultBindings_[] =[m { 31, { "Go forward", navigateForward_KeyShortcut, "navigate.forward" }, 0 },[m { 32, { "Go to parent directory", navigateParent_KeyShortcut, "navigate.parent" }, 0 },[m { 33, { "Go to site root", navigateRoot_KeyShortcut, "navigate.root" }, 0 },[m [31m- { 40, { "Open link via keyboard", 'f', 0, "document.linkkeys" }, 0 },[m [32m+[m[32m { 40, { "Open link via home row keys", 'f', 0, "document.linkkeys arg:1" }, 0 },[m [32m+[m[32m { 41, { "Open link via modifier key", SDLK_LALT, 0, "document.linkkeys arg:0" }, argRelease_BindFlag },[m /* The following cannot currently be changed (built-in duplicates). */[m { 1000, { NULL, SDLK_SPACE, KMOD_SHIFT, "scroll.page arg:-1" }, argRepeat_BindFlag },[m { 1001, { NULL, SDLK_SPACE, 0, "scroll.page arg:1" }, argRepeat_BindFlag },[m [36m@@ -110,6 +112,11 @@[m [mstatic void bindDefaults_(void) {[m [m static iBinding *find_Keys_(iKeys *d, int key, int mods) {[m size_t pos;[m [32m+[m[32m /* Do not differentiate between left and right modifier keys. */[m [32m+[m[32m key = normalizedMod_Sym(key);[m [32m+[m[32m if (isMod_Sym(key)) {[m [32m+[m[32m mods = 0;[m [32m+[m[32m }[m const iBinding elem = { .key = key, .mods = mods };[m if (locate_PtrSet(&d->lookup, &elem, &pos)) {[m return at_PtrSet(&d->lookup, pos);[m [36m@@ -138,8 +145,8 @@[m [mstatic void updateLookup_Keys_(iKeys *d) {[m void setKey_Binding(int id, int key, int mods) {[m iBinding *bind = findId_Keys_(&keys_, id);[m if (bind) {[m [31m- bind->key = key;[m [31m- bind->mods = mods;[m [32m+[m[32m bind->key = normalizedMod_Sym(key);[m [32m+[m[32m bind->mods = isMod_Sym(key) ? 0 : mods;[m updateLookup_Keys_(&keys_);[m }[m }[m [36m@@ -252,25 +259,18 @@[m [mvoid setLabel_Keys(int id, const char *label) {[m }[m }[m [m [31m-#if 0[m [31m-const iString *label_Keys(const char *command) {[m [31m- iKeys *d = &keys_;[m [31m- /* TODO: A hash wouldn't hurt here. */[m [31m- iConstForEach(PtrSet, i, &d->bindings) {[m [31m- const iBinding *bind = *i.value;[m [31m- if (!cmp_String(&bind->command, command) && !isEmpty_String(&bind->label)) {[m [31m- return &bind->label;[m [31m- }[m [31m- }[m [31m- return collectNew_String();[m [31m-}[m [31m-#endif[m [31m-[m iBool processEvent_Keys(const SDL_Event *ev) {[m iKeys *d = &keys_;[m [31m- if (ev->type == SDL_KEYDOWN) {[m [32m+[m[32m if (ev->type == SDL_KEYDOWN || ev->type == SDL_KEYUP) {[m const iBinding *bind = find_Keys_(d, ev->key.keysym.sym, keyMods_Sym(ev->key.keysym.mod));[m if (bind) {[m [32m+[m[32m if (ev->type == SDL_KEYUP) {[m [32m+[m[32m if (bind->flags & argRelease_BindFlag) {[m [32m+[m[32m postCommandf_App("%s release:1", cstr_String(&bind->command));[m [32m+[m[32m return iTrue;[m [32m+[m[32m }[m [32m+[m[32m return iFalse;[m [32m+[m[32m }[m if (ev->key.repeat && (bind->flags & argRepeat_BindFlag)) {[m postCommandf_App("%s repeat:1", cstr_String(&bind->command));[m }[m [1mdiff --git a/src/ui/util.c b/src/ui/util.c[m [1mindex 559c5381..c1312062 100644[m [1m--- a/src/ui/util.c[m [1m+++ b/src/ui/util.c[m [36m@@ -117,6 +117,14 @@[m [miBool isMod_Sym(int key) {[m key == SDLK_LGUI || key == SDLK_RGUI || key == SDLK_LSHIFT || key == SDLK_RSHIFT;[m }[m [m [32m+[m[32mint normalizedMod_Sym(int key) {[m [32m+[m[32m if (key == SDLK_RSHIFT) key = SDLK_LSHIFT;[m [32m+[m[32m if (key == SDLK_RCTRL) key = SDLK_LCTRL;[m [32m+[m[32m if (key == SDLK_RALT) key = SDLK_LALT;[m [32m+[m[32m if (key == SDLK_RGUI) key = SDLK_LGUI;[m [32m+[m[32m return key;[m [32m+[m[32m}[m [32m+[m int keyMods_Sym(int kmods) {[m kmods &= (KMOD_SHIFT | KMOD_ALT | KMOD_CTRL | KMOD_GUI);[m /* Don't treat left/right modifiers differently. */[m [1mdiff --git a/src/ui/util.h b/src/ui/util.h[m [1mindex c0e3a04c..f7a67f9a 100644[m [1m--- a/src/ui/util.h[m [1m+++ b/src/ui/util.h[m [36m@@ -49,6 +49,7 @@[m [miLocalDef iBool isResize_UserEvent(const SDL_Event *d) {[m #endif[m [m iBool isMod_Sym (int key);[m [32m+[m[32mint normalizedMod_Sym (int key);[m int keyMods_Sym (int kmods); /* shift, alt, control, or gui */[m void toString_Sym (int key, int kmods, iString *str);[m [m
text/gemini; charset=utf-8
This content has been proxied by September (ba2dc).