Skip to content

Commit 3e92c4a

Browse files
committed
fix: macOS 한글 자소분리
1 parent b9d643f commit 3e92c4a

5 files changed

Lines changed: 329 additions & 49 deletions

File tree

indra/llwindow/llopenglview-objc.mm

Lines changed: 172 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -517,19 +517,29 @@ - (void) keyDown:(NSEvent *)theEvent
517517
{
518518
ch = [str_no_modifiers characterAtIndex:0];
519519
}
520+
521+
// Korean input fix: Improved input source detection
522+
NSString *inputSource = [[NSTextInputContext currentInputContext] selectedKeyboardInputSource];
523+
BOOL isKoreanInput = [inputSource containsString:@"Korean"] ||
524+
[inputSource containsString:@"Hangul"] ||
525+
[inputSource containsString:@"2-Set Korean"] ||
526+
[inputSource containsString:@"390 Hangul"];
527+
520528
bool acceptsText = mHasMarkedText ? false : callKeyDown(&eventData, keycode, mModifiers, ch);
521529

522530
if (acceptsText &&
523531
!mMarkedTextAllowed &&
524532
!(mModifiers & (NSControlKeyMask | NSCommandKeyMask)) && // commands don't invoke InputWindow
525-
![(LLAppDelegate*)[NSApp delegate] romanScript] &&
533+
(isKoreanInput || ![(LLAppDelegate*)[NSApp delegate] romanScript]) && // Korean input condition improved
526534
ch > ' ' &&
527535
ch != NSDeleteCharacter &&
528536
(ch < 0xF700 || ch > 0xF8FF)) // 0xF700-0xF8FF: reserved for function keys on the keyboard(from NSEvent.h)
529537
{
538+
// Korean input mode - activate Input Method handling
530539
[(LLAppDelegate*)[NSApp delegate] showInputWindow:true withEvent:theEvent];
531540
} else
532541
{
542+
// Handle through Input Context for IME processing
533543
[[self inputContext] handleEvent:theEvent];
534544
}
535545
}
@@ -628,21 +638,49 @@ - (BOOL) performDragOperation:(id<NSDraggingInfo>)sender
628638

629639
- (BOOL)hasMarkedText
630640
{
641+
// Korean input fix: Also check NSTextInputContext state
642+
NSTextInputContext *context = [self inputContext];
643+
if (context && [context respondsToSelector:@selector(hasMarkedText)])
644+
{
645+
return mHasMarkedText || [context hasMarkedText];
646+
}
631647
return mHasMarkedText;
632648
}
633649

634650
- (NSRange)markedRange
635651
{
652+
// Korean input fix: Improved range handling
653+
if (!mHasMarkedText)
654+
{
655+
return NSMakeRange(NSNotFound, 0);
656+
}
657+
636658
int range[2];
637659
getPreeditMarkedRange(&range[0], &range[1]);
638-
return NSMakeRange(range[0], range[1]);
660+
661+
// Validate range before returning
662+
if (range[0] >= 0 && range[1] > 0)
663+
{
664+
return NSMakeRange(range[0], range[1]);
665+
}
666+
667+
return NSMakeRange(NSNotFound, 0);
639668
}
640669

641670
- (NSRange)selectedRange
642671
{
672+
// Korean input fix: Improved selection range handling
643673
int range[2];
644674
getPreeditSelectionRange(&range[0], &range[1]);
645-
return NSMakeRange(range[0], range[1]);
675+
676+
// Validate range before returning
677+
if (range[0] >= 0)
678+
{
679+
return NSMakeRange(range[0], range[1] > 0 ? range[1] : 0);
680+
}
681+
682+
// Default to current cursor position
683+
return NSMakeRange(0, 0);
646684
}
647685

