1/*
2 This file is part of Warzone 2100.
3 Copyright (C) 1999-2004  Eidos Interactive
4 Copyright (C) 2005-2020  Warzone 2100 Project
5
6 Warzone 2100 is free software; you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation; either version 2 of the License, or
9 (at your option) any later version.
10
11 Warzone 2100 is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 GNU General Public License for more details.
15
16 You should have received a copy of the GNU General Public License
17 along with Warzone 2100; if not, write to the Free Software
18 Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
19 */
20
21#import "cocoa_wrapper.h"
22
23#ifdef WZ_OS_MAC
24
25#if ! __has_feature(objc_arc)
26#error "Objective-C ARC (Automatic Reference Counting) is off"
27#endif
28
29#import <AppKit/AppKit.h>
30#import <ApplicationServices/ApplicationServices.h>
31
32static inline NSString * _Nonnull nsstringify(const char *str)
33{
34    NSString * nsString = [NSString stringWithUTF8String:str];
35    if (nsString == nil)
36    {
37        return @"stringWithUTF8String failed";
38    }
39	return nsString;
40}
41
42int cocoaShowAlert(const char *message, const char *information, unsigned style,
43                   const char *buttonTitle, ...)
44{
45    NSInteger buttonID = -1;
46    @autoreleasepool {
47        NSAlert *alert = [[NSAlert alloc] init];
48        [alert setMessageText:nsstringify(message)];
49        [alert setInformativeText:nsstringify(information)];
50        [alert setAlertStyle:(NSAlertStyle)style];
51
52        va_list args;
53        va_start(args, buttonTitle);
54        const char *currentButtonTitle = buttonTitle;
55        do {
56            [alert addButtonWithTitle:nsstringify(currentButtonTitle)];
57        } while ((currentButtonTitle = va_arg(args, const char *)));
58        va_end(args);
59
60        buttonID = [alert runModal];
61    }
62    return buttonID - NSAlertFirstButtonReturn;
63}
64
65bool cocoaSelectFileInFinder(const char *filename)
66{
67    if (filename == nullptr) return false;
68    BOOL success = NO;
69	@autoreleasepool {
70        success = [[NSWorkspace sharedWorkspace] selectFile:nsstringify(filename) inFileViewerRootedAtPath:@""];
71    }
72    return success;
73}
74
75bool cocoaSelectFolderInFinder(const char* path)
76{
77	if (path == nullptr) return false;
78	BOOL success = NO;
79	@autoreleasepool {
80		NSURL *pathURL = [NSURL fileURLWithPath:nsstringify(path) isDirectory:YES];
81		if (pathURL == nil) return false;
82		success = [[NSWorkspace sharedWorkspace] openURL:pathURL];
83	}
84	return success;
85}
86
87bool cocoaOpenURL(const char *url)
88{
89    assert(url != nullptr);
90    BOOL success = NO;
91	@autoreleasepool {
92        success = [[NSWorkspace sharedWorkspace] openURL:[NSURL URLWithString:nsstringify(url)]];
93    }
94    return success;
95}
96
97bool cocoaOpenUserCrashReportFolder()
98{
99    BOOL success = NO;
100    @autoreleasepool {
101        NSArray *paths = NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSUserDomainMask, YES);
102        NSString *libraryPath = ([paths count] > 0) ? [paths objectAtIndex:0] : nil;
103        if (libraryPath == nil) return false;
104        NSURL *libraryURL = [NSURL fileURLWithPath:libraryPath isDirectory:YES];
105        NSURL *crashReportsURL = [NSURL URLWithString:@"Logs/DiagnosticReports" relativeToURL:libraryURL];
106        success = [[NSWorkspace sharedWorkspace] openURL:crashReportsURL];
107    }
108    return success;
109}
110
111bool cocoaGetApplicationSupportDir(char *const tmpstr, size_t const size)
112{
113	@autoreleasepool {
114		NSArray *paths = NSSearchPathForDirectoriesInDomains(NSApplicationSupportDirectory, NSUserDomainMask, TRUE);
115		NSString *path = ([paths count] > 0) ? [paths objectAtIndex:0] : nil;
116		if (path == nil) return false;
117		BOOL success = [path getCString:tmpstr maxLength:size encoding:NSUTF8StringEncoding];
118		return success;
119	}
120}
121
122bool cocoaSetFileQuarantineAttribute(const char *path)
123{
124	@autoreleasepool {
125		//	kLSQuarantineTypeKey -> kLSQuarantineTypeOtherDownload
126		NSDictionary * quarantineProperties = @{
127			(NSString *)kLSQuarantineTypeKey : (NSString *)kLSQuarantineTypeOtherDownload
128		};
129
130//		if (@available(macOS 10.10, *)) {	// "@available" is only available on Xcode 9+
131		if (floor(NSAppKitVersionNumber) >= NSAppKitVersionNumber10_10) { // alternative to @available
132			NSURL *fileURL = [NSURL fileURLWithFileSystemRepresentation:path isDirectory:NO relativeToURL:NULL];
133			if (fileURL == nil) return false;
134
135#pragma clang diagnostic push
136#pragma clang diagnostic ignored "-Wunguarded-availability" // only required until we can use @available
137			NSError *error = nil;
138			BOOL success = [fileURL setResourceValue:quarantineProperties forKey:NSURLQuarantinePropertiesKey error:&error];
139#pragma clang diagnostic pop
140			if (!success)
141			{
142				// Failed to set resource value
143				NSLog(@"Failed to set resource file: %@", error);
144				return false;
145			}
146		} else {
147			// macOS 10.9 and earlier require now-deprecated APIs
148			return false;
149		}
150		return true;
151	}
152}
153
154bool TransformProcessState(ProcessApplicationTransformState newState)
155{
156	@autoreleasepool {
157		ProcessSerialNumber psn = {0, kCurrentProcess};
158		OSStatus result = TransformProcessType(&psn, newState);
159
160		if (result != 0)
161		{
162			NSError *error = [NSError errorWithDomain:NSOSStatusErrorDomain code:result userInfo:nil];
163			NSLog(@"TransformProcessType failed with error - %@", error);
164		}
165		return (result == 0);
166	}
167}
168
169bool cocoaTransformToBackgroundApplication()
170{
171    return TransformProcessState(kProcessTransformToBackgroundApplication);
172}
173
174#endif // WZ_OS_MAC
175