diff --git a/res/about/version.gmi b/res/about/version.gmi

index 1b8e502a..48b4cbd1 100644

--- a/res/about/version.gmi

+++ b/res/about/version.gmi

@@ -7,9 +7,10 @@

Release notes



0.10

-* 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.

+* 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.

-* Added a keybinding to activate keyboard link navigation mode (default is "F").

+* Added a new keyboard link navigation mode focusing on the home row keys. The default keybinding for this is "F".

+* 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}.

diff --git a/src/ui/bindingswidget.c b/src/ui/bindingswidget.c

index ff68ea7b..dee844db 100644

--- a/src/ui/bindingswidget.c

+++ b/src/ui/bindingswidget.c

@@ -195,6 +195,12 @@ static iBool processEvent_BindingsWidget_(iBindingsWidget *d, const SDL_Event *e

         postCommand_App("bindings.changed");

         return iTrue;

     }

+ else if (ev->type == SDL_KEYUP && isMod_Sym(ev->key.keysym.sym)) {

+ setKey_BindingItem_(item_ListWidget(d->list, d->activePos), ev->key.keysym.sym, 0);

+ setActiveItem_BindingsWidget_(d, iInvalidPos);

+ postCommand_App("bindings.changed");

+ return iTrue;

+ }

 }

 return processEvent_Widget(w, ev);

}

diff --git a/src/ui/documentwidget.c b/src/ui/documentwidget.c

index b1c166aa..3cf564ac 100644

--- a/src/ui/documentwidget.c

+++ b/src/ui/documentwidget.c

@@ -125,11 +125,17 @@ enum iDocumentWidgetFlag {

 showLinkNumbers_DocumentWidgetFlag       = iBit(3),

};



+enum iDocumentLinkOrdinalMode {

+ numbersAndAlphabet_DocumentLinkOrdinalMode,

+ homeRow_DocumentLinkOrdinalMode,

+};

+

struct Impl_DocumentWidget {

 iWidget        widget;

 enum iRequestState state;

 iPersistentDocumentState mod;

 int            flags;

+ enum iDocumentLinkOrdinalMode ordinalMode;

 iString *      titleUser;

 iGmRequest *   request;