648686
- (void)setMarkedText:(id)aString selectedRange:(NSRange)selectedRange replacementRange:(NSRange)replacementRange
@@ -653,49 +691,72 @@ - (void)setMarkedText:(id)aString selectedRange:(NSRange)selectedRange replaceme
653691
// Apple also says when aString is an NSString object,
654692
// the receiver is expected to render the marked text with distinguishing appearance.
655693
// So I tried to make attributedStringInfo, but it won't be used... (Pell Smit)
656-
694+
695+
// Korean input fix: Improved composition text handling based on Chrome's approach
657696
if (mMarkedTextAllowed)
658697
{
659-
unsigned int selected[2] = {
660-
unsigned(selectedRange.location),
661-
unsigned(selectedRange.length)
662-
};
698+
// Check if we have composition text
699+
BOOL hasComposition = [aString length] > 0;
663700

664-
unsigned int replacement[2] = {
665-
unsigned(replacementRange.location),
666-
unsigned(replacementRange.length)
667-
};
668-
669-
int string_length = [aString length];
670-
unichar text[string_length];
671-
attributedStringInfo segments;
672-
// I used 'respondsToSelector:@selector(string)'
673-
// to judge aString is an attributed string or not.
674-
if ([aString respondsToSelector:@selector(string)])
675-
{
676-
// aString is attibuted
677-
[[aString string] getCharacters:text range:NSMakeRange(0, string_length)];
678-
segments = getSegments((NSAttributedString *)aString);
679-
}
680-
else
701+
// If we have existing marked text and a replacement range is specified, handle it first
702+
if (mHasMarkedText && replacementRange.location != NSNotFound && replacementRange.length > 0)
681703
{
682-
// aString is not attributed
683-
[aString getCharacters:text range:NSMakeRange(0, string_length)];
684-
segments.seg_lengths.push_back(string_length);
685-
segments.seg_standouts.push_back(true);
704+
// Delete the replacement range text first
705+
callDeleteRange((int)replacementRange.location, (int)replacementRange.length);
686706
}
687-
setMarkedText(text, selected, replacement, string_length, segments);
688-
if (string_length > 0)
707+
708+
if (hasComposition)
689709
{
710+
unsigned int selected[2] = {
711+
unsigned(selectedRange.location),
712+
unsigned(selectedRange.length)
713+
};
714+
715+
unsigned int replacement[2] = {
716+
unsigned(replacementRange.location != NSNotFound ? replacementRange.location : 0),
717+
unsigned(replacementRange.length)
718+
};
719+
720+
int string_length = [aString length];
721+
unichar text[string_length];
722+
attributedStringInfo segments;
723+
724+
// I used 'respondsToSelector:@selector(string)'
725+
// to judge aString is an attributed string or not.
726+
if ([aString respondsToSelector:@selector(string)])
727+
{
728+
// aString is attributed
729+
[[aString string] getCharacters:text range:NSMakeRange(0, string_length)];
730+
segments = getSegments((NSAttributedString *)aString);
731+
}
732+
else
733+
{
734+
// aString is not attributed
735+
[aString getCharacters:text range:NSMakeRange(0, string_length)];
736+
segments.seg_lengths.push_back(string_length);
737+
segments.seg_standouts.push_back(true);
738+
}
739+
740+
// Set marked text with improved handling for Korean composition
741+
setMarkedText(text, selected, replacement, string_length, segments);
690742
mHasMarkedText = TRUE;
691743
mMarkedTextLength = string_length;
744+
745+
// Notify the composition text update
746+
const wchar_t* wtext = reinterpret_cast<const wchar_t*>(text);
747+
std::vector<int> seg_lengths_vec(segments.seg_lengths.begin(), segments.seg_lengths.end());
748+
std::vector<bool> standouts_vec(segments.seg_standouts.begin(), segments.seg_standouts.end());
749+
callCompositionTextUpdate(wtext, string_length, (int)selectedRange.location,
750+
seg_lengths_vec.data(), (int)seg_lengths_vec.size(),
751+
standouts_vec.data());
692752
}
693753
else
694754
{
695-
// we must clear the marked text when aString is null.
755+
// Composition completed or cancelled
696756
[self unmarkText];
697757
}
698758
} else {
759+
// Marked text not allowed, clear any existing marked text
699760
if (mHasMarkedText)
700761
{
701762
[self unmarkText];
@@ -705,20 +766,40 @@ - (void)setMarkedText:(id)aString selectedRange:(NSRange)selectedRange replaceme
705766

706767
- (void)commitCurrentPreedit
707768
{
769+
// Korean input fix: Improved commit process
708770
if (mHasMarkedText)
709771
{
710-
if ([[self inputContext] respondsToSelector:@selector(commitEditing)])
772+
NSTextInputContext *context = [self inputContext];
773+
if ([context respondsToSelector:@selector(commitEditing)])
711774
{
712-
[[self inputContext] commitEditing];
775+
[context commitEditing];
713776
}
777+
778+
// Reset internal state after commit
779+
mHasMarkedText = FALSE;
780+
mMarkedTextLength = 0;
781+
782+
// Notify composition commit
783+
callCompositionTextCommit();
714784
}
715785
}
716786

717787
- (void)unmarkText
718788
{
719-
[[self inputContext] discardMarkedText];
720-
resetPreedit();
721-
mHasMarkedText = FALSE;
789+
// Korean input fix: Improved unmarking process
790+
if (mHasMarkedText)
791+
{
792+
// Notify input context
793+
[[self inputContext] discardMarkedText];
794+
795+
// Reset internal state
796+
resetPreedit();
797+
mHasMarkedText = FALSE;
798+
mMarkedTextLength = 0;
799+
800+
// Clear composition state
801+
clearCompositionText();
802+
}
722803
}
723804

724805
// We don't support attributed strings.
@@ -743,6 +824,22 @@ - (void)insertText:(id)insertString
743824

744825
- (void)insertText:(id)aString replacementRange:(NSRange)replacementRange
745826
{
827+
if (aString == nil) return;
828+
829+
// Korean input fix: Clear any existing marked text first
830+
if (mHasMarkedText)
831+
{
832+
resetPreedit();
833+
mHasMarkedText = FALSE;
834+
callCompositionTextCommit();
835+
}
836+
837+
// Handle replacement range if specified
838+
if (replacementRange.location != NSNotFound && replacementRange.length > 0)
839+
{
840+
callDeleteRange((int)replacementRange.location, (int)replacementRange.length);
841+
}
842+
746843
// SL-19801 Special workaround for system emoji picker
747844
if ([aString length] == 2)
748845
{
@@ -767,27 +864,27 @@ - (void)insertText:(id)aString replacementRange:(NSRange)replacementRange
767864

768865
@try
769866
{
770-
if (!mHasMarkedText)
867+
// Korean input fix: Process each character with improved handling
868+
for (NSInteger i = 0; i < [aString length]; i++)
771869
{
772-
for (NSInteger i = 0; i < [aString length]; i++)
870+
unichar character = [aString characterAtIndex:i];
871+
872+
// Check if this is a Korean completed character (Hangul syllables)
873+
if (character >= 0xAC00 && character <= 0xD7A3)
773874
{
774-
callUnicodeCallback([aString characterAtIndex:i], mModifiers);
875+
// Korean completed character - handle as final composed character
876+
callUnicodeCallback(character, mModifiers);
775877
}
776-
} else {
777-
resetPreedit();
778-
// We may never get this point since unmarkText may be called before insertText ever gets called once we submit our text.
779-
// But just in case...
780-
781-
for (NSInteger i = 0; i < [aString length]; i++)
878+
else
782879
{
783-
handleUnicodeCharacter([aString characterAtIndex:i]);
880+
// Other characters (including Korean Jamo if any)
881+
callUnicodeCallback(character, mModifiers);
784882
}
785-
mHasMarkedText = FALSE;
786883
}
787884
}
788885
@catch(NSException * e)
789886
{
790-
NSLog(@"Failed to process an attributed string. Exception: %@ String: %@", e.name, aString);
887+
NSLog(@"Failed to process text input. Exception: %@ String: %@", e.name, aString);
791888
}
792889
}
793890

@@ -830,7 +927,33 @@ - (BOOL)drawsVerticallyForCharacterAtIndex:(NSUInteger)charIndex
830927

831928
- (void) allowMarkedTextInput:(bool)allowed
832929
{
930+
// Korean input fix: Improved marked text control
833931
mMarkedTextAllowed = allowed;
932+
933+
// Clear existing marked text if not allowed
934+
if (!allowed && mHasMarkedText)
935+
{
936+
[self unmarkText];
937+
}
938+
}
939+
940+
// Korean input fix: New utility methods
941+
- (BOOL)isKoreanInputActive
942+
{
943+
NSString *inputSource = [[NSTextInputContext currentInputContext] selectedKeyboardInputSource];
944+
return [inputSource containsString:@"Korean"] ||
945+
[inputSource containsString:@"Hangul"] ||
946+
[inputSource containsString:@"2-Set Korean"] ||
947+
[inputSource containsString:@"390 Hangul"];
948+
}
949+
950+
- (void)resetIMEState
951+
{
952+
if (mHasMarkedText)
953+
{
954+
[self unmarkText];
955+
}
956+
resetIMEState();
834957
}
835958

836959
@end

indra/llwindow/llwindowcallbacks.h

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,16 @@ class LLWindowCallbacks
9595
virtual std::string translateString(const char* tag);
9696
virtual std::string translateString(const char* tag,
9797
const std::map<std::string, std::string>& args);
98+
99+
// Korean input handling callbacks - added for fixing Korean character decomposition
100+
virtual void handleCompositionTextUpdate(LLWindow* window,
101+
const LLWString& composition_text,
102+
S32 cursor_position,
103+
const std::vector<S32>& segment_lengths,
104+
const std::vector<bool>& standouts) {}
105+
106+
virtual void handleCompositionTextCommit(LLWindow* window) {}
107+
virtual void handleDeleteChar(LLWindow* window) {}
98108
};
99109

100110

indra/llwindow/llwindowmacosx-objc.h

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -184,4 +184,19 @@ GLViewRef getGLView();
184184

185185
unsigned int getModifiers();
186186

187+
// Korean input handling functions - added for fixing Korean character decomposition issue
188+
void callDeleteRange(int start, int length);
189+
void callCompositionTextUpdate(const wchar_t* text, int text_length,
190+
int cursor_position,
191+
const int* segment_lengths, int segment_count,
192+
const bool* standouts);
193+
void callCompositionTextCommit();
194+
bool isKoreanInputActive();
195+
bool hasCompositionText();
196+
void clearCompositionText();
197+
bool detectKoreanInputMethod();
198+
void enableIMEForKorean(bool enable);
199+
void resetIMEState();
200+
bool isIMEComposing();
201+
187202
#endif // LL_LLWINDOWMACOSX_OBJC_H

0 commit comments

Comments
 (0)