1- use std:: ffi:: { c_char , c_void } ;
1+ use std:: { ffi:: c_void , ptr :: NonNull } ;
22
3- use objc2_core_foundation:: CFType ;
3+ use objc2_core_foundation:: { CFArray , CFRetained , CFString , CFType } ;
44
55type Boolean = u8 ;
6- type CFIndex = isize ;
76type OSStatus = i32 ;
8- type CFStringEncoding = u32 ;
97type TISInputSourceRef = * const c_void ;
108
11- const K_CFSTRING_ENCODING_UTF8 : CFStringEncoding = 0x0800_0100 ;
9+ #[ derive( Debug , Clone , PartialEq , Eq ) ]
10+ pub struct InputSource {
11+ pub id : String ,
12+ pub name : String ,
13+ }
14+
15+ impl std:: fmt:: Display for InputSource {
16+ fn fmt ( & self , f : & mut std:: fmt:: Formatter < ' _ > ) -> std:: fmt:: Result {
17+ write ! ( f, "{} ({})" , self . name, self . id)
18+ }
19+ }
1220
1321#[ link( name = "Carbon" , kind = "framework" ) ]
1422unsafe extern "C" {
15- static kTISPropertyInputSourceID: * const c_void ;
23+ static kTISPropertyInputSourceID: * const CFString ;
24+ static kTISPropertyLocalizedName: * const CFString ;
1625
1726 fn TISCopyCurrentKeyboardInputSource ( ) -> TISInputSourceRef ;
1827 fn TISCreateInputSourceList (
1928 properties : * const c_void ,
2029 includeAllInstalled : Boolean ,
21- ) -> * const c_void ;
30+ ) -> * const CFArray ;
2231 fn TISGetInputSourceProperty (
2332 inputSource : TISInputSourceRef ,
24- propertyKey : * const c_void ,
33+ propertyKey : * const CFString ,
2534 ) -> * const c_void ;
2635 fn TISSelectInputSource ( inputSource : TISInputSourceRef ) -> OSStatus ;
2736}
2837
2938#[ link( name = "CoreFoundation" , kind = "framework" ) ]
3039unsafe extern "C" {
31- fn CFArrayGetCount ( theArray : * const c_void ) -> CFIndex ;
32- fn CFArrayGetValueAtIndex ( theArray : * const c_void , idx : CFIndex ) -> * const c_void ;
3340 fn CFRelease ( cf : * mut CFType ) ;
34- fn CFStringGetCString (
35- theString : * const c_void ,
36- buffer : * mut c_char ,
37- bufferSize : CFIndex ,
38- encoding : CFStringEncoding ,
39- ) -> Boolean ;
4041}
4142
4243pub fn current_input_source_id ( ) -> Option < String > {
@@ -51,21 +52,28 @@ pub fn current_input_source_id() -> Option<String> {
5152}
5253
5354pub fn select_input_source ( id : & str ) -> Result < ( ) , String > {
54- let sources = unsafe { TISCreateInputSourceList ( std:: ptr:: null ( ) , false as Boolean ) } ;
55- if sources. is_null ( ) {
55+ let Some ( sources) = input_source_list ( false ) else {
5656 return Err ( "TISCreateInputSourceList returned null" . to_string ( ) ) ;
57- }
57+ } ;
58+
59+ select_input_source_from_list ( & sources, id)
60+ }
5861
59- let result = select_input_source_from_list ( sources, id) ;
60- unsafe { CFRelease ( sources. cast_mut ( ) . cast ( ) ) } ;
61- result
62+ pub fn enabled_input_sources ( ) -> Vec < InputSource > {
63+ input_source_list ( false )
64+ . map ( |sources| input_sources_from_list ( & sources) )
65+ . unwrap_or_default ( )
6266}
6367
64- fn select_input_source_from_list ( sources : * const c_void , id : & str ) -> Result < ( ) , String > {
65- let count = unsafe { CFArrayGetCount ( sources) } ;
68+ fn input_source_list ( include_all_installed : bool ) -> Option < CFRetained < CFArray > > {
69+ let sources =
70+ unsafe { TISCreateInputSourceList ( std:: ptr:: null ( ) , include_all_installed as Boolean ) } ;
71+ NonNull :: new ( sources. cast_mut ( ) ) . map ( |sources| unsafe { CFRetained :: from_raw ( sources) } )
72+ }
6673
67- for idx in 0 ..count {
68- let source = unsafe { CFArrayGetValueAtIndex ( sources, idx) } ;
74+ fn select_input_source_from_list ( sources : & CFArray , id : & str ) -> Result < ( ) , String > {
75+ for idx in 0 ..sources. count ( ) {
76+ let source = unsafe { sources. value_at_index ( idx) } ;
6977 if source. is_null ( ) {
7078 continue ;
7179 }
@@ -83,6 +91,20 @@ fn select_input_source_from_list(sources: *const c_void, id: &str) -> Result<(),
8391 Err ( format ! ( "input source not found: {id}" ) )
8492}
8593
94+ fn input_sources_from_list ( sources : & CFArray ) -> Vec < InputSource > {
95+ ( 0 ..sources. count ( ) )
96+ . filter_map ( |idx| {
97+ let source = unsafe { sources. value_at_index ( idx) } ;
98+ if source. is_null ( ) {
99+ return None ;
100+ }
101+ let id = input_source_id ( source) ?;
102+ let name = input_source_name ( source) . unwrap_or_else ( || id. clone ( ) ) ;
103+ Some ( InputSource { id, name } )
104+ } )
105+ . collect ( )
106+ }
107+
86108fn input_source_id ( source : TISInputSourceRef ) -> Option < String > {
87109 let id_ref = unsafe { TISGetInputSourceProperty ( source, kTISPropertyInputSourceID) } ;
88110 if id_ref. is_null ( ) {
@@ -92,21 +114,16 @@ fn input_source_id(source: TISInputSourceRef) -> Option<String> {
92114 cf_string_to_string ( id_ref)
93115}
94116
95- fn cf_string_to_string ( value : * const c_void ) -> Option < String > {
96- let mut buffer = vec ! [ 0u8 ; 1024 ] ;
97- let ok = unsafe {
98- CFStringGetCString (
99- value,
100- buffer. as_mut_ptr ( ) . cast ( ) ,
101- buffer. len ( ) as CFIndex ,
102- K_CFSTRING_ENCODING_UTF8 ,
103- )
104- } ;
105-
106- if ok == 0 {
117+ fn input_source_name ( source : TISInputSourceRef ) -> Option < String > {
118+ let name_ref = unsafe { TISGetInputSourceProperty ( source, kTISPropertyLocalizedName) } ;
119+ if name_ref. is_null ( ) {
107120 return None ;
108121 }
109122
110- let nul = buffer. iter ( ) . position ( |& byte| byte == 0 ) ?;
111- Some ( String :: from_utf8_lossy ( & buffer[ ..nul] ) . into_owned ( ) )
123+ cf_string_to_string ( name_ref)
124+ }
125+
126+ fn cf_string_to_string ( value : * const c_void ) -> Option < String > {
127+ NonNull :: new ( value. cast_mut ( ) . cast :: < CFString > ( ) )
128+ . map ( |value| unsafe { value. as_ref ( ) } . to_string ( ) )
112129}
0 commit comments