@@ -12,7 +12,14 @@ import os from 'node:os';
1212import { BrowserWindow , screen , ipcMain , app } from 'electron' ;
1313
1414import { DeepReadonly } from './settings' ;
15- import { ShowMenuRequest , Menu , MenuItem , WMInfo , SelectionSource } from '../common' ;
15+ import {
16+ ShowMenuRequest ,
17+ Menu ,
18+ MenuItem ,
19+ WMInfo ,
20+ SelectionSource ,
21+ InteractionTarget ,
22+ } from '../common' ;
1623import { IPCCallbacks } from '../common/ipc' ;
1724import { ItemActionRegistry } from './item-actions/item-action-registry' ;
1825import { Notification } from './utils/notification' ;
@@ -663,106 +670,115 @@ export class MenuWindow extends BrowserWindow {
663670 // execute the action.
664671 ipcMain . on (
665672 'menu-window.select-item' ,
666- ( event , path : string , time : number , source : SelectionSource ) => {
667- const execute = ( item : DeepReadonly < MenuItem > ) => {
668- ItemActionRegistry . getInstance ( )
669- . execute ( item , this . kando )
670- . catch ( ( error ) => {
671- Notification . show ( {
672- title : 'Failed to execute action' ,
673- message : error instanceof Error ? error . message : error ,
674- type : 'error' ,
673+ (
674+ event ,
675+ target : InteractionTarget ,
676+ path : string ,
677+ time : number ,
678+ source : SelectionSource
679+ ) => {
680+ const pathArray = this . pathToArray ( path ) ;
681+
682+ if ( target === InteractionTarget . eItem ) {
683+ const execute = ( item : DeepReadonly < MenuItem > ) => {
684+ ItemActionRegistry . getInstance ( )
685+ . execute ( item , this . kando )
686+ . catch ( ( error ) => {
687+ Notification . show ( {
688+ title : 'Failed to execute action' ,
689+ message : error instanceof Error ? error . message : error ,
690+ type : 'error' ,
691+ } ) ;
675692 } ) ;
693+ } ;
694+
695+ let item : DeepReadonly < MenuItem > ;
696+ let executeDelayed = false ;
697+
698+ try {
699+ // Find the selected item.
700+ item = this . getMenuItemAtPath ( this . lastMenu . root , path ) ;
701+
702+ // If the action is not delayed, we execute it immediately.
703+ executeDelayed = ItemActionRegistry . getInstance ( ) . delayedExecution ( item ) ;
704+ if ( ! executeDelayed ) {
705+ execute ( item ) ;
706+ }
707+ } catch ( error ) {
708+ Notification . show ( {
709+ title : 'Failed to select item' ,
710+ message : error instanceof Error ? error . message : error ,
711+ type : 'error' ,
676712 } ) ;
677- } ;
678-
679- let item : DeepReadonly < MenuItem > ;
680- let executeDelayed = false ;
681-
682- try {
683- // Find the selected item.
684- item = this . getMenuItemAtPath ( this . lastMenu . root , path ) ;
685-
686- // If the action is not delayed, we execute it immediately.
687- executeDelayed = ItemActionRegistry . getInstance ( ) . delayedExecution ( item ) ;
688- if ( ! executeDelayed ) {
689- execute ( item ) ;
690713 }
691- } catch ( error ) {
692- Notification . show ( {
693- title : 'Failed to select item' ,
694- message : error instanceof Error ? error . message : error ,
695- type : 'error' ,
714+
715+ // Also wait with the execution of the selected action until the fade-out
716+ // animation is finished to make sure that any resulting events (such as virtual
717+ // key presses) are not captured by the window.
718+ this . hideWindow ( ) . then ( ( ) => {
719+ // If the action is delayed, we execute it after the window is hidden.
720+ if ( executeDelayed ) {
721+ execute ( item ) ;
722+ }
696723 } ) ;
697- }
698724
699- // Also wait with the execution of the selected action until the fade-out
700- // animation is finished to make sure that any resulting events (such as virtual
701- // key presses) are not captured by the window.
702- this . hideWindow ( ) . then ( ( ) => {
703- // If the action is delayed, we execute it after the window is hidden.
704- if ( executeDelayed ) {
705- execute ( item ) ;
725+ // Track selection for achievements.
726+ this . kando . achievementTracker . onSelectionMade (
727+ Math . min ( Math . max ( pathArray . length , 1 ) , 3 ) as 1 | 2 | 3 , // depth between 1 and 3
728+ time ,
729+ source
730+ ) ;
731+
732+ this . lastSelections . push ( { time, date : new Date ( ) } ) ;
733+ if ( this . lastSelections . length > 10 ) {
734+ this . lastSelections . shift ( ) ;
706735 }
707- } ) ;
708736
709- // Call the provided callbacks if they exist.
710- const pathArray = this . pathToArray ( path ) ;
711- this . ipcCallbacks . onSelect ( pathArray ) ;
712-
713- // Track selection for achievements.
714- this . kando . achievementTracker . onSelectionMade (
715- Math . min ( Math . max ( pathArray . length , 1 ) , 3 ) as 1 | 2 | 3 , // depth between 1 and 3
716- time ,
717- source
718- ) ;
719-
720- this . lastSelections . push ( { time, date : new Date ( ) } ) ;
721- if ( this . lastSelections . length > 10 ) {
722- this . lastSelections . shift ( ) ;
723- }
737+ // Check for many-selections-streak achievement.
738+ if ( this . lastSelections . length === 10 ) {
739+ const oldest = this . lastSelections [ 0 ] ;
740+ const newest = this . lastSelections [ 9 ] ;
741+ const timeDiff = newest . date . getTime ( ) - oldest . date . getTime ( ) ;
724742
725- // Check for many-selections-streak achievement.
726- if ( this . lastSelections . length === 10 ) {
727- const oldest = this . lastSelections [ 0 ] ;
728- const newest = this . lastSelections [ 9 ] ;
729- const timeDiff = newest . date . getTime ( ) - oldest . date . getTime ( ) ;
743+ if ( timeDiff <= 30000 ) {
744+ this . kando . achievementTracker . incrementStat ( 'manySelectionsStreaks1' ) ;
745+ }
730746
731- if ( timeDiff <= 30000 ) {
732- this . kando . achievementTracker . incrementStat ( 'manySelectionsStreaks1 ' ) ;
733- }
747+ if ( timeDiff <= 20000 ) {
748+ this . kando . achievementTracker . incrementStat ( 'manySelectionsStreaks2 ' ) ;
749+ }
734750
735- if ( timeDiff <= 20000 ) {
736- this . kando . achievementTracker . incrementStat ( 'manySelectionsStreaks2' ) ;
751+ if ( timeDiff <= 10000 ) {
752+ this . kando . achievementTracker . incrementStat ( 'manySelectionsStreaks3' ) ;
753+ }
737754 }
738755
739- if ( timeDiff <= 10000 ) {
740- this . kando . achievementTracker . incrementStat ( 'manySelectionsStreaks3' ) ;
756+ // Check for the speedy-selections-streak achievement.
757+ if ( this . lastSelections . length === 10 ) {
758+ let average = 0.0 ;
759+ this . lastSelections . forEach ( ( selection ) => {
760+ average += selection . time / this . lastSelections . length ;
761+ } ) ;
762+ if ( average < 750 ) {
763+ this . kando . achievementTracker . incrementStat ( 'speedySelectionsStreaks1' ) ;
764+ }
765+ if ( average < 500 ) {
766+ this . kando . achievementTracker . incrementStat ( 'speedySelectionsStreaks2' ) ;
767+ }
768+ if ( average < 250 ) {
769+ this . kando . achievementTracker . incrementStat ( 'speedySelectionsStreaks3' ) ;
770+ }
741771 }
742772 }
743773
744- // Check for the speedy-selections-streak achievement.
745- if ( this . lastSelections . length === 10 ) {
746- let average = 0.0 ;
747- this . lastSelections . forEach ( ( selection ) => {
748- average += selection . time / this . lastSelections . length ;
749- } ) ;
750- if ( average < 750 ) {
751- this . kando . achievementTracker . incrementStat ( 'speedySelectionsStreaks1' ) ;
752- }
753- if ( average < 500 ) {
754- this . kando . achievementTracker . incrementStat ( 'speedySelectionsStreaks2' ) ;
755- }
756- if ( average < 250 ) {
757- this . kando . achievementTracker . incrementStat ( 'speedySelectionsStreaks3' ) ;
758- }
759- }
774+ // Call the provided callbacks if they exist.
775+ this . ipcCallbacks . onSelect ( target , pathArray ) ;
760776 }
761777 ) ;
762778
763779 // When the user hovers a menu item, we report this to whoever requested the menu.
764- ipcMain . on ( 'menu-window.hover-item' , ( event , path ) => {
765- this . ipcCallbacks . onHover ( this . pathToArray ( path ) ) ;
780+ ipcMain . on ( 'menu-window.hover-item' , ( event , target , path ) => {
781+ this . ipcCallbacks . onHover ( target , this . pathToArray ( path ) ) ;
766782 } ) ;
767783
768784 // We do not hide the window immediately when the user aborts a selection. Instead, we
0 commit comments