FreeRDP
Loading...
Searching...
No Matches
AdvancedKeyboardView.m
1/*
2 Advanced keyboard view interface
3
4 Copyright 2013 Thincast Technologies GmbH, Author: Martin Fleisz
5
6 This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
7 If a copy of the MPL was not distributed with this file, You can obtain one at
8 http://mozilla.org/MPL/2.0/.
9 */
10
11#import "AdvancedKeyboardView.h"
12#import "RDPOverlayStyle.h"
13#import "Utils.h"
14#include <freerdp/locale/keyboard.h>
15
16// a key of the extended keyboard
17typedef struct
18{
19 NSString *title; // label, or nil for an icon key
20 NSString *symbol; // SF Symbol for icon key
21 NSString *label; // accessibility label of an icon key
22 NSInteger vkey;
23 BOOL repeats; // auto repeat while held
24} AdvancedKey;
25
26// empty cell in a block
27#define KEY_GAP { nil, nil, nil, 0, NO }
28
29// a block of keys laid out in a grid, row by row from the top left
30typedef struct
31{
32 const AdvancedKey *keys;
33 int count;
34 int columns;
35} AdvancedKeyBlock;
36
37// a block placed on a page, with its width in key columns
38typedef struct
39{
40 const AdvancedKeyBlock *block;
41 int width;
42} AdvancedPageBlock;
43
44typedef struct
45{
46 const AdvancedPageBlock *blocks;
47 int count;
48} AdvancedPage;
49
50#define COUNT_OF(a) ((int)(sizeof(a) / sizeof((a)[0])))
51
52static const AdvancedKey navKeys[] = {
53 { @"prt scn", nil, nil, VK_SNAPSHOT | KBDEXT, NO },
54 { @"scr lk", nil, nil, VK_SCROLL, NO },
55 { @"break", nil, nil, VK_PAUSE | KBDEXT, NO },
56
57 { @"home", nil, nil, VK_HOME | KBDEXT, NO },
58 { @"insert", nil, nil, VK_INSERT | KBDEXT, NO },
59 { @"pg up", nil, nil, VK_PRIOR | KBDEXT, YES },
60
61 { @"end", nil, nil, VK_END | KBDEXT, NO },
62 { nil, @"arrow.up", @"Up", VK_UP | KBDEXT, YES },
63 { @"pg dn", nil, nil, VK_NEXT | KBDEXT, YES },
64
65 { nil, @"arrow.left", @"Left", VK_LEFT | KBDEXT, YES },
66 { nil, @"arrow.down", @"Down", VK_DOWN | KBDEXT, YES },
67 { nil, @"arrow.right", @"Right", VK_RIGHT | KBDEXT, YES },
68};
69
70static const AdvancedKey numKeys[] = {
71 { @"/", nil, nil, VK_DIVIDE | KBDEXT, NO },
72 { @"7", nil, nil, VK_KEY_7, NO },
73 { @"8", nil, nil, VK_KEY_8, NO },
74 { @"9", nil, nil, VK_KEY_9, NO },
75
76 { @"*", nil, nil, VK_MULTIPLY, NO },
77 { @"4", nil, nil, VK_KEY_4, NO },
78 { @"5", nil, nil, VK_KEY_5, NO },
79 { @"6", nil, nil, VK_KEY_6, NO },
80
81 { @"-", nil, nil, VK_SUBTRACT, NO },
82 { @"1", nil, nil, VK_KEY_1, NO },
83 { @"2", nil, nil, VK_KEY_2, NO },
84 { @"3", nil, nil, VK_KEY_3, NO },
85
86 { @"+", nil, nil, VK_ADD, NO },
87 { @"0", nil, nil, VK_KEY_0, NO },
88 KEY_GAP,
89 { @".", nil, nil, VK_OEM_PERIOD, NO },
90};
91
92static const AdvancedKey fnKeys[] = {
93 { @"F1", nil, nil, VK_F1, NO }, { @"F2", nil, nil, VK_F2, NO },
94 { @"F3", nil, nil, VK_F3, NO }, { @"F4", nil, nil, VK_F4, NO },
95
96 { @"F5", nil, nil, VK_F5, NO }, { @"F6", nil, nil, VK_F6, NO },
97 { @"F7", nil, nil, VK_F7, NO }, { @"F8", nil, nil, VK_F8, NO },
98
99 { @"F9", nil, nil, VK_F9, NO }, { @"F10", nil, nil, VK_F10, NO },
100 { @"F11", nil, nil, VK_F11, NO }, { @"F12", nil, nil, VK_F12, NO },
101};
102
103static const AdvancedKey actionKeys[] = {
104 { nil, @"delete.left", @"Backspace", VK_BACK, YES },
105 { nil, @"return", @"Enter", VK_RETURN | KBDEXT, NO },
106};
107
108static const AdvancedKeyBlock navBlock = { navKeys, COUNT_OF(navKeys), 3 };
109static const AdvancedKeyBlock numBlock = { numKeys, COUNT_OF(numKeys), 4 };
110static const AdvancedKeyBlock fnBlock = { fnKeys, COUNT_OF(fnKeys), 4 };
111static const AdvancedKeyBlock actionBlock = { actionKeys, COUNT_OF(actionKeys), 1 };
112
113// pages, indexed by the page enum below
114enum
115{
116 PAGE_NAV,
117 PAGE_NUM,
118 PAGE_FN,
119 PAGE_COUNT
120};
121
122static NSString *const pageTitles[PAGE_COUNT] = { @"nav", @"123", @"Fn" };
123
124static const AdvancedPageBlock narrowNavBlocks[] = { { &navBlock, 3 }, { &actionBlock, 1 } };
125static const AdvancedPageBlock narrowNumBlocks[] = { { &numBlock, 4 }, { &actionBlock, 1 } };
126static const AdvancedPageBlock narrowFnBlocks[] = { { &fnBlock, 4 }, { &actionBlock, 1 } };
127static const AdvancedPage narrowPages[PAGE_COUNT] = {
128 { narrowNavBlocks, COUNT_OF(narrowNavBlocks) },
129 { narrowNumBlocks, COUNT_OF(narrowNumBlocks) },
130 { narrowFnBlocks, COUNT_OF(narrowFnBlocks) },
131};
132
133static const AdvancedPageBlock wideNumBlocks[] = { { &navBlock, 3 },
134 { &numBlock, 4 },
135 { &actionBlock, 1 } };
136static const AdvancedPageBlock wideFnBlocks[] = { { &fnBlock, 7 }, { &actionBlock, 1 } };
137static const AdvancedPage widePages[PAGE_COUNT] = {
138 { NULL, 0 },
139 { wideNumBlocks, COUNT_OF(wideNumBlocks) },
140 { wideFnBlocks, COUNT_OF(wideFnBlocks) },
141};
142
143static const CGFloat kWideLayoutWidth = 600.0; // narrower keyboards use the paged layout
144static const CGFloat kPadding = 8.0;
145static const CGFloat kTopPadding = 14.0; // keeps the keys clear of the toolbar above
146static const CGFloat kKeySpacing = 8.0;
147static const CGFloat kBlockGap = 20.0; // between blocks, with a separator in the middle
148static const CGFloat kTabRowHeight = 36.0;
149static const CGFloat kTabWidth = 64.0;
150static const CGFloat kTabHeight = 30.0;
151
152// key auto repeat
153static const NSTimeInterval kRepeatDelay = 0.4;
154static const NSTimeInterval kRepeatInterval = 0.08;
155
156@interface AdvancedKeyboardView (Private)
157- (NSInteger)displayedPage;
158- (const AdvancedPage *)pageForDisplay;
159- (void)rebuildPage;
160- (void)sendKey:(NSInteger)vkey;
161- (void)stopRepeat;
162@end
163
164@implementation AdvancedKeyboardView
165
166@synthesize delegate = _delegate;
167
168- (id)initWithFrame:(CGRect)frame delegate:(NSObject<AdvancedKeyboardDelegate> *)delegate
169{
170 self = [super initWithFrame:frame inputViewStyle:UIInputViewStyleKeyboard];
171 if (self)
172 {
173 _delegate = delegate;
174 [self setAutoresizingMask:UIViewAutoresizingFlexibleWidth];
175
176 _key_buttons = [[NSMutableArray alloc] init];
177 _tab_buttons = [[NSMutableArray alloc] init];
178 _separators = [[NSMutableArray alloc] init];
179 _page = PAGE_NAV;
180 }
181 return self;
182}
183
184- (void)dealloc
185{
186 [self stopRepeat];
187 [_key_buttons release];
188 [_tab_buttons release];
189 [_separators release];
190 [super dealloc];
191}
192
193- (void)willMoveToWindow:(UIWindow *)newWindow
194{
195 if (newWindow == nil)
196 [self stopRepeat];
197 [super willMoveToWindow:newWindow];
198}
199
200- (void)layoutSubviews
201{
202 [super layoutSubviews];
203
204 UIEdgeInsets safe = [self safeAreaInsets];
205 CGRect area = UIEdgeInsetsInsetRect(
206 [self bounds], UIEdgeInsetsMake(kTopPadding, safe.left + kPadding,
207 MAX(safe.bottom, kPadding), safe.right + kPadding));
208
209 // check wide mode
210 BOOL wide = (area.size.width >= kWideLayoutWidth);
211 if (!_has_layout || wide != _wide_layout)
212 {
213 _wide_layout = wide;
214 _has_layout = YES;
215 [self rebuildPage];
216 }
217
218 CGFloat tabSlot = area.size.width / MAX([_tab_buttons count], 1);
219 CGFloat tabY = CGRectGetMaxY(area) - (kTabRowHeight + kTabHeight) * 0.5;
220 for (NSUInteger i = 0; i < [_tab_buttons count]; i++)
221 [[_tab_buttons objectAtIndex:i]
222 setFrame:CGRectMake(CGRectGetMinX(area) + tabSlot * i + (tabSlot - kTabWidth) * 0.5,
223 tabY, kTabWidth, kTabHeight)];
224
225 const AdvancedPage *page = [self pageForDisplay];
226 CGRect keys = area;
227 keys.size.height -= kTabRowHeight + kKeySpacing;
228
229 int totalWidth = 0;
230 for (int i = 0; i < page->count; i++)
231 totalWidth += page->blocks[i].width;
232 const CGFloat available = keys.size.width - kBlockGap * (page->count - 1);
233
234 CGFloat x = CGRectGetMinX(keys);
235 NSUInteger keyIndex = 0;
236 for (int i = 0; i < page->count; i++)
237 {
238 const AdvancedKeyBlock *block = page->blocks[i].block;
239 const CGFloat blockWidth = available * page->blocks[i].width / totalWidth;
240 const int rows = block->count / block->columns;
241 const CGFloat cellWidth =
242 (blockWidth - kKeySpacing * (block->columns - 1)) / block->columns;
243 const CGFloat cellHeight = (keys.size.height - kKeySpacing * (rows - 1)) / rows;
244
245 for (int k = 0; k < block->count; k++)
246 {
247 if (block->keys[k].vkey == 0)
248 continue;
249
250 const int col = k % block->columns;
251 const int row = k / block->columns;
252 [[_key_buttons objectAtIndex:keyIndex++]
253 setFrame:CGRectMake(x + col * (cellWidth + kKeySpacing),
254 CGRectGetMinY(keys) + row * (cellHeight + kKeySpacing),
255 cellWidth, cellHeight)];
256 }
257
258 x += blockWidth;
259 if (i < page->count - 1)
260 {
261 [[_separators objectAtIndex:i]
262 setFrame:CGRectMake(x + kBlockGap * 0.5 - 0.5, CGRectGetMinY(keys), 1.0,
263 keys.size.height)];
264 x += kBlockGap;
265 }
266 }
267}
268
269- (BOOL)enableInputClicksWhenVisible
270{
271 return YES;
272}
273
274#pragma mark -
275#pragma mark button events
276
277- (void)keyPressed:(UIButton *)sender
278{
279 [self sendKey:[sender tag]];
280}
281
282// repeating keys send on touch down and then repeat until they are released
283- (void)keyDown:(UIButton *)sender
284{
285 [self stopRepeat];
286 [self sendKey:[sender tag]];
287
288 _repeat_key = [sender tag];
289 _repeat_timer = [[NSTimer timerWithTimeInterval:kRepeatInterval
290 target:self
291 selector:@selector(repeatTimerFired:)
292 userInfo:nil
293 repeats:YES] retain];
294 [_repeat_timer setFireDate:[NSDate dateWithTimeIntervalSinceNow:kRepeatDelay]];
295 [[NSRunLoop currentRunLoop] addTimer:_repeat_timer forMode:NSRunLoopCommonModes];
296}
297
298- (void)keyUp:(UIButton *)sender
299{
300 (void)sender;
301 [self stopRepeat];
302}
303
304- (void)repeatTimerFired:(NSTimer *)timer
305{
306 (void)timer;
307 [self sendKey:_repeat_key];
308}
309
310- (void)tabSelected:(UIButton *)sender
311{
312 if ([sender tag] == [self displayedPage])
313 return;
314
315 [[UIDevice currentDevice] playInputClick];
316 _page = [sender tag];
317 [self rebuildPage];
318 [self setNeedsLayout];
319}
320
321@end
322
323#pragma mark -
324@implementation AdvancedKeyboardView (Private)
325
326// wide screen devices show the navigation keys on the number page
327- (NSInteger)displayedPage
328{
329 return (_wide_layout && _page == PAGE_NAV) ? PAGE_NUM : _page;
330}
331
332- (const AdvancedPage *)pageForDisplay
333{
334 return _wide_layout ? &widePages[[self displayedPage]] : &narrowPages[[self displayedPage]];
335}
336
337- (void)rebuildPage
338{
339 [self stopRepeat];
340 for (UIView *view in _key_buttons)
341 [view removeFromSuperview];
342 for (UIView *view in _tab_buttons)
343 [view removeFromSuperview];
344 for (UIView *view in _separators)
345 [view removeFromSuperview];
346 [_key_buttons removeAllObjects];
347 [_tab_buttons removeAllObjects];
348 [_separators removeAllObjects];
349
350 UIImageSymbolConfiguration *symbolConfig =
351 [UIImageSymbolConfiguration configurationWithPointSize:IsPad() ? 20.0 : 18.0
352 weight:UIImageSymbolWeightRegular];
353
354 const AdvancedPage *page = [self pageForDisplay];
355 for (int i = 0; i < page->count; i++)
356 {
357 const AdvancedKeyBlock *block = page->blocks[i].block;
358 for (int k = 0; k < block->count; k++)
359 {
360 const AdvancedKey *key = &block->keys[k];
361 if (key->vkey == 0)
362 continue;
363
364 UIImage *image = key->symbol ? [UIImage systemImageNamed:key->symbol
365 withConfiguration:symbolConfig]
366 : nil;
367 UIButton *btn = RDPCreateKeyButton(key->title, image);
368 [btn setTag:key->vkey];
369 if (key->label)
370 [btn setAccessibilityLabel:NSLocalizedString(key->label, @"Extended keyboard key")];
371
372 if (key->repeats)
373 {
374 [btn addTarget:self
375 action:@selector(keyDown:)
376 forControlEvents:UIControlEventTouchDown];
377 [btn addTarget:self
378 action:@selector(keyUp:)
379 forControlEvents:UIControlEventTouchUpInside | UIControlEventTouchUpOutside |
380 UIControlEventTouchCancel];
381 }
382 else
383 {
384 [btn addTarget:self
385 action:@selector(keyPressed:)
386 forControlEvents:UIControlEventTouchUpInside];
387 }
388
389 [self addSubview:btn];
390 [_key_buttons addObject:btn];
391 }
392
393 if (i < page->count - 1)
394 {
395 UIView *separator = [[[UIView alloc] initWithFrame:CGRectZero] autorelease];
396 [separator setBackgroundColor:[UIColor separatorColor]];
397 [self addSubview:separator];
398 [_separators addObject:separator];
399 }
400 }
401
402 for (NSInteger p = _wide_layout ? PAGE_NUM : PAGE_NAV; p < PAGE_COUNT; p++)
403 {
404 UIButton *tab = RDPCreateTabButton(pageTitles[p]);
405 [tab setTag:p];
406 [tab setSelected:(p == [self displayedPage])];
407 [tab addTarget:self
408 action:@selector(tabSelected:)
409 forControlEvents:UIControlEventTouchUpInside];
410 [self addSubview:tab];
411 [_tab_buttons addObject:tab];
412 }
413}
414
415- (void)sendKey:(NSInteger)vkey
416{
417 [[UIDevice currentDevice] playInputClick];
418 if ([[self delegate] respondsToSelector:@selector(advancedKeyPressedVKey:)])
419 [[self delegate] advancedKeyPressedVKey:vkey];
420}
421
422- (void)stopRepeat
423{
424 [_repeat_timer invalidate];
425 [_repeat_timer release];
426 _repeat_timer = nil;
427}
428
429@end