1 /*****************************************************************************
2 * VLCStringUtility.m: MacOS X interface module
3 *****************************************************************************
4 * Copyright (C) 2002-2014 VLC authors and VideoLAN
7 * Authors: Jon Lech Johansen <jon-vl@nanocrew.net>
8 * Christophe Massiot <massiot@via.ecp.fr>
9 * Derk-Jan Hartman <hartman at videolan dot org>
10 * Felix Paul Kühne <fkuehne at videolan dot org>
12 * This program is free software; you can redistribute it and/or modify
13 * it under the terms of the GNU General Public License as published by
14 * the Free Software Foundation; either version 2 of the License, or
15 * (at your option) any later version.
17 * This program is distributed in the hope that it will be useful,
18 * but WITHOUT ANY WARRANTY; without even the implied warranty of
19 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 * GNU General Public License for more details.
22 * You should have received a copy of the GNU General Public License
23 * along with this program; if not, write to the Free Software
24 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
25 *****************************************************************************/
27 #import "VLCStringUtility.h"
30 #import "CompatibilityFixes.h"
32 #import <IOKit/storage/IOMedia.h>
33 #import <IOKit/storage/IOCDMedia.h>
34 #import <IOKit/storage/IODVDMedia.h>
35 #import <IOKit/storage/IOBDMedia.h>
37 NSString *const kVLCMediaAudioCD = @"AudioCD";
38 NSString *const kVLCMediaDVD = @"DVD";
39 NSString *const kVLCMediaVCD = @"VCD";
40 NSString *const kVLCMediaSVCD = @"SVCD";
41 NSString *const kVLCMediaBD = @"Blu-ray";
42 NSString *const kVLCMediaVideoTSFolder = @"VIDEO_TS";
43 NSString *const kVLCMediaBDMVFolder = @"BDMV";
44 NSString *const kVLCMediaUnknown = @"Unknown";
47 #import <vlc_strings.h>
49 @implementation VLCStringUtility
52 + (VLCStringUtility *)sharedInstance
54 static VLCStringUtility *sharedInstance = nil;
55 static dispatch_once_t pred;
57 dispatch_once(&pred, ^{
58 sharedInstance = [VLCStringUtility new];
61 return sharedInstance;
65 #pragma mark String utility
67 - (NSString *)localizedString:(const char *)psz
69 NSString * stringObject = nil;
72 stringObject = toNSStr(_(psz));
74 if (stringObject == NULL) {
75 msg_Err(getIntf(), "could not translate: %s", psz);
84 /* i_width is in pixels */
85 - (NSString *)wrapString:(NSString *)o_in_string toWidth:(int)i_width
87 NSMutableString *o_wrapped;
88 NSString *o_out_string;
89 NSRange glyphRange, effectiveRange, charRange;
90 NSRect lineFragmentRect;
91 unsigned glyphIndex, breaksInserted = 0;
93 NSTextStorage *o_storage = [[NSTextStorage alloc] initWithString: o_in_string
94 attributes: [NSDictionary dictionaryWithObjectsAndKeys:
95 [NSFont labelFontOfSize: 0.0], NSFontAttributeName, nil]];
96 NSLayoutManager *o_layout_manager = [[NSLayoutManager alloc] init];
97 NSTextContainer *o_container = [[NSTextContainer alloc]
98 initWithContainerSize: NSMakeSize(i_width, 2000)];
100 [o_layout_manager addTextContainer: o_container];
101 [o_storage addLayoutManager: o_layout_manager];
103 o_wrapped = [o_in_string mutableCopy];
104 glyphRange = [o_layout_manager glyphRangeForTextContainer: o_container];
106 for (glyphIndex = glyphRange.location ; glyphIndex < NSMaxRange(glyphRange) ;
107 glyphIndex += effectiveRange.length) {
108 lineFragmentRect = [o_layout_manager lineFragmentRectForGlyphAtIndex: glyphIndex
109 effectiveRange: &effectiveRange];
110 charRange = [o_layout_manager characterRangeForGlyphRange: effectiveRange
111 actualGlyphRange: &effectiveRange];
112 if ([o_wrapped lineRangeForRange:
113 NSMakeRange(charRange.location + breaksInserted, charRange.length)].length > charRange.length) {
114 [o_wrapped insertString: @"\n" atIndex: NSMaxRange(charRange) + breaksInserted];
118 o_out_string = [NSString stringWithString: o_wrapped];
123 - (NSString *)getCurrentTimeAsString:(input_thread_t *)p_input negative:(BOOL)b_negative
125 assert(p_input != nil);
127 char psz_time[MSTRTIME_MAX_SIZE];
128 int64_t t = var_GetInteger(p_input, "time");
130 mtime_t dur = input_item_GetDuration(input_GetItem(p_input));
131 if (b_negative && dur > 0) {
132 mtime_t remaining = 0;
135 return [NSString stringWithFormat: @"-%s", secstotimestr(psz_time, (remaining / 1000000))];
137 return toNSStr(secstotimestr(psz_time, t / CLOCK_FREQ ));
140 - (NSString *)stringForTime:(long long int)time
143 long long positiveDuration = llabs(time);
144 if (positiveDuration > 3600)
145 return [NSString stringWithFormat:@"%s%01ld:%02ld:%02ld",
147 (long) (positiveDuration / 3600),
148 (long)((positiveDuration / 60) % 60),
149 (long) (positiveDuration % 60)];
151 return [NSString stringWithFormat:@"%s%02ld:%02ld",
153 (long)((positiveDuration / 60) % 60),
154 (long) (positiveDuration % 60)];
156 // Return a string that represents an undefined time.
162 #pragma mark Key Shortcuts
167 unsigned int i_vlckey;
168 } nskeys_to_vlckeys[] =
170 { NSUpArrowFunctionKey, KEY_UP },
171 { NSDownArrowFunctionKey, KEY_DOWN },
172 { NSLeftArrowFunctionKey, KEY_LEFT },
173 { NSRightArrowFunctionKey, KEY_RIGHT },
174 { NSF1FunctionKey, KEY_F1 },
175 { NSF2FunctionKey, KEY_F2 },
176 { NSF3FunctionKey, KEY_F3 },
177 { NSF4FunctionKey, KEY_F4 },
178 { NSF5FunctionKey, KEY_F5 },
179 { NSF6FunctionKey, KEY_F6 },
180 { NSF7FunctionKey, KEY_F7 },
181 { NSF8FunctionKey, KEY_F8 },
182 { NSF9FunctionKey, KEY_F9 },
183 { NSF10FunctionKey, KEY_F10 },
184 { NSF11FunctionKey, KEY_F11 },
185 { NSF12FunctionKey, KEY_F12 },
186 { NSInsertFunctionKey, KEY_INSERT },
187 { NSHomeFunctionKey, KEY_HOME },
188 { NSEndFunctionKey, KEY_END },
189 { NSPageUpFunctionKey, KEY_PAGEUP },
190 { NSPageDownFunctionKey, KEY_PAGEDOWN },
191 { NSMenuFunctionKey, KEY_MENU },
192 { NSTabCharacter, KEY_TAB },
193 { NSCarriageReturnCharacter, KEY_ENTER },
194 { NSEnterCharacter, KEY_ENTER },
195 { NSBackspaceCharacter, KEY_BACKSPACE },
196 { NSDeleteCharacter, KEY_DELETE },
201 * Takes the first value of an cocoa key string, and converts it to VLCs int representation.
203 unsigned int CocoaKeyToVLC(unichar i_key)
207 for (i = 0; nskeys_to_vlckeys[i].i_nskey != 0; i++) {
208 if (nskeys_to_vlckeys[i].i_nskey == i_key) {
209 return nskeys_to_vlckeys[i].i_vlckey;
212 return (unsigned int)i_key;
215 /* takes a good old const c string and converts it to NSString without UTF8 loss */
217 NSString *toNSStr(const char *str) {
218 return str != NULL ? [NSString stringWithUTF8String:str] : @"";
222 * Converts VLC key string to a prettified version, for hotkey settings.
223 * The returned string adapts similar how its done within the cocoa framework when setting this
226 - (NSString *)OSXStringKeyToString:(NSString *)theString
228 if (![theString isEqualToString:@""]) {
230 if ([theString characterAtIndex:([theString length] - 1)] != 0x2b)
231 theString = [theString stringByReplacingOccurrencesOfString:@"+" withString:@""];
233 theString = [theString stringByReplacingOccurrencesOfString:@"+" withString:@""];
234 theString = [NSString stringWithFormat:@"%@+", theString];
236 if ([theString characterAtIndex:([theString length] - 1)] != 0x2d)
237 theString = [theString stringByReplacingOccurrencesOfString:@"-" withString:@""];
239 theString = [theString stringByReplacingOccurrencesOfString:@"-" withString:@""];
240 theString = [NSString stringWithFormat:@"%@-", theString];
243 theString = [theString stringByReplacingOccurrencesOfString:@"Command" withString: [NSString stringWithUTF8String:"\xE2\x8C\x98"]];
244 theString = [theString stringByReplacingOccurrencesOfString:@"Alt" withString: [NSString stringWithUTF8String:"\xE2\x8C\xA5"]];
245 theString = [theString stringByReplacingOccurrencesOfString:@"Shift" withString: [NSString stringWithUTF8String:"\xE2\x87\xA7"]];
246 theString = [theString stringByReplacingOccurrencesOfString:@"Ctrl" withString: [NSString stringWithUTF8String:"\xE2\x8C\x83"]];
247 /* show non-character keys correctly */
248 theString = [theString stringByReplacingOccurrencesOfString:@"Right" withString:[NSString stringWithUTF8String:"\xE2\x86\x92"]];
249 theString = [theString stringByReplacingOccurrencesOfString:@"Left" withString:[NSString stringWithUTF8String:"\xE2\x86\x90"]];
250 theString = [theString stringByReplacingOccurrencesOfString:@"Page Up" withString:[NSString stringWithUTF8String:"\xE2\x87\x9E"]];
251 theString = [theString stringByReplacingOccurrencesOfString:@"Page Down" withString:[NSString stringWithUTF8String:"\xE2\x87\x9F"]];
252 theString = [theString stringByReplacingOccurrencesOfString:@"Up" withString:[NSString stringWithUTF8String:"\xE2\x86\x91"]];
253 theString = [theString stringByReplacingOccurrencesOfString:@"Down" withString:[NSString stringWithUTF8String:"\xE2\x86\x93"]];
254 theString = [theString stringByReplacingOccurrencesOfString:@"Enter" withString:[NSString stringWithUTF8String:"\xe2\x86\xb5"]];
255 theString = [theString stringByReplacingOccurrencesOfString:@"Tab" withString:[NSString stringWithUTF8String:"\xe2\x87\xa5"]];
256 theString = [theString stringByReplacingOccurrencesOfString:@"Delete" withString:[NSString stringWithUTF8String:"\xe2\x8c\xab"]]; /* capitalize plain characters to suit the menubar's look */
257 theString = [theString capitalizedString];
260 theString = [NSString stringWithString:_NS("Not Set")];
265 * Converts VLC key string to cocoa modifiers which can be used as setKeyEquivalent for menu items
267 - (unsigned int)VLCModifiersToCocoa:(NSString *)theString
269 unsigned int new = 0;
271 if ([theString rangeOfString:@"Command"].location != NSNotFound)
272 new |= NSCommandKeyMask;
273 if ([theString rangeOfString:@"Alt"].location != NSNotFound)
274 new |= NSAlternateKeyMask;
275 if ([theString rangeOfString:@"Shift"].location != NSNotFound)
276 new |= NSShiftKeyMask;
277 if ([theString rangeOfString:@"Ctrl"].location != NSNotFound)
278 new |= NSControlKeyMask;
283 * Converts VLC key to cocoa string which can be used as setKeyEquivalentModifierMask for menu items
285 - (NSString *)VLCKeyToString:(NSString *)theString
287 if (![theString isEqualToString:@""]) {
288 if ([theString characterAtIndex:([theString length] - 1)] != 0x2b)
289 theString = [theString stringByReplacingOccurrencesOfString:@"+" withString:@""];
291 theString = [theString stringByReplacingOccurrencesOfString:@"+" withString:@""];
292 theString = [NSString stringWithFormat:@"%@+", theString];
294 if ([theString characterAtIndex:([theString length] - 1)] != 0x2d)
295 theString = [theString stringByReplacingOccurrencesOfString:@"-" withString:@""];
297 theString = [theString stringByReplacingOccurrencesOfString:@"-" withString:@""];
298 theString = [NSString stringWithFormat:@"%@-", theString];
300 theString = [theString stringByReplacingOccurrencesOfString:@"Command" withString:@""];
301 theString = [theString stringByReplacingOccurrencesOfString:@"Alt" withString:@""];
302 theString = [theString stringByReplacingOccurrencesOfString:@"Shift" withString:@""];
303 theString = [theString stringByReplacingOccurrencesOfString:@"Ctrl" withString:@""];
307 #pragma GCC diagnostic ignored "-Wformat"
309 if ([theString length] > 1) {
310 if ([theString rangeOfString:@"Page Up"].location != NSNotFound)
311 return [NSString stringWithFormat:@"%C", NSPageUpFunctionKey];
312 else if ([theString rangeOfString:@"Page Down"].location != NSNotFound)
313 return [NSString stringWithFormat:@"%C", NSPageDownFunctionKey];
314 else if ([theString rangeOfString:@"Up"].location != NSNotFound)
315 return [NSString stringWithFormat:@"%C", NSUpArrowFunctionKey];
316 else if ([theString rangeOfString:@"Down"].location != NSNotFound)
317 return [NSString stringWithFormat:@"%C", NSDownArrowFunctionKey];
318 else if ([theString rangeOfString:@"Right"].location != NSNotFound)
319 return [NSString stringWithFormat:@"%C", NSRightArrowFunctionKey];
320 else if ([theString rangeOfString:@"Left"].location != NSNotFound)
321 return [NSString stringWithFormat:@"%C", NSLeftArrowFunctionKey];
322 else if ([theString rangeOfString:@"Enter"].location != NSNotFound)
323 return [NSString stringWithFormat:@"%C", NSEnterCharacter]; // we treat NSCarriageReturnCharacter as aquivalent
324 else if ([theString rangeOfString:@"Insert"].location != NSNotFound)
325 return [NSString stringWithFormat:@"%C", NSInsertFunctionKey];
326 else if ([theString rangeOfString:@"Home"].location != NSNotFound)
327 return [NSString stringWithFormat:@"%C", NSHomeFunctionKey];
328 else if ([theString rangeOfString:@"End"].location != NSNotFound)
329 return [NSString stringWithFormat:@"%C", NSEndFunctionKey];
330 else if ([theString rangeOfString:@"Menu"].location != NSNotFound)
331 return [NSString stringWithFormat:@"%C", NSMenuFunctionKey];
332 else if ([theString rangeOfString:@"Tab"].location != NSNotFound)
333 return [NSString stringWithFormat:@"%C", NSTabCharacter];
334 else if ([theString rangeOfString:@"Backspace"].location != NSNotFound)
335 return [NSString stringWithFormat:@"%C", NSBackspaceCharacter];
336 else if ([theString rangeOfString:@"Delete"].location != NSNotFound)
337 return [NSString stringWithFormat:@"%C", NSDeleteCharacter];
338 else if ([theString rangeOfString:@"F12"].location != NSNotFound)
339 return [NSString stringWithFormat:@"%C", NSF12FunctionKey];
340 else if ([theString rangeOfString:@"F11"].location != NSNotFound)
341 return [NSString stringWithFormat:@"%C", NSF11FunctionKey];
342 else if ([theString rangeOfString:@"F10"].location != NSNotFound)
343 return [NSString stringWithFormat:@"%C", NSF10FunctionKey];
344 else if ([theString rangeOfString:@"F9"].location != NSNotFound)
345 return [NSString stringWithFormat:@"%C", NSF9FunctionKey];
346 else if ([theString rangeOfString:@"F8"].location != NSNotFound)
347 return [NSString stringWithFormat:@"%C", NSF8FunctionKey];
348 else if ([theString rangeOfString:@"F7"].location != NSNotFound)
349 return [NSString stringWithFormat:@"%C", NSF7FunctionKey];
350 else if ([theString rangeOfString:@"F6"].location != NSNotFound)
351 return [NSString stringWithFormat:@"%C", NSF6FunctionKey];
352 else if ([theString rangeOfString:@"F5"].location != NSNotFound)
353 return [NSString stringWithFormat:@"%C", NSF5FunctionKey];
354 else if ([theString rangeOfString:@"F4"].location != NSNotFound)
355 return [NSString stringWithFormat:@"%C", NSF4FunctionKey];
356 else if ([theString rangeOfString:@"F3"].location != NSNotFound)
357 return [NSString stringWithFormat:@"%C", NSF3FunctionKey];
358 else if ([theString rangeOfString:@"F2"].location != NSNotFound)
359 return [NSString stringWithFormat:@"%C", NSF2FunctionKey];
360 else if ([theString rangeOfString:@"F1"].location != NSNotFound)
361 return [NSString stringWithFormat:@"%C", NSF1FunctionKey];
362 else if ([theString rangeOfString:@"Space"].location != NSNotFound)
364 /* note that we don't support esc here, since it is reserved for leaving fullscreen */
367 #pragma GCC diagnostic warning "-Wformat"
374 #pragma mark base64 helpers
376 - (NSString *)b64Decode:(NSString *)string
378 char *psz_decoded_string = vlc_b64_decode([string UTF8String]);
379 if(!psz_decoded_string)
382 NSString *returnStr = [NSString stringWithFormat:@"%s", psz_decoded_string];
383 free(psz_decoded_string);
388 - (NSString *)b64EncodeAndFree:(char *)psz_string
390 char *psz_encoded_string = vlc_b64_encode(psz_string);
392 if(!psz_encoded_string)
395 NSString *returnStr = [NSString stringWithFormat:@"%s", psz_encoded_string];
396 free(psz_encoded_string);
401 - (NSString *) getBSDNodeFromMountPath:(NSString *)mountPath
405 FSVolumeRefNum actualVolume;
406 err = FSPathMakeRef ((const UInt8 *) [mountPath fileSystemRepresentation], &ref, NULL);
408 // get a FSVolumeRefNum from mountPath
410 FSCatalogInfo catalogInfo;
411 err = FSGetCatalogInfo (&ref,
419 actualVolume = catalogInfo.volume;
426 GetVolParmsInfoBuffer volumeParms;
427 err = FSGetVolumeParms(actualVolume, &volumeParms, sizeof(volumeParms));
429 NSString *bsdName = [NSString stringWithUTF8String:(char *)volumeParms.vMDeviceID];
430 return [NSString stringWithFormat:@"/dev/r%@", bsdName];
436 - (NSString *)getVolumeTypeFromMountPath:(NSString *)mountPath
440 FSVolumeRefNum actualVolume;
441 NSString *returnValue;
442 err = FSPathMakeRef ((const UInt8 *) [mountPath fileSystemRepresentation], &ref, NULL);
444 // get a FSVolumeRefNum from mountPath
446 FSCatalogInfo catalogInfo;
447 err = FSGetCatalogInfo (&ref,
455 actualVolume = catalogInfo.volume;
462 GetVolParmsInfoBuffer volumeParms;
463 err = FSGetVolumeParms(actualVolume, &volumeParms, sizeof(volumeParms));
465 CFMutableDictionaryRef matchingDict;
466 io_service_t service;
468 if (!volumeParms.vMDeviceID) {
472 matchingDict = IOBSDNameMatching(kIOMasterPortDefault, 0, volumeParms.vMDeviceID);
473 service = IOServiceGetMatchingService(kIOMasterPortDefault, matchingDict);
475 if (IO_OBJECT_NULL != service) {
476 if (IOObjectConformsTo(service, kIOCDMediaClass))
477 returnValue = kVLCMediaAudioCD;
478 else if (IOObjectConformsTo(service, kIODVDMediaClass))
479 returnValue = kVLCMediaDVD;
480 else if (IOObjectConformsTo(service, kIOBDMediaClass))
481 returnValue = kVLCMediaBD;
482 IOObjectRelease(service);
489 if ([mountPath rangeOfString:@"VIDEO_TS" options:NSCaseInsensitiveSearch | NSBackwardsSearch].location != NSNotFound)
490 returnValue = kVLCMediaVideoTSFolder;
491 else if ([mountPath rangeOfString:@"BDMV" options:NSCaseInsensitiveSearch | NSBackwardsSearch].location != NSNotFound)
492 returnValue = kVLCMediaBDMVFolder;
494 // NSFileManager is not thread-safe, don't use defaultManager outside of the main thread
495 NSFileManager * fm = [[NSFileManager alloc] init];
497 NSArray *dirContents = [fm contentsOfDirectoryAtPath:mountPath error:nil];
498 for (int i = 0; i < [dirContents count]; i++) {
499 NSString *currentFile = [dirContents objectAtIndex:i];
500 NSString *fullPath = [mountPath stringByAppendingPathComponent:currentFile];
503 if ([fm fileExistsAtPath:fullPath isDirectory:&isDir] && isDir)
505 if ([currentFile caseInsensitiveCompare:@"SVCD"] == NSOrderedSame) {
506 returnValue = kVLCMediaSVCD;
509 if ([currentFile caseInsensitiveCompare:@"VCD"] == NSOrderedSame) {
510 returnValue = kVLCMediaVCD;
513 if ([currentFile caseInsensitiveCompare:@"BDMV"] == NSOrderedSame) {
514 returnValue = kVLCMediaBDMVFolder;
517 if ([currentFile caseInsensitiveCompare:@"VIDEO_TS"] == NSOrderedSame) {
518 returnValue = kVLCMediaVideoTSFolder;
525 returnValue = kVLCMediaVideoTSFolder;
533 NSImage *imageFromRes(NSString *o_id)
535 NSString *result = @"";
536 if (OSX_YOSEMITE_AND_HIGHER) {
537 result = [result stringByAppendingString:@"ys-"];
540 result = [result stringByAppendingString:o_id];
542 return [NSImage imageNamed:result];