 iAtomicInt     isRequestUpdated; /* request has new content, need to parse it */

@@ -228,7 +234,6 @@ void init_DocumentWidget(iDocumentWidget *d) {

 addAction_Widget(w, navigateForward_KeyShortcut, "navigate.forward");

 addAction_Widget(w, navigateParent_KeyShortcut, "navigate.parent");

 addAction_Widget(w, navigateRoot_KeyShortcut, "navigate.root");

- addAction_Widget(w, 'f', 0, "document.linkkeys");

}



void deinit_DocumentWidget(iDocumentWidget *d) {

@@ -371,6 +376,15 @@ static void invalidateLink_DocumentWidget_(iDocumentWidget *d, iGmLinkId id) {

 }

}



+static void invalidateVisibleLinks_DocumentWidget_(iDocumentWidget *d) {

+ iConstForEach(PtrArray, i, &d->visibleLinks) {

+ const iGmRun *run = i.ptr;

+ if (run->linkId) {

+ insert_PtrSet(d->invalidRuns, run);

+ }

+ }

+}

+

static void updateHover_DocumentWidget_(iDocumentWidget *d, iInt2 mouse) {

 const iWidget *w            = constAs_Widget(d);

 const iRect    docBounds    = documentBounds_DocumentWidget_(d);

@@ -969,7 +983,7 @@ static void smoothScroll_DocumentWidget_(iDocumentWidget *d, int offset, int dur

 /* Get rid of link numbers when scrolling. */

 if (offset && d->flags & showLinkNumbers_DocumentWidgetFlag) {

     d->flags &= ~showLinkNumbers_DocumentWidgetFlag;

- invalidate_DocumentWidget_(d);

+ invalidateVisibleLinks_DocumentWidget_(d);

 }

 if (!prefs_App()->smoothScrolling) {

     duration = 0; /* always instant */

@@ -1542,8 +1556,14 @@ static iBool handleCommand_DocumentWidget_(iDocumentWidget *d, const char *cmd)

     return iTrue;

 }

 else if (equal_Command(cmd, "document.linkkeys") && document_App() == d) {

- iChangeFlags(d->flags, showLinkNumbers_DocumentWidgetFlag, iTrue);

- invalidate_DocumentWidget_(d);

+ if (argLabel_Command(cmd, "release")) {

+ iChangeFlags(d->flags, showLinkNumbers_DocumentWidgetFlag, iFalse);

+ }

+ else {

+ d->ordinalMode = arg_Command(cmd);

+ iChangeFlags(d->flags, showLinkNumbers_DocumentWidgetFlag, iTrue);

+ }

+ invalidateVisibleLinks_DocumentWidget_(d);

     refresh_Widget(d);

     return iTrue;

 }

@@ -1815,45 +1835,75 @@ static iBool processPlayerEvents_DocumentWidget_(iDocumentWidget *d, const SDL_E

 return iFalse;

}



-static size_t linkOrdinalFromKey_(int key) {

- if (key >= '1' && key <= '9') {

- return key - '1';

- }

- if (key < 'a' || key > 'z') {

- return iInvalidPos;

- }

- int ord = key - 'a' + 9;

+/* Sorted by proximity to F and J. */

+static const int homeRowKeys_[] = {

+ 'f', 'd', 's', 'a',

+ 'j', 'k', 'l',

+ 'r', 'e', 'w', 'q',

+ 'u', 'i', 'o', 'p',

+ 'v', 'c', 'x', 'z',

+ 'm', 'n',

+ 'g', 'h',

+ 'b',

+ 't', 'y', 'u',

+};

+

+static size_t linkOrdinalFromKey_DocumentWidget_(const iDocumentWidget *d, int key) {

+ size_t ord = iInvalidPos;

+ if (d->ordinalMode == numbersAndAlphabet_DocumentLinkOrdinalMode) {

+ if (key >= '1' && key <= '9') {

+ return key - '1';

+ }

+ if (key < 'a' || key > 'z') {

+ return iInvalidPos;

+ }

+ ord = key - 'a' + 9;

#if defined (iPlatformApple)

- /* Skip keys that would conflict with default system shortcuts: hide, minimize, quit, close. */

- if (key == 'h' || key == 'm' || key == 'q' || key == 'w') {

- return iInvalidPos;

- }

- if (key > 'h') ord--;

- if (key > 'm') ord--;

- if (key > 'q') ord--;

- if (key > 'w') ord--;

+ /* Skip keys that would conflict with default system shortcuts: hide, minimize, quit, close. */

+ if (key == 'h' || key == 'm' || key == 'q' || key == 'w') {

+ return iInvalidPos;

+ }

+ if (key > 'h') ord--;

+ if (key > 'm') ord--;

+ if (key > 'q') ord--;

+ if (key > 'w') ord--;

#endif

+ }

+ else {

+ iForIndices(i, homeRowKeys_) {

+ if (homeRowKeys_[i] == key) {

+ return i;

+ }

+ }

+ }

 return ord;

}



-static iChar linkOrdinalChar_(size_t ord) {

- if (ord < 9) {

- return 0x278a + ord;

- }

+static iChar linkOrdinalChar_DocumentWidget_(const iDocumentWidget *d, size_t ord) {

+ if (d->ordinalMode == numbersAndAlphabet_DocumentLinkOrdinalMode) {

+ if (ord < 9) {

+ return 0x278a + ord;

+ }

#if defined (iPlatformApple)

- if (ord < 9 + 22) {

- int key = 'a' + ord - 9;

- if (key >= 'h') key++;

- if (key >= 'm') key++;

- if (key >= 'q') key++;

- if (key >= 'w') key++;

- return 0x24b6 + key - 'a';

- }

+ if (ord < 9 + 22) {

+ int key = 'a' + ord - 9;

+ if (key >= 'h') key++;

+ if (key >= 'm') key++;

+ if (key >= 'q') key++;

+ if (key >= 'w') key++;

+ return 0x24b6 + key - 'a';

+ }

#else

- if (ord < 9 + 26) {

- return 0x24b6 + ord - 9;

- }

+ if (ord < 9 + 26) {

+ return 0x24b6 + ord - 9;

+ }

#endif

+ }

+ else {

+ if (ord < iElemCount(homeRowKeys_)) {

+ return 0x24b6 + homeRowKeys_[ord] - 'a';

+ }

+ }

 return 0;

}



@@ -1866,31 +1916,11 @@ static iBool processEvent_DocumentWidget_(iDocumentWidget *d, const SDL_Event *e

     }

     return iTrue;

 }

- if (ev->type == SDL_KEYUP) {

- const int key = ev->key.keysym.sym;

- switch (key) {

- case SDLK_LALT:

- case SDLK_RALT:

- if (document_App() == d) {

- iChangeFlags(d->flags, showLinkNumbers_DocumentWidgetFlag, iFalse);

- invalidate_DocumentWidget_(d);

- refresh_Widget(w);

- }

- break;

- case SDLK_PAGEUP:

- case SDLK_PAGEDOWN:

- case SDLK_SPACE:

- case SDLK_UP:

- case SDLK_DOWN:

-// d->smoothContinue = iFalse;

- break;

- }

- }

 if (ev->type == SDL_KEYDOWN) {

     const int key = ev->key.keysym.sym;

     if ((d->flags & showLinkNumbers_DocumentWidgetFlag) &&

         ((key >= '1' && key <= '9') || (key >= 'a' && key <= 'z'))) {

- const size_t ord = linkOrdinalFromKey_(key);

+ const size_t ord = linkOrdinalFromKey_DocumentWidget_(d, key);

         iConstForEach(PtrArray, i, &d->visibleLinks) {

             if (ord == iInvalidPos) break;

             const iGmRun *run = i.ptr;

@@ -1904,6 +1934,8 @@ static iBool processEvent_DocumentWidget_(iDocumentWidget *d, const SDL_Event *e

                                  cstr_String(absoluteUrl_String(

                                  d->mod.url, linkUrl_GmDocument(d->doc, run->linkId))));

                 iChangeFlags(d->flags, showLinkNumbers_DocumentWidgetFlag, iFalse);

+ invalidateVisibleLinks_DocumentWidget_(d);

+ refresh_Widget(d);

                 return iTrue;

             }

         }

@@ -1912,19 +1944,11 @@ static iBool processEvent_DocumentWidget_(iDocumentWidget *d, const SDL_Event *e

         case SDLK_ESCAPE:

             if (d->flags & showLinkNumbers_DocumentWidgetFlag && document_App() == d) {

                 iChangeFlags(d->flags, showLinkNumbers_DocumentWidgetFlag, iFalse);

- invalidate_DocumentWidget_(d);

+ invalidateVisibleLinks_DocumentWidget_(d);

                 refresh_Widget(d);

                 return iTrue;

             }

             break;

- case SDLK_LALT:

- case SDLK_RALT:

- if (document_App() == d) {

- iChangeFlags(d->flags, showLinkNumbers_DocumentWidgetFlag, iTrue);

- invalidate_DocumentWidget_(d);

- refresh_Widget(w);

- }

- break;

#if 1

         case SDLK_KP_1:

         case '`': {

@@ -2374,11 +2398,11 @@ static void drawRun_DrawContext_(void *context, const iGmRun *run) {

 else {

     if (d->showLinkNumbers && run->linkId && run->flags & decoration_GmRunFlag) {

         const size_t ord = visibleLinkOrdinal_DocumentWidget_(d->widget, run->linkId);

- const iChar ordChar = linkOrdinalChar_(ord);

+ const iChar ordChar = linkOrdinalChar_DocumentWidget_(d->widget, ord);

         if (ordChar) {

             drawString_Text(run->font,

                             init_I2(d->viewPos.x - gap_UI / 3, visPos.y),

- fg,

+ tmQuote_ColorId,

                             collect_String(newUnicodeN_String(&ordChar, 1)));

             goto runDrawn;

         }

diff --git a/src/ui/keys.c b/src/ui/keys.c

index ea874343..d42ecfba 100644

--- a/src/ui/keys.c

+++ b/src/ui/keys.c

@@ -57,7 +57,8 @@ static void clear_Keys_(iKeys *d) {

}



enum iBindFlag {

- argRepeat_BindFlag = iBit(1),

+ argRepeat_BindFlag = iBit(1),

+ argRelease_BindFlag = iBit(2),

};



/* TODO: This indirection could be used for localization, although all UI strings

@@ -73,7 +74,8 @@ static const struct { int id; iMenuItem bind; int flags; } defaultBindings_[] =

 { 31, { "Go forward",                navigateForward_KeyShortcut,   "navigate.forward"   }, 0 },

 { 32, { "Go to parent directory",    navigateParent_KeyShortcut,    "navigate.parent"    }, 0 },

 { 33, { "Go to site root",           navigateRoot_KeyShortcut,      "navigate.root"      }, 0 },

- { 40, { "Open link via keyboard", 'f', 0, "document.linkkeys" }, 0 },

+ { 40, { "Open link via home row keys", 'f', 0, "document.linkkeys arg:1" }, 0 },

+ { 41, { "Open link via modifier key", SDLK_LALT, 0, "document.linkkeys arg:0" }, argRelease_BindFlag },

 /* The following cannot currently be changed (built-in duplicates). */

 { 1000, { NULL, SDLK_SPACE, KMOD_SHIFT, "scroll.page arg:-1" }, argRepeat_BindFlag },

 { 1001, { NULL, SDLK_SPACE, 0, "scroll.page arg:1" }, argRepeat_BindFlag },

@@ -110,6 +112,11 @@ static void bindDefaults_(void) {



static iBinding *find_Keys_(iKeys *d, int key, int mods) {

 size_t pos;

+ /* Do not differentiate between left and right modifier keys. */

+ key = normalizedMod_Sym(key);

+ if (isMod_Sym(key)) {

+ mods = 0;

+ }

 const iBinding elem = { .key = key, .mods = mods };

 if (locate_PtrSet(&d->lookup, &elem, &pos)) {

     return at_PtrSet(&d->lookup, pos);

@@ -138,8 +145,8 @@ static void updateLookup_Keys_(iKeys *d) {

void setKey_Binding(int id, int key, int mods) {

 iBinding *bind = findId_Keys_(&keys_, id);

 if (bind) {

- bind->key = key;

- bind->mods = mods;

+ bind->key = normalizedMod_Sym(key);

+ bind->mods = isMod_Sym(key) ? 0 : mods;

     updateLookup_Keys_(&keys_);

 }

}

@@ -252,25 +259,18 @@ void setLabel_Keys(int id, const char *label) {

 }

}



-#if 0

-const iString *label_Keys(const char *command) {

- iKeys *d = &keys_;

- /* TODO: A hash wouldn't hurt here. */

- iConstForEach(PtrSet, i, &d->bindings) {

- const iBinding *bind = *i.value;

- if (!cmp_String(&bind->command, command) && !isEmpty_String(&bind->label)) {

- return &bind->label;

- }

- }

- return collectNew_String();

-}

-#endif

-

iBool processEvent_Keys(const SDL_Event *ev) {

 iKeys *d = &keys_;

- if (ev->type == SDL_KEYDOWN) {

+ if (ev->type == SDL_KEYDOWN || ev->type == SDL_KEYUP) {

     const iBinding *bind = find_Keys_(d, ev->key.keysym.sym, keyMods_Sym(ev->key.keysym.mod));

     if (bind) {

+ if (ev->type == SDL_KEYUP) {

+ if (bind->flags & argRelease_BindFlag) {

+ postCommandf_App("%s release:1", cstr_String(&bind->command));

+ return iTrue;

+ }

+ return iFalse;

+ }

         if (ev->key.repeat && (bind->flags & argRepeat_BindFlag)) {

             postCommandf_App("%s repeat:1", cstr_String(&bind->command));

         }

diff --git a/src/ui/util.c b/src/ui/util.c

index 559c5381..c1312062 100644

--- a/src/ui/util.c

+++ b/src/ui/util.c

@@ -117,6 +117,14 @@ iBool isMod_Sym(int key) {

        key == SDLK_LGUI || key == SDLK_RGUI || key == SDLK_LSHIFT || key == SDLK_RSHIFT;

}



+int normalizedMod_Sym(int key) {

+ if (key == SDLK_RSHIFT) key = SDLK_LSHIFT;

+ if (key == SDLK_RCTRL) key = SDLK_LCTRL;

+ if (key == SDLK_RALT) key = SDLK_LALT;

+ if (key == SDLK_RGUI) key = SDLK_LGUI;

+ return key;

+}

+

int keyMods_Sym(int kmods) {

 kmods &= (KMOD_SHIFT | KMOD_ALT | KMOD_CTRL | KMOD_GUI);

 /* Don't treat left/right modifiers differently. */

diff --git a/src/ui/util.h b/src/ui/util.h

index c0e3a04c..f7a67f9a 100644

--- a/src/ui/util.h

+++ b/src/ui/util.h

@@ -49,6 +49,7 @@ iLocalDef iBool isResize_UserEvent(const SDL_Event *d) {

#endif



iBool isMod_Sym (int key);

+int normalizedMod_Sym (int key);

int keyMods_Sym (int kmods); /* shift, alt, control, or gui */

void toString_Sym (int key, int kmods, iString *str);



Proxy Information
Original URL
gemini://git.skyjake.fi/lagrange/work%2Fv1.7/pcdiff/a6314e152b2d2f306bcbb880356d3890efbfc89e
Status Code
Success (20)
Meta
text/plain
Capsule Response Time
70.752844 milliseconds
Gemini-to-HTML Time
4.750121 milliseconds

This content has been proxied by September (ba2dc).