From 9e760bf123e974332fcbd74cadc51daf6d659953 Mon Sep 17 00:00:00 2001
From: Nick Borovets
Date: Thu, 19 Feb 2026 16:20:10 +0300
Subject: [PATCH 1/4] Add Windows keystroke transformation support
Enhance KCEventTransformer to include a method for transforming keystrokes to their Windows equivalents. Update KCDefaultVisualizer to display both macOS and Windows keystroke representations when applicable.
---
keycastr/KCDefaultVisualizer.m | 13 ++++++++-
keycastr/KCEventTransformer.h | 2 ++
keycastr/KCEventTransformer.m | 49 ++++++++++++++++++++++++++++++++++
3 files changed, 63 insertions(+), 1 deletion(-)
diff --git a/keycastr/KCDefaultVisualizer.m b/keycastr/KCDefaultVisualizer.m
index 32614cc..0fb6d9c 100644
--- a/keycastr/KCDefaultVisualizer.m
+++ b/keycastr/KCDefaultVisualizer.m
@@ -33,6 +33,7 @@
#import "KCDefaultVisualizer.h"
#import "KCKeystroke.h"
#import "KCMouseEvent.h"
+#import "KCEventTransformer.h"
#import "NSBezierPath+RoundedRect.h"
#import "NSUserDefaults+Utility.h"
@@ -316,7 +317,17 @@ - (void)addKeystroke:(KCKeystroke *)keystroke
[self abandonCurrentBezelView];
}
- [self appendString:[keystroke convertToString]];
+ NSString *macString = [keystroke convertToString];
+ NSString *winString = [[KCEventTransformer currentTransformer] transformedValueForWindows:keystroke];
+
+ NSString *displayString;
+ if (winString.length > 0) {
+ displayString = [NSString stringWithFormat:@"%@ | %@", macString, winString];
+ } else {
+ displayString = macString;
+ }
+
+ [self appendString:displayString];
}
- (void)appendString:(NSString *)string
diff --git a/keycastr/KCEventTransformer.h b/keycastr/KCEventTransformer.h
index 5211c3b..e0ff76b 100644
--- a/keycastr/KCEventTransformer.h
+++ b/keycastr/KCEventTransformer.h
@@ -41,4 +41,6 @@
- (id)transformedValue:(KCKeycastrEvent *)event;
+- (NSString *)transformedValueForWindows:(KCKeycastrEvent *)event;
+
@end
diff --git a/keycastr/KCEventTransformer.m b/keycastr/KCEventTransformer.m
index 31f4534..e0de3a1 100644
--- a/keycastr/KCEventTransformer.m
+++ b/keycastr/KCEventTransformer.m
@@ -322,6 +322,55 @@ - (id)transformedValue:(KCKeycastrEvent *)event
return mutableResponse;
}
+- (NSString *)transformedValueForWindows:(KCKeycastrEvent *)event {
+ if (![event isKindOfClass:[KCKeystroke class]]) {
+ return nil;
+ }
+
+ KCKeystroke *keystroke = (KCKeystroke *)event;
+ NSEventModifierFlags modifiers = event.modifierFlags;
+
+ BOOL isCommand = (modifiers & NSEventModifierFlagCommand) != 0;
+ BOOL isControl = (modifiers & NSEventModifierFlagControl) != 0;
+ BOOL isOption = (modifiers & NSEventModifierFlagOption) != 0;
+ BOOL isShift = (modifiers & NSEventModifierFlagShift) != 0;
+
+ // Only show Windows equivalent if there are modifiers (Command, Control, or Option)
+ // We treat Shift-only as a "simple letter" case usually, unless it's a special key?
+ // User said: "don't duplicate simple letters like 'a | a'".
+ // Shift+A -> "A". Windows: "Shift+A"? Or just "A"?
+ // Usually Windows shortcuts are Ctrl+C.
+ // Let's require Command, Control, or Option for now.
+ if (!isCommand && !isControl && !isOption) {
+ return nil;
+ }
+
+ NSMutableArray *parts = [NSMutableArray array];
+
+ if (isControl) [parts addObject:@"Win"];
+ if (isCommand) [parts addObject:@"Ctrl"];
+ if (isOption) [parts addObject:@"Alt"];
+ if (isShift) [parts addObject:@"Shift"];
+
+ // Get the character key
+ NSString *charString = nil;
+ NSString *specialKeyString = [[self _specialKeys] objectForKey:@(keystroke.keyCode)];
+ if (specialKeyString) {
+ // We might want to map some symbols to text if possible, but keeping them as is for now is safer
+ // unless we have a map for Windows names.
+ // For example, arrow keys are symbols in _specialKeys.
+ charString = specialKeyString;
+ } else {
+ charString = [self translatedCharacterForKeystroke:keystroke];
+ }
+
+ if (charString.length > 0) {
+ [parts addObject:[charString uppercaseString]];
+ }
+
+ return [parts componentsJoinedByString:@"+"];
+}
+
- (NSString *)translatedCharacterForKeystroke:(KCKeystroke *)keystroke {
if ([self shouldReturnOriginalCharactersForKeyCode:keystroke.keyCode
characters:keystroke.characters] && keystroke.isCommand) {
From b9ef9587755eaae9c4f48cede5a6a5c9ecb0d3cc Mon Sep 17 00:00:00 2001
From: Nick Borovets
Date: Thu, 19 Feb 2026 20:04:58 +0300
Subject: [PATCH 2/4] Add option to show Windows equivalent keystrokes in
visualizers
Implemented a user preference to display Windows equivalent keystrokes alongside macOS keystrokes in both KCDefaultVisualizer and SvelteVisualizer. Updated relevant UI components and user defaults to support this feature.
Co-authored-by: Cursor
---
keycastr/KCDefaultVisualizer.m | 14 ++---
.../KCDefaultVisualizer.nib/designable.nib | 51 +++++++++++-------
.../KCDefaultVisualizer.nib/keyedobjects.nib | Bin 24086 -> 24769 bytes
keycastr/Svelte.nib/designable.nib | 25 ++++++---
keycastr/Svelte.nib/keyedobjects.nib | Bin 3083 -> 3671 bytes
keycastr/SvelteVisualizer.h | 2 +
keycastr/SvelteVisualizer.m | 42 +++++++++++----
7 files changed, 92 insertions(+), 42 deletions(-)
diff --git a/keycastr/KCDefaultVisualizer.m b/keycastr/KCDefaultVisualizer.m
index 0fb6d9c..9e8c0c0 100644
--- a/keycastr/KCDefaultVisualizer.m
+++ b/keycastr/KCDefaultVisualizer.m
@@ -194,6 +194,7 @@ - (void)noteFlagsChanged:(NSEventModifierFlags)flags
requiringSecureCoding:NO
error:NULL],
@"default_displayModifiedCharacters": @NO,
+ @"default.showWindowsEquivalent": @YES,
};
}
@@ -318,13 +319,14 @@ - (void)addKeystroke:(KCKeystroke *)keystroke
}
NSString *macString = [keystroke convertToString];
- NSString *winString = [[KCEventTransformer currentTransformer] transformedValueForWindows:keystroke];
+ BOOL showWindowsEquivalent = [[NSUserDefaults standardUserDefaults] boolForKey:@"default.showWindowsEquivalent"];
- NSString *displayString;
- if (winString.length > 0) {
- displayString = [NSString stringWithFormat:@"%@ | %@", macString, winString];
- } else {
- displayString = macString;
+ NSString *displayString = macString;
+ if (showWindowsEquivalent) {
+ NSString *winString = [[KCEventTransformer currentTransformer] transformedValueForWindows:keystroke];
+ if (winString.length > 0) {
+ displayString = [NSString stringWithFormat:@"%@ | %@", macString, winString];
+ }
}
[self appendString:displayString];
diff --git a/keycastr/KCDefaultVisualizer.nib/designable.nib b/keycastr/KCDefaultVisualizer.nib/designable.nib
index 7e1a3ad..ed37d03 100644
--- a/keycastr/KCDefaultVisualizer.nib/designable.nib
+++ b/keycastr/KCDefaultVisualizer.nib/designable.nib
@@ -1,7 +1,8 @@
-
+
-
+
+
@@ -13,7 +14,7 @@
-
+
@@ -40,7 +41,7 @@
-
+
@@ -85,7 +86,7 @@
-
+
@@ -130,7 +131,7 @@
-
+
@@ -175,7 +176,7 @@
-
+
@@ -209,7 +210,7 @@
-
+
@@ -235,7 +236,7 @@
-
+
@@ -246,7 +247,7 @@
-
+
-
-
-
-
+
+
+
+
+
@@ -349,6 +360,8 @@
+
+
@@ -379,7 +392,7 @@
-
+
diff --git a/keycastr/KCDefaultVisualizer.nib/keyedobjects.nib b/keycastr/KCDefaultVisualizer.nib/keyedobjects.nib
index 9bbdf5308f17acac633f4d4cb662409ca699bcd8..081640484ac3016817c344c008c0314cf0f24c8d 100644
GIT binary patch
literal 24769
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
NBqM$PP;V_}{||US1d0Fv
literal 24086
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
LKZ9qq7qkBZ#oGOn
diff --git a/keycastr/Svelte.nib/designable.nib b/keycastr/Svelte.nib/designable.nib
index 8c6c7bc..20dd0e3 100644
--- a/keycastr/Svelte.nib/designable.nib
+++ b/keycastr/Svelte.nib/designable.nib
@@ -1,7 +1,8 @@
-
+
-
+
+
@@ -13,11 +14,11 @@
-
+
-
+
+
-
+
diff --git a/keycastr/Svelte.nib/keyedobjects.nib b/keycastr/Svelte.nib/keyedobjects.nib
index 079ff93a0307f25e745b74b0b754d8ddf132380a..aefc36200d3bd17e7d8a25fc7e9bbbf0856ef4d7 100644
GIT binary patch
delta 1904
zcmb7^e^69a6vxl*z9;WpKtTmT5mv-dTWNRssUq=14Yf99XKI>esvUg;I|ECx3#1IQ
z+~u*bz%D1*>EE|MZ+UIX;|Hhc5ojCxRXXh
z_t8-A{j^to06QPVzK3Wu+Cs;pK{`H`9-?E*Q7cd@Q5mRA)GAaKDjT&LwFZ@gT8qj>
z<)N&obs-wg57FrQ5Y=4nn&yDg=JN!Ux>jFcRy6%!)*M3K-NP<-t#U+Zb_WhODEq^R~8m1BZuP?maD@M#bf6Q3hRZ
z4C)Vfd;x90P&gYFel!KYAZ$0D0@F!i#>PiPM2-^N1^A5EkdwrLJWkj~Jx+>|Pmo>6
zDA|p?#5qkY*gi@GluS&BR4i7
zDbHY>NZ1aOeB_(*Dfuz^HB*YIpU7yxOx7SH;zg2!@Au>pd5}%J4hB=gBhUCp@pqW;jq$
ze+=lbPm;P~S5FOp2OPm?U8*u49o1*VH?+B=f-QYyHToDu
zVcT@sY(22?#-gyAXcEa^n>Gomq)2$UER=0Pw5Wi1AR
zEbV70*UD-r-Jzc^8zkKwTSnkcs>qNvi0W|uR9
zemq1AXK2^oM@W@U;8|){{6&6#GmCEO#kO+`MvL=eCT>)mpOC~h8#j&Jn>e}orewtV
zLZ?X)Ivpi;T^HCq{21i?A5!f214aQ~dJ~CDOg_
z-*nBdiA_|(zboY|<1FW_;M~f&jdMF^CFcD$d(E8>;!MhO?Hlj&mnc2a^tF9V|Ks
zI*2+f(IH6(Ne5X6qC>I{DLSO;uvCX-I;824j{j8cCvr?Xo?N8$BV
zciL;=w%fXLi3G)$_<}mcw;@+iDj=e6`1-{}6Y(v6)K4bLC;#VcmH5TPCjZ|#=gD)P
z=Q-y*x8JO&w1*SD+qP!=c4mfCJ|bf1MkoSV&>zq&lv+S^ImTbO7G9dS_a3zOJy9tg
zDL5}3vQLZhnL_1k$(|8ovsL?BG5%fA{$5O65cUtk{!!RJiSp0I%CAo4cgOxy*nf%Y
z#nL1RVf!FI6cD1SiBg$`Q&=Jj#Wqp2+hOkzqP$!<<+WnG92VoXn}w)t5n@yA7EI#M
zR%jcv9l8~|4cYP3J%#B|j6o8OIVD7NQu`9t@Q(gudZ?VR
zwhs;r<&p!rTEgntola*4(sySwgV{{(VRyCG=~lI_)(Pd6%MSKinMYEQ>Eqf3U;Yc0
zm-ZC-USvNeAF&QjGA@f|c@21wyTGS;J$Q((0UzfWIL@2F&lL}I0P_Q^f#(zp=7eb*
zlh$ymKbN{UGqgL|kDS>P`U$f-!B3R)m;T^A>wBIg|EuJGftSEO%5C7w+~(eAb}jyz
zWov#qA8bSXV>e;6=U-!#N49}?wjRpl`gYpJsBJiVqMO5^U@WW(YKKRiJHSR+`V{GB
z)XAqwpCP?bVoa7epCJ}+cn78^(C`k-P@v%*jUVHG+@tQDyeim=B>QNv>54DPE_zQE`)ESg~7C{u$Ld
zs(8KP4WLIpkNh45JkmVUJzC(=LXQlOOpn;3CXbrs7vqd3th*j~Fx8i%DyF?|yk*S2
P*wX2qZ`tT}EZXunmg+l_
diff --git a/keycastr/SvelteVisualizer.h b/keycastr/SvelteVisualizer.h
index 7c224c4..55f9714 100644
--- a/keycastr/SvelteVisualizer.h
+++ b/keycastr/SvelteVisualizer.h
@@ -34,6 +34,8 @@
@interface SvelteVisualizerView : NSView
+@property (nonatomic, assign) BOOL showWindowsEquivalent;
+
@end
@interface SvelteVisualizer : KCVisualizer
diff --git a/keycastr/SvelteVisualizer.m b/keycastr/SvelteVisualizer.m
index b1a02b9..4e02c3a 100644
--- a/keycastr/SvelteVisualizer.m
+++ b/keycastr/SvelteVisualizer.m
@@ -32,6 +32,7 @@
#import "KCKeycastrEvent.h"
#import "KCKeystroke.h"
#import "KCMouseEvent.h"
+#import "KCEventTransformer.h"
@implementation SvelteVisualizerFactory
@@ -57,6 +58,8 @@ @implementation SvelteVisualizerView {
NSString *_displayedString;
}
+@synthesize showWindowsEquivalent = _showWindowsEquivalent;
+
-(void) drawRect:(NSRect)rect
{
NSRect frame = [self frame];
@@ -141,19 +144,27 @@ -(void) drawRect:(NSRect)rect
- (void)noteKeyEvent:(KCKeycastrEvent *)event
{
- if (_displayedString) {
- [_displayedString autorelease];
- _displayedString = [[_displayedString stringByAppendingString:[event convertToString]] retain];
+ NSString *macString = [event convertToString];
+ NSString *winString = self.showWindowsEquivalent ? [[KCEventTransformer currentTransformer] transformedValueForWindows:event] : nil;
-
- if (_displayedString.length > 6) {
- NSRange range = NSMakeRange(_displayedString.length - 6, 6);
+ if (winString.length > 0) {
+ // For modifier-based shortcuts, replace the display entirely (don't accumulate)
+ [_displayedString autorelease];
+ _displayedString = [[NSString stringWithFormat:@"%@ | %@", macString, winString] retain];
+ } else {
+ // Regular keypresses: accumulate, keep last 6 chars
+ if (_displayedString) {
[_displayedString autorelease];
- _displayedString = [[_displayedString substringWithRange:range] retain];
+ _displayedString = [[_displayedString stringByAppendingString:macString] retain];
+
+ if (_displayedString.length > 6) {
+ NSRange range = NSMakeRange(_displayedString.length - 6, 6);
+ [_displayedString autorelease];
+ _displayedString = [[_displayedString substringWithRange:range] retain];
+ }
+ } else {
+ _displayedString = [macString retain];
}
- }
- else {
- _displayedString = [[event convertToString] retain];
}
[self setNeedsDisplay:YES];
}
@@ -172,6 +183,7 @@ -(void) noteFlagsChanged:(NSEventModifierFlags)flags
@interface SvelteVisualizer ()
@property (nonatomic, assign) BOOL displayAll;
+@property (nonatomic, assign) BOOL showWindowsEquivalent;
@end
@@ -208,6 +220,7 @@ -(id) init
[_visualizerWindow setContentView:_visualizerView];
_displayAll = [[[NSUserDefaults standardUserDefaults] valueForKey:@"svelte.displayAll"] boolValue];
+ self.showWindowsEquivalent = [[[NSUserDefaults standardUserDefaults] valueForKey:@"svelte.showWindowsEquivalent"] boolValue];
// TODO: migrate away from using NSNotificationCenter for this, as it is far too chatty
__weak typeof(self) weakSelf = self;
@@ -216,6 +229,7 @@ -(id) init
queue:nil
usingBlock:^(NSNotification * _Nonnull notification) {
weakSelf.displayAll = [notification.object boolForKey:@"svelte.displayAll"];
+ weakSelf.showWindowsEquivalent = [notification.object boolForKey:@"svelte.showWindowsEquivalent"];
}];
return self;
@@ -268,8 +282,14 @@ - (void)noteFlagsChanged:(NSEventModifierFlags)flags
[_visualizerView noteFlagsChanged:flags];
}
+-(void) setShowWindowsEquivalent:(BOOL)showWindowsEquivalent {
+ _showWindowsEquivalent = showWindowsEquivalent;
+ _visualizerView.showWindowsEquivalent = showWindowsEquivalent;
+}
+
+ (NSDictionary *)visualizerDefaults {
- return @{ @"svelte.displayAll": @YES };
+ return @{ @"svelte.displayAll": @YES,
+ @"svelte.showWindowsEquivalent": @YES };
}
@end
From d6e27550d25cc4add7bec6dd9e9dbb6aab12a17e Mon Sep 17 00:00:00 2001
From: Nick Borovets
Date: Thu, 19 Feb 2026 20:17:57 +0300
Subject: [PATCH 3/4] Disable Windows equivalent keystrokes display in
KCDefaultVisualizer and SvelteVisualizer
Updated user defaults to turn off the option for displaying Windows equivalent keystrokes in both visualizers, aligning with user preferences.
---
keycastr/KCDefaultVisualizer.m | 2 +-
keycastr/SvelteVisualizer.m | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/keycastr/KCDefaultVisualizer.m b/keycastr/KCDefaultVisualizer.m
index 9e8c0c0..1ad4e47 100644
--- a/keycastr/KCDefaultVisualizer.m
+++ b/keycastr/KCDefaultVisualizer.m
@@ -194,7 +194,7 @@ - (void)noteFlagsChanged:(NSEventModifierFlags)flags
requiringSecureCoding:NO
error:NULL],
@"default_displayModifiedCharacters": @NO,
- @"default.showWindowsEquivalent": @YES,
+ @"default.showWindowsEquivalent": @NO,
};
}
diff --git a/keycastr/SvelteVisualizer.m b/keycastr/SvelteVisualizer.m
index 4e02c3a..7d5ae01 100644
--- a/keycastr/SvelteVisualizer.m
+++ b/keycastr/SvelteVisualizer.m
@@ -289,7 +289,7 @@ -(void) setShowWindowsEquivalent:(BOOL)showWindowsEquivalent {
+ (NSDictionary *)visualizerDefaults {
return @{ @"svelte.displayAll": @YES,
- @"svelte.showWindowsEquivalent": @YES };
+ @"svelte.showWindowsEquivalent": @NO };
}
@end
From cce31c22cfbe7d821b364732539651f14e45b214 Mon Sep 17 00:00:00 2001
From: Nick Borovets
Date: Thu, 19 Feb 2026 20:25:31 +0300
Subject: [PATCH 4/4] Refine Windows keystroke display logic in
KCEventTransformer
Updated the condition for displaying Windows equivalent keystrokes to require Command, Control, or Option modifiers, while excluding Shift. This change aligns with user feedback to avoid duplicating simple letter inputs.
---
keycastr/KCEventTransformer.m | 8 +-------
1 file changed, 1 insertion(+), 7 deletions(-)
diff --git a/keycastr/KCEventTransformer.m b/keycastr/KCEventTransformer.m
index e0de3a1..3d39959 100644
--- a/keycastr/KCEventTransformer.m
+++ b/keycastr/KCEventTransformer.m
@@ -335,13 +335,7 @@ - (NSString *)transformedValueForWindows:(KCKeycastrEvent *)event {
BOOL isOption = (modifiers & NSEventModifierFlagOption) != 0;
BOOL isShift = (modifiers & NSEventModifierFlagShift) != 0;
- // Only show Windows equivalent if there are modifiers (Command, Control, or Option)
- // We treat Shift-only as a "simple letter" case usually, unless it's a special key?
- // User said: "don't duplicate simple letters like 'a | a'".
- // Shift+A -> "A". Windows: "Shift+A"? Or just "A"?
- // Usually Windows shortcuts are Ctrl+C.
- // Let's require Command, Control, or Option for now.
- if (!isCommand && !isControl && !isOption) {
+ if (!isCommand && !isControl && !isOption && !isShift) {
return nil;
}