├── webloc.png ├── .gitignore ├── MBBase64.h ├── deployment-files ├── example launchAgents │ └── org.hasseg.setWeblocThumb.desktop.plist ├── install.command ├── readme.txt └── LICENSE-2.0.txt ├── launchAgentGen.h ├── HGCLIUtils.h ├── readme.md ├── MBBase64.m ├── Makefile ├── launchAgentGen.m ├── HGCLIUtils.m ├── ANSIEscapeHelper.h ├── setWeblocThumb.m └── ANSIEscapeHelper.m /webloc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ali-rantakari/setWeblocThumb/HEAD/webloc.png -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | imgBase64.m 2 | setWeblocThumb 3 | usage.txt 4 | deploymentScpTarget 5 | deployment/* 6 | *.plist 7 | changelog.html 8 | 9 | -------------------------------------------------------------------------------- /MBBase64.h: -------------------------------------------------------------------------------- 1 | // from: http://www.cocoadev.com/index.pl?BaseSixtyFour 2 | // by MiloBird (http://www.cocoadev.com/index.pl?MiloBird) 3 | // "It's public domain, just use it." 4 | // 5 | #import 6 | 7 | 8 | @interface NSData (MBBase64) 9 | 10 | + (id)dataWithBase64EncodedString:(NSString *)string; // Padding '=' characters are optional. Whitespace is ignored. 11 | - (NSString *)base64Encoding; 12 | 13 | @end 14 | 15 | -------------------------------------------------------------------------------- /deployment-files/example launchAgents/org.hasseg.setWeblocThumb.desktop.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Label 6 | org.hasseg.setWeblocThumb.desktop 7 | ProgramArguments 8 | 9 | /usr/local/bin/setWeblocThumb 10 | /Users/your_username/Desktop 11 | 12 | WatchPaths 13 | 14 | /Users/your_username/Desktop 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /launchAgentGen.h: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | launchAgentGen.h 4 | setWeblocThumb 5 | 6 | Copyright (c) 2009-2012 Ali Rantakari (http://hasseg.org) 7 | 8 | -------------- 9 | 10 | Licensed under the Apache License, Version 2.0 (the "License"); you may 11 | not use this file except in compliance with the License. You may obtain 12 | a copy of the License at 13 | 14 | http://www.apache.org/licenses/LICENSE-2.0 15 | 16 | Unless required by applicable law or agreed to in writing, software 17 | distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 18 | WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 19 | License for the specific language governing permissions and limitations 20 | under the License. 21 | 22 | */ 23 | 24 | #import 25 | 26 | BOOL generateLaunchAgent(NSString *targetPath); 27 | void printLaunchAgentWatchPaths(); 28 | 29 | -------------------------------------------------------------------------------- /deployment-files/install.command: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # install script for setWeblocThumb 4 | # (c) 2009 Ali Rantakari 5 | # 6 | 7 | DN="`dirname \"$0\"`" 8 | THISDIR="`cd \"$DN\"; pwd`" 9 | 10 | BINDIR=/usr/local/bin 11 | 12 | BINFILE="${THISDIR}/setWeblocThumb" 13 | 14 | 15 | 16 | if [ ! -e "${BINFILE}" ];then 17 | echo "Error: can not find \"${BINFILE}\". Make sure you're running this script from within the distribution directory (the same directory where setWeblocThumb resides.)" 18 | exit 1 19 | fi 20 | echo "=================================" 21 | echo 22 | echo "This script will install:" 23 | echo 24 | printf "setWeblocThumb executable to: \e[36m${BINDIR}\e[m\n" 25 | echo 26 | echo $'We\'ll need administrator rights to install to this location so \e[33mplease enter your admin password when asked\e[m.' 27 | echo $'\e[1mPress any key to continue installing or Ctrl-C to cancel.\e[m' 28 | read 29 | echo 30 | sudo -v 31 | if [ ! $? -eq 0 ];then echo "error! aborting." >&2; exit 10; fi 32 | echo 33 | 34 | echo -n "Creating directories..." 35 | sudo mkdir -p ${BINDIR} 36 | if [ ! $? -eq 0 ];then echo "...error! aborting." >&2; exit 10; fi 37 | echo "done." 38 | 39 | echo -n "Installing the binary executable..." 40 | sudo cp -f "${BINFILE}" "${BINDIR}" 41 | if [ ! $? -eq 0 ];then echo "...error! aborting." >&2; exit 10; fi 42 | echo "done." 43 | 44 | echo 45 | echo $'\e[32msetWeblocThumb has been successfully installed.\e[m' 46 | echo 47 | 48 | -------------------------------------------------------------------------------- /deployment-files/readme.txt: -------------------------------------------------------------------------------- 1 | 2 | setWeblocThumb 3 | ------------------------- 4 | 5 | DESCRIPTION: 6 | 7 | This is a small command-line program for OS X that 8 | assigns custom icons to Web Internet Location (.webloc) 9 | files, displaying a thumbnail of the web page that 10 | they point to. 11 | 12 | setWeblocThumb requires Mac OS 10.5 (Leopard) or 13 | later. 14 | 15 | Copyright (c) 2009 Ali Rantakari 16 | http://hasseg.org/setWeblocThumb 17 | 18 | 19 | USAGE: 20 | 21 | Run without any arguments to see the usage info. 22 | 23 | If you'd like to have this program run automatically 24 | whenever you add .webloc files somewhere (e.g. by 25 | dragging them from your web browser windows), I suggest 26 | using a launch agent. An example launchd configuration 27 | file has been provided, which asks the system to run 28 | setWeblocThumb every time the Desktop folder is modified 29 | (e.g. if some files are added there). Below are some 30 | directions on how to take it into use: 31 | 32 | 1. Make a copy of the provided example launch agent
 configuration file into the LaunchAgents folder in 33 | your Library: 34 | 35 | $ mkdir -p ~/Library/LaunchAgents 36 | $ cp "example launchAgents/org.hasseg.setWeblocThumb.desktop.plist" ~/Library/LaunchAgents/. 37 | 38 | 2. Edit this file and verify that the paths are correct 39 | (i.e. replace "your_username" with your actual username, 40 | or change the paths completely). 41 | 42 | 3. Tell launchd to load this launch agent: 43 | 44 | $ launchctl load ~/Library/LaunchAgents/org.hasseg.setWeblocThumb.desktop.plist 45 | 46 | To learn more about launchd, launchctl and launch 47 | agents, please refer to Apple's documentation: 48 | 49 | http://developer.apple.com/MacOsX/launchd.html 50 | 51 | Also note that you can achieve similar functionality 52 | (i.e. run setWeblocThumb every time files are added to 53 | a particular folder) by other means as well, such as 54 | folder actions and the Hazel application. I use launchd 55 | simply because folder actions don't quite work for me 56 | (on Mac OS 10.5.7) and I don't own a license to Hazel. 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | -------------------------------------------------------------------------------- /HGCLIUtils.h: -------------------------------------------------------------------------------- 1 | // CLI app utils 2 | // 3 | // http://hasseg.org/ 4 | // 5 | 6 | /* 7 | The MIT License 8 | 9 | Copyright (c) 2010 Ali Rantakari 10 | 11 | Permission is hereby granted, free of charge, to any person obtaining a copy 12 | of this software and associated documentation files (the "Software"), to deal 13 | in the Software without restriction, including without limitation the rights 14 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 15 | copies of the Software, and to permit persons to whom the Software is 16 | furnished to do so, subject to the following conditions: 17 | 18 | The above copyright notice and this permission notice shall be included in 19 | all copies or substantial portions of the Software. 20 | 21 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 22 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 23 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 24 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 25 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 26 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 27 | THE SOFTWARE. 28 | */ 29 | 30 | #import 31 | 32 | 33 | extern NSStringEncoding outputStrEncoding; 34 | 35 | 36 | // helper function macros 37 | #define kEmptyMutableAttributedString [[[NSMutableAttributedString alloc] init] autorelease] 38 | #define MUTABLE_ATTR_STR(x) [[[NSMutableAttributedString alloc] initWithString:(x)] autorelease] 39 | #define ATTR_STR(x) [[[NSAttributedString alloc] initWithString:(x)] autorelease] 40 | #define WHITESPACE(x) [@"" stringByPaddingToLength:(x) withString:@" " startingAtIndex:0] 41 | 42 | 43 | 44 | void Print(NSString *aStr); 45 | void Printf(NSString *aStr, ...); 46 | void PrintfErr(NSString *aStr, ...); 47 | 48 | NSString *strConcat(NSString *firstStr, ...); 49 | NSString *escapeDoubleQuotes(NSString *str); 50 | 51 | void replaceInMutableAttrStr(NSMutableAttributedString *str, NSString *searchStr, NSAttributedString *replaceStr); 52 | void wordWrapMutableAttrStr(NSMutableAttributedString *mutableAttrStr, NSUInteger width); 53 | 54 | BOOL moveFileToTrash(NSString *filePath); 55 | 56 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # setWeblocThumb 2 | 3 | This is a small command-line program for OS X that assigns custom icons to Web Internet Location (`.webloc`) files, displaying a thumbnail of the web page that they point to. 4 | 5 | 6 | ## Automating It 7 | 8 | If you'd like to have this program run automatically whenever you add .webloc files somewhere (e.g. by dragging them from your web browser windows), you can do this in a number of ways. If you have a license to the [Hazel] application, that might be easiest, but other ways are _folder actions (via Automator)_ and _launch agents_, which I'll explain below. 9 | 10 | ### Launch Agents 11 | 12 | A launch agent is a configuration that tells the launchd system to do something (e.g. run `setWeblocThumb`) when something happens (e.g. a file is added to a specific folder). 13 | 14 | You can use `setWeblocThumb`'s `-a` argument to generate launch agents that watch certain paths in your filesystem. For example, run the following command to have it run every time you add files onto your desktop: 15 | 16 | setWeblocThumb -a ~/Desktop 17 | 18 | To learn more about `launchd`, `launchctl` and launch agents, please refer to [Apple's documentation][launchd-apple] and the [Wikipedia article][launchd-wikipedia]. 19 | 20 | ### Folder Actions 21 | 22 | You can use [Automator] to create a folder action that runs `setWeblocThumb` whenever files are added to a particular folder. 23 | 24 | _(Note: these directions are written for Snow Leopard)_ 25 | 26 | 1. Open Automator and select the Folder Action template 27 | 1. Select the folder you'd like to attach this action to from the combo box in the upper right-hand corner where it says "Folder Action receives files and folders added to" 28 | 1. Drag the Run Shell Script action from the list in the left (it's under Utilities) to the action area on the right 29 | 1. Select Pass input: as arguments in the action's settings 30 | 1. Type the following into the shell script action's text field: 31 | 32 | /usr/local/bin/setWeblocThumb "`dirname \"$1\"`" 33 | 34 | 1. Save 35 | 36 | [Hazel]: http://www.noodlesoft.com/hazel.php 37 | [launchd-wikipedia]: http://en.wikipedia.org/wiki/Launchd 38 | [launchd-apple]: http://developer.apple.com/MacOsX/launchd.html 39 | [Automator]: http://www.apple.com/macosx/what-is-macosx/apps-and-utilities.html#automator 40 | 41 | 42 | ## License 43 | 44 | Licensed under the Apache License, Version 2.0 (the "License"); you may 45 | not use this file except in compliance with the License. You may obtain 46 | a copy of the License at 47 | 48 | 49 | 50 | Unless required by applicable law or agreed to in writing, software 51 | distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 52 | WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 53 | License for the specific language governing permissions and limitations 54 | under the License. 55 | -------------------------------------------------------------------------------- /MBBase64.m: -------------------------------------------------------------------------------- 1 | // from: http://www.cocoadev.com/index.pl?BaseSixtyFour 2 | // by MiloBird (http://www.cocoadev.com/index.pl?MiloBird) 3 | // "It's public domain, just use it." 4 | // 5 | #import "MBBase64.h" 6 | 7 | 8 | static const char encodingTable[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; 9 | 10 | 11 | @implementation NSData (MBBase64) 12 | 13 | + (id)dataWithBase64EncodedString:(NSString *)string; 14 | { 15 | if (string == nil) 16 | [NSException raise:NSInvalidArgumentException format:@""]; 17 | if ([string length] == 0) 18 | return [NSData data]; 19 | 20 | static char *decodingTable = NULL; 21 | if (decodingTable == NULL) 22 | { 23 | decodingTable = malloc(256); 24 | if (decodingTable == NULL) 25 | return nil; 26 | memset(decodingTable, CHAR_MAX, 256); 27 | NSUInteger i; 28 | for (i = 0; i < 64; i++) 29 | decodingTable[(short)encodingTable[i]] = i; 30 | } 31 | 32 | const char *characters = [string cStringUsingEncoding:NSASCIIStringEncoding]; 33 | if (characters == NULL) // Not an ASCII string! 34 | return nil; 35 | char *bytes = malloc((([string length] + 3) / 4) * 3); 36 | if (bytes == NULL) 37 | return nil; 38 | NSUInteger length = 0; 39 | 40 | NSUInteger i = 0; 41 | while (YES) 42 | { 43 | char buffer[4]; 44 | short bufferLength; 45 | for (bufferLength = 0; bufferLength < 4; i++) 46 | { 47 | if (characters[i] == '\0') 48 | break; 49 | if (isspace(characters[i]) || characters[i] == '=') 50 | continue; 51 | buffer[bufferLength] = decodingTable[(short)characters[i]]; 52 | if (buffer[bufferLength++] == CHAR_MAX) // Illegal character! 53 | { 54 | free(bytes); 55 | return nil; 56 | } 57 | } 58 | 59 | if (bufferLength == 0) 60 | break; 61 | if (bufferLength == 1) // At least two characters are needed to produce one byte! 62 | { 63 | free(bytes); 64 | return nil; 65 | } 66 | 67 | // Decode the characters in the buffer to bytes. 68 | bytes[length++] = (buffer[0] << 2) | (buffer[1] >> 4); 69 | if (bufferLength > 2) 70 | bytes[length++] = (buffer[1] << 4) | (buffer[2] >> 2); 71 | if (bufferLength > 3) 72 | bytes[length++] = (buffer[2] << 6) | buffer[3]; 73 | } 74 | 75 | realloc(bytes, length); 76 | return [NSData dataWithBytesNoCopy:bytes length:length]; 77 | } 78 | 79 | - (NSString *)base64Encoding; 80 | { 81 | if ([self length] == 0) 82 | return @""; 83 | 84 | char *characters = malloc((([self length] + 2) / 3) * 4); 85 | if (characters == NULL) 86 | return nil; 87 | NSUInteger length = 0; 88 | 89 | NSUInteger i = 0; 90 | while (i < [self length]) 91 | { 92 | char buffer[3] = {0,0,0}; 93 | short bufferLength = 0; 94 | while (bufferLength < 3 && i < [self length]) 95 | buffer[bufferLength++] = ((char *)[self bytes])[i++]; 96 | 97 | // Encode the bytes in the buffer to four characters, including padding "=" characters if necessary. 98 | characters[length++] = encodingTable[(buffer[0] & 0xFC) >> 2]; 99 | characters[length++] = encodingTable[((buffer[0] & 0x03) << 4) | ((buffer[1] & 0xF0) >> 4)]; 100 | if (bufferLength > 1) 101 | characters[length++] = encodingTable[((buffer[1] & 0x0F) << 2) | ((buffer[2] & 0xC0) >> 6)]; 102 | else characters[length++] = '='; 103 | if (bufferLength > 2) 104 | characters[length++] = encodingTable[buffer[2] & 0x3F]; 105 | else characters[length++] = '='; 106 | } 107 | 108 | return [[[NSString alloc] initWithBytesNoCopy:characters length:length encoding:NSASCIIStringEncoding freeWhenDone:YES] autorelease]; 109 | } 110 | 111 | @end 112 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # setWeblocThumb makefile 2 | # 3 | # Created by Ali Rantakari on 4 Aug, 2009 4 | # 5 | 6 | SHELL=/bin/bash 7 | 8 | SCP_TARGET=$(shell cat ./deploymentScpTarget) 9 | APP_VERSION=$(shell grep "^const int VERSION_" setWeblocThumb.m | awk '{print $$NF}' | tr -d ';' | tr '\n' '.' | sed -e 's/.$$//') 10 | VERSION_ON_SERVER=$(shell curl -Ss http://hasseg.org/setWeblocThumb/?versioncheck=y) 11 | TEMP_DEPLOYMENT_DIR=deployment/$(APP_VERSION) 12 | TEMP_DEPLOYMENT_ZIPFILE=$(TEMP_DEPLOYMENT_DIR)/setWeblocThumb-v$(APP_VERSION).zip 13 | TEMP_DEPLOYMENT_USAGEFILE="usage.txt" 14 | VERSIONCHANGELOGFILELOC="$(TEMP_DEPLOYMENT_DIR)/changelog.html" 15 | GENERALCHANGELOGFILELOC="changelog.html" 16 | SCP_TARGET=$(shell cat ./deploymentScpTarget) 17 | DEPLOYMENT_INCLUDES_DIR="./deployment-files" 18 | 19 | COMPILER=clang 20 | 21 | 22 | SOURCE_FILES=setWeblocThumb.m launchAgentGen.m MBBase64.m ANSIEscapeHelper.m HGCLIUtils.m 23 | 24 | 25 | 26 | all: setWeblocThumb 27 | 28 | docs: usage.txt 29 | 30 | 31 | usage.txt: setWeblocThumb 32 | @echo 33 | @echo ---- generating usage.txt: 34 | @echo ====================================== 35 | ./setWeblocThumb > usage.txt 36 | 37 | 38 | 39 | #------------------------------------------------------------------------- 40 | #------------------------------------------------------------------------- 41 | # generate imgBase64.m with the imgBase64 NSString* 42 | #------------------------------------------------------------------------- 43 | imgBase64.m: webloc.png 44 | @echo 45 | @echo ---- Base64-encoding base icon PNG: 46 | @echo ====================================== 47 | echo -n "NSString *imgBase64 = @\"" > imgBase64.m 48 | cat webloc.png | openssl base64 -e | tr -d '\n' >> imgBase64.m 49 | echo "\";" >> imgBase64.m 50 | 51 | 52 | #------------------------------------------------------------------------- 53 | #------------------------------------------------------------------------- 54 | # compile the binary itself 55 | #------------------------------------------------------------------------- 56 | setWeblocThumb: $(SOURCE_FILES) imgBase64.m 57 | @echo 58 | @echo ---- Compiling: 59 | @echo ====================================== 60 | $(COMPILER) -O3 -Wall -force_cpusubtype_ALL -mmacosx-version-min=10.5 -arch i386 -framework Cocoa -framework WebKit -o $@ $(SOURCE_FILES) 61 | 62 | 63 | 64 | 65 | #------------------------------------------------------------------------- 66 | #------------------------------------------------------------------------- 67 | # run clang static analyzer 68 | #------------------------------------------------------------------------- 69 | analyze: 70 | @echo 71 | @echo ---- Analyzing: 72 | @echo ====================================== 73 | $(COMPILER_CLANG) --analyze $(SOURCE_FILES) 74 | 75 | 76 | 77 | 78 | 79 | #------------------------------------------------------------------------- 80 | #------------------------------------------------------------------------- 81 | # make release package (prepare for deployment) 82 | #------------------------------------------------------------------------- 83 | package: setWeblocThumb docs 84 | @echo 85 | @echo ---- Preparing for deployment: 86 | @echo ====================================== 87 | 88 | # create zip archive 89 | mkdir -p $(TEMP_DEPLOYMENT_DIR) 90 | echo "-D -j $(TEMP_DEPLOYMENT_ZIPFILE) setWeblocThumb" | xargs zip 91 | cd "$(DEPLOYMENT_INCLUDES_DIR)"; echo "-g -R ../$(TEMP_DEPLOYMENT_ZIPFILE) *" | xargs zip 92 | 93 | # if changelog doesn't already exist in the deployment dir 94 | # for this version, get 'general' changelog file from root if 95 | # one exists, and if not, create an empty changelog file 96 | @( if [ ! -e $(VERSIONCHANGELOGFILELOC) ];then\ 97 | if [ -e $(GENERALCHANGELOGFILELOC) ];then\ 98 | cp $(GENERALCHANGELOGFILELOC) $(VERSIONCHANGELOGFILELOC);\ 99 | echo "Copied existing changelog.html from project root into deployment dir - opening it for editing";\ 100 | else\ 101 | echo -e "
    \n
  • \n
\n" > $(VERSIONCHANGELOGFILELOC);\ 102 | echo "Created new empty changelog.html into deployment dir - opening it for editing";\ 103 | fi; \ 104 | else\ 105 | echo "changelog.html exists for $(APP_VERSION) - opening it for editing";\ 106 | fi ) 107 | @open -a Smultron $(VERSIONCHANGELOGFILELOC) 108 | 109 | 110 | 111 | 112 | #------------------------------------------------------------------------- 113 | #------------------------------------------------------------------------- 114 | # deploy to server 115 | #------------------------------------------------------------------------- 116 | deploy: package 117 | @echo 118 | @echo ---- Deploying to server: 119 | @echo ====================================== 120 | 121 | @echo "Checking latest version number vs. current version number..." 122 | @( if [ "$(VERSION_ON_SERVER)" != "$(APP_VERSION)" ];then\ 123 | echo "Latest version on server is $(VERSION_ON_SERVER). Uploading $(APP_VERSION).";\ 124 | else\ 125 | echo "NOTE: Current version exists on server: ($(APP_VERSION)).";\ 126 | fi;\ 127 | echo "Press enter to continue uploading to server or Ctrl-C to cancel.";\ 128 | read INPUTSTR;\ 129 | scp -r $(TEMP_DEPLOYMENT_DIR) $(TEMP_DEPLOYMENT_USAGEFILE) $(SCP_TARGET); ) 130 | 131 | 132 | 133 | 134 | 135 | #------------------------------------------------------------------------- 136 | #------------------------------------------------------------------------- 137 | clean: 138 | @echo 139 | @echo ---- Cleaning up: 140 | @echo ====================================== 141 | -rm -Rf setWeblocThumb 142 | -rm -Rf imgBase64.m 143 | -rm -Rf usage.txt 144 | 145 | 146 | 147 | -------------------------------------------------------------------------------- /launchAgentGen.m: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | launchAgentGen.m 4 | setWeblocThumb 5 | 6 | Copyright (c) 2009-2012 Ali Rantakari (http://hasseg.org) 7 | 8 | -------------- 9 | 10 | Licensed under the Apache License, Version 2.0 (the "License"); you may 11 | not use this file except in compliance with the License. You may obtain 12 | a copy of the License at 13 | 14 | http://www.apache.org/licenses/LICENSE-2.0 15 | 16 | Unless required by applicable law or agreed to in writing, software 17 | distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 18 | WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 19 | License for the specific language governing permissions and limitations 20 | under the License. 21 | 22 | */ 23 | 24 | #import "launchAgentGen.h" 25 | 26 | #include // for _NSGetExecutablePath() 27 | #import "HGCLIUtils.h" 28 | 29 | 30 | NSString *encodeForXML(NSString *aStr) 31 | { 32 | NSMutableString *str = [[aStr mutableCopy] autorelease]; 33 | 34 | [str replaceOccurrencesOfString:@"&" withString:@"&" options:NSLiteralSearch range:NSMakeRange(0, [str length])]; 35 | [str replaceOccurrencesOfString:@"\"" withString:@""" options:NSLiteralSearch range:NSMakeRange(0, [str length])]; 36 | [str replaceOccurrencesOfString:@"'" withString:@"'" options:NSLiteralSearch range:NSMakeRange(0, [str length])]; 37 | [str replaceOccurrencesOfString:@">" withString:@">" options:NSLiteralSearch range:NSMakeRange(0, [str length])]; 38 | [str replaceOccurrencesOfString:@"<" withString:@"<" options:NSLiteralSearch range:NSMakeRange(0, [str length])]; 39 | 40 | return str; 41 | } 42 | 43 | 44 | static NSString *launchAgentXMLFormat = 45 | @"\n" 46 | @"\n" 47 | @"\n" 48 | @"\n" 49 | @" Label\n" 50 | @" %@\n" 51 | @" ProgramArguments\n" 52 | @" \n" 53 | @" %@\n" 54 | @" %@\n" 55 | @" \n" 56 | @" WatchPaths\n" 57 | @" \n" 58 | @" %@\n" 59 | @" \n" 60 | @"\n" 61 | @""; 62 | 63 | #define LAUNCHCTL_PATH @"/bin/launchctl" 64 | #define USER_LAUNCH_AGENTS_PATH [@"~/Library/LaunchAgents" stringByStandardizingPath] 65 | #define LAUNCH_AGENT_LABEL_PREFIX @"org.hasseg.setWeblocThumb." 66 | 67 | NSString *getExecutablePath() 68 | { 69 | // (If buffer is not long enough, _NSGetExecutablePath() sets buflen to 70 | // the correct length and returns -1) 71 | uint32_t buflen = 0; 72 | _NSGetExecutablePath(NULL, &buflen); 73 | char buf[buflen]; 74 | _NSGetExecutablePath(buf, &buflen); 75 | 76 | return [[NSString stringWithUTF8String:buf] stringByStandardizingPath]; 77 | } 78 | 79 | BOOL generateLaunchAgent(NSString *targetPath) 80 | { 81 | // Determine absolute target path 82 | NSString *absTargetPath = nil; 83 | if ([targetPath isAbsolutePath]) 84 | absTargetPath = targetPath; 85 | else 86 | absTargetPath = [targetPath stringByStandardizingPath]; 87 | 88 | if (![absTargetPath isAbsolutePath]) 89 | { 90 | PrintfErr(@"Cannot make path absolute: %@\n", targetPath); 91 | PrintfErr(@"Please provide an absolute path.\n"); 92 | return NO; 93 | } 94 | 95 | // Format launch agent label suitable for XML and a filename 96 | NSMutableString *labelSuffix = [[absTargetPath mutableCopy] autorelease]; 97 | if ([labelSuffix hasPrefix:@"/"]) 98 | [labelSuffix deleteCharactersInRange:NSMakeRange(0,1)]; 99 | [labelSuffix replaceOccurrencesOfString:@"/" withString:@"." options:NSLiteralSearch range:NSMakeRange(0, [labelSuffix length])]; 100 | [labelSuffix replaceOccurrencesOfString:@"\"" withString:@"-" options:NSLiteralSearch range:NSMakeRange(0, [labelSuffix length])]; 101 | [labelSuffix replaceOccurrencesOfString:@"'" withString:@"-" options:NSLiteralSearch range:NSMakeRange(0, [labelSuffix length])]; 102 | NSString *label = [LAUNCH_AGENT_LABEL_PREFIX stringByAppendingString:labelSuffix]; 103 | 104 | // Get absolute path to self (the setWeblocThumb executable) 105 | NSString *pathToExec = getExecutablePath(); 106 | 107 | // Generate LaunchAgent property list XML 108 | NSString *plistXML = [NSString stringWithFormat:launchAgentXMLFormat, 109 | encodeForXML(label), 110 | encodeForXML(pathToExec), 111 | encodeForXML(absTargetPath), 112 | encodeForXML(absTargetPath)]; 113 | 114 | // Determine target path for saving the .plist file into 115 | NSString *savePath = [USER_LAUNCH_AGENTS_PATH 116 | stringByAppendingPathComponent:[label stringByAppendingString:@".plist"]]; 117 | 118 | // Write property list XML into file 119 | if ([[NSFileManager defaultManager] fileExistsAtPath:savePath]) 120 | { 121 | PrintfErr(@"File already exists:\n %@\n", savePath); 122 | PrintfErr(@"If you want to replace the existing LaunchAgent, unload\n" 123 | @"it with launchctl and then remove the file.\n"); 124 | return NO; 125 | } 126 | NSError *writeErr = nil; 127 | if (![plistXML writeToFile:savePath atomically:NO encoding:NSUTF8StringEncoding error:&writeErr]) 128 | { 129 | PrintfErr(@"Cannot write file:\n %@\n", savePath); 130 | PrintfErr(@"Reason: %@", [writeErr description]); 131 | return NO; 132 | } 133 | 134 | Printf(@"The launch agent has been saved to:\n %@\n", savePath); 135 | 136 | // Load the launch agent via launchctl 137 | NSTask *launchctlLoadTask = [NSTask 138 | launchedTaskWithLaunchPath:LAUNCHCTL_PATH 139 | arguments:[NSArray arrayWithObjects:@"load", savePath, nil] 140 | ]; 141 | [launchctlLoadTask waitUntilExit]; 142 | if (0 < [launchctlLoadTask terminationStatus]) 143 | { 144 | PrintfErr(@"Error running 'launchctl load': exit status %i\n", [launchctlLoadTask terminationStatus]); 145 | PrintfErr(@"You will have to load the launch agent manually using launchctl.\n"); 146 | return NO; 147 | } 148 | 149 | Printf(@"The launch agent has been successfully loaded.\n"); 150 | 151 | return YES; 152 | } 153 | 154 | 155 | #define FM [NSFileManager defaultManager] 156 | 157 | void iterateOurLaunchAgents(void(^workerBlock)(NSDictionary *agentPlist)) 158 | { 159 | NSError *dirEnumErr = nil; 160 | NSArray *launchAgentDirContents = [FM contentsOfDirectoryAtPath:USER_LAUNCH_AGENTS_PATH error:&dirEnumErr]; 161 | if (dirEnumErr != nil) 162 | { 163 | PrintfErr(@"Error reading user launch agent directory contents:\n %@\n", USER_LAUNCH_AGENTS_PATH); 164 | return; 165 | } 166 | 167 | for (NSString *filename in launchAgentDirContents) 168 | { 169 | NSString *path = [USER_LAUNCH_AGENTS_PATH stringByAppendingPathComponent:filename]; 170 | // Ignore folders: 171 | BOOL isDir; 172 | if (![FM fileExistsAtPath:path isDirectory:&isDir] || isDir) 173 | continue; 174 | // Read plist into dictionary: 175 | NSDictionary *agentDict = [NSDictionary dictionaryWithContentsOfFile:path]; 176 | if (agentDict == nil) 177 | continue; 178 | // Ignore if it does not seem to be executing this program: 179 | NSArray *programArgs = [agentDict objectForKey:@"ProgramArguments"]; 180 | if (programArgs == nil || programArgs.count == 0) 181 | continue; 182 | if (![[programArgs objectAtIndex:0] hasSuffix:@"setWeblocThumb"]) 183 | continue; 184 | // At this point we know this agent runs this program: 185 | workerBlock(agentDict); 186 | } 187 | } 188 | 189 | void printLaunchAgentWatchPaths() 190 | { 191 | iterateOurLaunchAgents(^(NSDictionary *agentPlist) 192 | { 193 | NSArray *watchPaths = [agentPlist objectForKey:@"WatchPaths"]; 194 | if (watchPaths == nil || watchPaths.count == 0) 195 | return; 196 | for (NSString *watchPath in watchPaths) 197 | { 198 | Printf(@"%@\n", watchPath); 199 | } 200 | }); 201 | } 202 | 203 | 204 | -------------------------------------------------------------------------------- /HGCLIUtils.m: -------------------------------------------------------------------------------- 1 | // CLI app utils 2 | // 3 | // http://hasseg.org/ 4 | // 5 | 6 | /* 7 | The MIT License 8 | 9 | Copyright (c) 2010 Ali Rantakari 10 | 11 | Permission is hereby granted, free of charge, to any person obtaining a copy 12 | of this software and associated documentation files (the "Software"), to deal 13 | in the Software without restriction, including without limitation the rights 14 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 15 | copies of the Software, and to permit persons to whom the Software is 16 | furnished to do so, subject to the following conditions: 17 | 18 | The above copyright notice and this permission notice shall be included in 19 | all copies or substantial portions of the Software. 20 | 21 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 22 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 23 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 24 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 25 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 26 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 27 | THE SOFTWARE. 28 | */ 29 | 30 | #import "HGCLIUtils.h" 31 | 32 | 33 | // the string encoding to use for output 34 | NSStringEncoding outputStrEncoding = NSUTF8StringEncoding; // default 35 | 36 | 37 | 38 | 39 | // helper methods for printing to stdout and stderr 40 | // from: http://www.sveinbjorn.org/objectivec_stdout 41 | // (modified to use non-deprecated version of writeToFile:... 42 | // and allow for using the "string format" syntax) 43 | 44 | void Print(NSString *aStr) 45 | { 46 | if (aStr == nil) 47 | return; 48 | [aStr writeToFile:@"/dev/stdout" atomically:NO encoding:outputStrEncoding error:NULL]; 49 | } 50 | 51 | void Printf(NSString *aStr, ...) 52 | { 53 | va_list argList; 54 | va_start(argList, aStr); 55 | NSString *str = [ 56 | [[NSString alloc] 57 | initWithFormat:aStr 58 | locale:[[NSUserDefaults standardUserDefaults] dictionaryRepresentation] 59 | arguments:argList 60 | ] autorelease 61 | ]; 62 | va_end(argList); 63 | 64 | [str writeToFile:@"/dev/stdout" atomically:NO encoding:outputStrEncoding error:NULL]; 65 | } 66 | 67 | void PrintfErr(NSString *aStr, ...) 68 | { 69 | va_list argList; 70 | va_start(argList, aStr); 71 | NSString *str = [ 72 | [[NSString alloc] 73 | initWithFormat:aStr 74 | locale:[[NSUserDefaults standardUserDefaults] dictionaryRepresentation] 75 | arguments:argList 76 | ] autorelease 77 | ]; 78 | va_end(argList); 79 | 80 | [str writeToFile:@"/dev/stderr" atomically:NO encoding:outputStrEncoding error:NULL]; 81 | } 82 | 83 | 84 | // returns YES if success, NO if failure 85 | BOOL moveFileToTrash(NSString *filePath) 86 | { 87 | if (filePath == nil) 88 | return NO; 89 | 90 | NSString *fileDir = [filePath stringByDeletingLastPathComponent]; 91 | NSString *fileName = [filePath lastPathComponent]; 92 | 93 | return [[NSWorkspace sharedWorkspace] 94 | performFileOperation:NSWorkspaceRecycleOperation 95 | source:fileDir 96 | destination:@"" 97 | files:[NSArray arrayWithObject:fileName] 98 | tag:nil 99 | ]; 100 | } 101 | 102 | 103 | 104 | 105 | // convenience function: concatenates strings (yes, I hate the 106 | // verbosity of -stringByAppendingString:.) 107 | // NOTE: MUST SEND nil AS THE LAST ARGUMENT 108 | NSString *strConcat(NSString *firstStr, ...) 109 | { 110 | if (!firstStr) 111 | return nil; 112 | 113 | va_list argList; 114 | NSMutableString *retVal = [firstStr mutableCopy]; 115 | NSString *str; 116 | va_start(argList, firstStr); 117 | while((str = va_arg(argList, NSString*))) 118 | [retVal appendString:str]; 119 | va_end(argList); 120 | return retVal; 121 | } 122 | 123 | NSString *escapeDoubleQuotes(NSString *str) 124 | { 125 | return [str stringByReplacingOccurrencesOfString:@"\"" withString:@"\\\""]; 126 | } 127 | 128 | 129 | 130 | // replaces all occurrences of searchStr in str with replaceStr 131 | void replaceInMutableAttrStr(NSMutableAttributedString *str, NSString *searchStr, NSAttributedString *replaceStr) 132 | { 133 | if (str == nil || searchStr == nil || replaceStr == nil) 134 | return; 135 | 136 | NSUInteger replaceStrLength = [replaceStr length]; 137 | NSString *strRegularString = [str string]; 138 | NSRange searchRange = NSMakeRange(0, [strRegularString length]); 139 | NSRange foundRange; 140 | do 141 | { 142 | foundRange = [strRegularString rangeOfString:searchStr options:NSLiteralSearch range:searchRange]; 143 | if (foundRange.location != NSNotFound) 144 | { 145 | [str replaceCharactersInRange:foundRange withAttributedString:replaceStr]; 146 | 147 | strRegularString = [str string]; 148 | searchRange.location = foundRange.location + replaceStrLength; 149 | searchRange.length = [strRegularString length] - searchRange.location; 150 | } 151 | } 152 | while (foundRange.location != NSNotFound); 153 | } 154 | 155 | 156 | #define UNICHAR_NEWLINE 10 157 | #define UNICHAR_TAB 9 158 | #define UNICHAR_SPACE 32 159 | #define TAB_STOP_LENGTH 4 160 | 161 | void wordWrapMutableAttrStr(NSMutableAttributedString *mutableAttrStr, NSUInteger width) 162 | { 163 | // replace tabs with spaces to avoid problems with different programs 164 | // (that would display our output) using different tab stop lengths: 165 | replaceInMutableAttrStr(mutableAttrStr, @"\t", ATTR_STR(WHITESPACE(TAB_STOP_LENGTH))); 166 | 167 | NSString *str = [[mutableAttrStr string] copy]; 168 | 169 | NSAttributedString *newlineAttrStr = ATTR_STR(@"\n"); 170 | 171 | // characters we'll consider as indentation: 172 | NSCharacterSet *indentChars = [NSCharacterSet characterSetWithCharactersInString:@" •"]; 173 | 174 | // find all input string indices where we want to 175 | // wrap the line 176 | NSUInteger strLength = [str length]; 177 | NSUInteger strIndex = 0; 178 | NSUInteger currentLineLength = 0; 179 | NSUInteger lastWhitespaceIndex = 0; 180 | unichar currentUnichar = 0; 181 | BOOL lastCharWasWhitespace = NO; 182 | BOOL lastCharWasIndentation = NO; 183 | NSUInteger numAddedChars = 0; 184 | NSUInteger currentLineIndentAmount = 0; 185 | while(strIndex < strLength) 186 | { 187 | if (width <= currentLineLength) 188 | { 189 | // insert newline at the wrap index, eating one whitespace 190 | // *if* we're wrapping at a whitespace (i.e. don't eat characters 191 | // if we've been forced to wrap in the middle of a word) 192 | 193 | NSUInteger indexToWrapAt = ((0 < lastWhitespaceIndex) ? lastWhitespaceIndex : strIndex) + numAddedChars; 194 | NSUInteger lengthToReplace = ((lastWhitespaceIndex != 0)?1:0); 195 | NSRange replaceRange = NSMakeRange(indexToWrapAt, lengthToReplace); 196 | 197 | NSAttributedString *replaceStr = nil; 198 | if (currentLineIndentAmount == 0) 199 | replaceStr = newlineAttrStr; 200 | else 201 | replaceStr = ATTR_STR(strConcat(@"\n", WHITESPACE(currentLineIndentAmount), nil)); 202 | 203 | [mutableAttrStr 204 | replaceCharactersInRange:replaceRange 205 | withAttributedString:replaceStr 206 | ]; 207 | 208 | numAddedChars += [replaceStr length] - lengthToReplace; 209 | 210 | lastWhitespaceIndex = 0; 211 | currentLineLength = (strIndex-(indexToWrapAt-numAddedChars)); 212 | } 213 | else 214 | { 215 | currentUnichar = [str characterAtIndex:strIndex]; 216 | 217 | if ((lastCharWasIndentation || currentLineLength == 0) && 218 | [indentChars characterIsMember:currentUnichar] 219 | ) 220 | { 221 | lastCharWasIndentation = YES; 222 | currentLineIndentAmount++; 223 | } 224 | else 225 | lastCharWasIndentation = NO; 226 | 227 | if (currentUnichar == UNICHAR_NEWLINE) 228 | { 229 | lastWhitespaceIndex = 0; 230 | currentLineLength = 0; 231 | currentLineIndentAmount = 0; 232 | } 233 | // we want to wrap at the beginning of the last 234 | // whitespace run of the current line, excluding the 235 | // beginning of the line (doesn't make sense to wrap 236 | // there): 237 | else if (!lastCharWasWhitespace && 238 | 0 < currentLineLength && 239 | currentUnichar == UNICHAR_SPACE 240 | ) 241 | { 242 | lastWhitespaceIndex = strIndex; 243 | currentLineLength++; 244 | } 245 | else 246 | { 247 | currentLineLength++; 248 | } 249 | 250 | lastCharWasWhitespace = (currentUnichar == UNICHAR_SPACE); 251 | } 252 | 253 | strIndex++; 254 | } 255 | 256 | [str release]; 257 | } 258 | 259 | 260 | 261 | 262 | -------------------------------------------------------------------------------- /deployment-files/LICENSE-2.0.txt: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /ANSIEscapeHelper.h: -------------------------------------------------------------------------------- 1 | // 2 | // ANSIEscapeHelper.h 3 | // AnsiColorsTest 4 | // 5 | // Created by Ali Rantakari on 18.3.09. 6 | // 7 | // Version 0.9.4 8 | // 9 | /* 10 | The MIT License 11 | 12 | Copyright (c) 2008-2009 Ali Rantakari 13 | 14 | Permission is hereby granted, free of charge, to any person obtaining a copy 15 | of this software and associated documentation files (the "Software"), to deal 16 | in the Software without restriction, including without limitation the rights 17 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 18 | copies of the Software, and to permit persons to whom the Software is 19 | furnished to do so, subject to the following conditions: 20 | 21 | The above copyright notice and this permission notice shall be included in 22 | all copies or substantial portions of the Software. 23 | 24 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 25 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 26 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 27 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 28 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 29 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 30 | THE SOFTWARE. 31 | */ 32 | 33 | #import 34 | 35 | 36 | // the CSI (Control Sequence Initiator) -- i.e. "escape sequence prefix". 37 | // (add your own CSI:Miami joke here) 38 | #define kANSIEscapeCSI @"\033[" 39 | 40 | // the end byte of an SGR (Select Graphic Rendition) 41 | // ANSI Escape Sequence 42 | #define kANSIEscapeSGREnd @"m" 43 | 44 | 45 | // color definition helper macros 46 | #define kBrightColorBrightness 1.0 47 | #define kBrightColorSaturation 0.4 48 | #define kBrightColorAlpha 1.0 49 | #define kBrightColorWithHue(h) [NSColor colorWithCalibratedHue:(h) saturation:kBrightColorSaturation brightness:kBrightColorBrightness alpha:kBrightColorAlpha] 50 | 51 | // default colors 52 | #define kDefaultANSIColorFgBlack [NSColor blackColor] 53 | #define kDefaultANSIColorFgRed [NSColor redColor] 54 | #define kDefaultANSIColorFgGreen [NSColor greenColor] 55 | #define kDefaultANSIColorFgYellow [NSColor yellowColor] 56 | #define kDefaultANSIColorFgBlue [NSColor blueColor] 57 | #define kDefaultANSIColorFgMagenta [NSColor magentaColor] 58 | #define kDefaultANSIColorFgCyan [NSColor cyanColor] 59 | #define kDefaultANSIColorFgWhite [NSColor whiteColor] 60 | 61 | #define kDefaultANSIColorFgBrightBlack [NSColor colorWithCalibratedWhite:0.337 alpha:1.0] 62 | #define kDefaultANSIColorFgBrightRed kBrightColorWithHue(1.0) 63 | #define kDefaultANSIColorFgBrightGreen kBrightColorWithHue(1.0/3.0) 64 | #define kDefaultANSIColorFgBrightYellow kBrightColorWithHue(1.0/6.0) 65 | #define kDefaultANSIColorFgBrightBlue kBrightColorWithHue(2.0/3.0) 66 | #define kDefaultANSIColorFgBrightMagenta kBrightColorWithHue(5.0/6.0) 67 | #define kDefaultANSIColorFgBrightCyan kBrightColorWithHue(0.5) 68 | #define kDefaultANSIColorFgBrightWhite [NSColor whiteColor] 69 | 70 | #define kDefaultANSIColorBgBlack [NSColor blackColor] 71 | #define kDefaultANSIColorBgRed [NSColor redColor] 72 | #define kDefaultANSIColorBgGreen [NSColor greenColor] 73 | #define kDefaultANSIColorBgYellow [NSColor yellowColor] 74 | #define kDefaultANSIColorBgBlue [NSColor blueColor] 75 | #define kDefaultANSIColorBgMagenta [NSColor magentaColor] 76 | #define kDefaultANSIColorBgCyan [NSColor cyanColor] 77 | #define kDefaultANSIColorBgWhite [NSColor whiteColor] 78 | 79 | #define kDefaultANSIColorBgBrightBlack kDefaultANSIColorFgBrightBlack 80 | #define kDefaultANSIColorBgBrightRed kDefaultANSIColorFgBrightRed 81 | #define kDefaultANSIColorBgBrightGreen kDefaultANSIColorFgBrightGreen 82 | #define kDefaultANSIColorBgBrightYellow kDefaultANSIColorFgBrightYellow 83 | #define kDefaultANSIColorBgBrightBlue kDefaultANSIColorFgBrightBlue 84 | #define kDefaultANSIColorBgBrightMagenta kDefaultANSIColorFgBrightMagenta 85 | #define kDefaultANSIColorBgBrightCyan kDefaultANSIColorFgBrightCyan 86 | #define kDefaultANSIColorBgBrightWhite kDefaultANSIColorFgBrightWhite 87 | 88 | // dictionary keys for the SGR code dictionaries that the array 89 | // escapeCodesForString:cleanString: returns contains 90 | #define kCodeDictKey_code @"code" 91 | #define kCodeDictKey_location @"location" 92 | 93 | // dictionary keys for the string formatting attribute 94 | // dictionaries that the array attributesForString:cleanString: 95 | // returns contains 96 | #define kAttrDictKey_range @"range" 97 | #define kAttrDictKey_attrName @"attributeName" 98 | #define kAttrDictKey_attrValue @"attributeValue" 99 | 100 | // minimum weight for an NSFont for it to be considered bold 101 | #define kBoldFontMinWeight 9 102 | 103 | 104 | 105 | 106 | /*! 107 | @enum sgrCode 108 | 109 | @abstract SGR (Select Graphic Rendition) ANSI control codes. 110 | */ 111 | enum sgrCode 112 | { 113 | SGRCodeNoneOrInvalid = -1, 114 | 115 | SGRCodeAllReset = 0, 116 | 117 | SGRCodeIntensityBold = 1, 118 | SGRCodeIntensityFaint = 2, 119 | SGRCodeIntensityNormal = 22, 120 | 121 | SGRCodeItalicOn = 3, 122 | 123 | SGRCodeUnderlineSingle = 4, 124 | SGRCodeUnderlineDouble = 21, 125 | SGRCodeUnderlineNone = 24, 126 | 127 | SGRCodeFgBlack = 30, 128 | SGRCodeFgRed = 31, 129 | SGRCodeFgGreen = 32, 130 | SGRCodeFgYellow = 33, 131 | SGRCodeFgBlue = 34, 132 | SGRCodeFgMagenta = 35, 133 | SGRCodeFgCyan = 36, 134 | SGRCodeFgWhite = 37, 135 | SGRCodeFgReset = 39, 136 | 137 | SGRCodeBgBlack = 40, 138 | SGRCodeBgRed = 41, 139 | SGRCodeBgGreen = 42, 140 | SGRCodeBgYellow = 43, 141 | SGRCodeBgBlue = 44, 142 | SGRCodeBgMagenta = 45, 143 | SGRCodeBgCyan = 46, 144 | SGRCodeBgWhite = 47, 145 | SGRCodeBgReset = 49, 146 | 147 | SGRCodeFgBrightBlack = 90, 148 | SGRCodeFgBrightRed = 91, 149 | SGRCodeFgBrightGreen = 92, 150 | SGRCodeFgBrightYellow = 93, 151 | SGRCodeFgBrightBlue = 94, 152 | SGRCodeFgBrightMagenta = 95, 153 | SGRCodeFgBrightCyan = 96, 154 | SGRCodeFgBrightWhite = 97, 155 | 156 | SGRCodeBgBrightBlack = 100, 157 | SGRCodeBgBrightRed = 101, 158 | SGRCodeBgBrightGreen = 102, 159 | SGRCodeBgBrightYellow = 103, 160 | SGRCodeBgBrightBlue = 104, 161 | SGRCodeBgBrightMagenta = 105, 162 | SGRCodeBgBrightCyan = 106, 163 | SGRCodeBgBrightWhite = 107 164 | }; 165 | 166 | 167 | 168 | 169 | 170 | 171 | /*! 172 | @class ANSIEscapeHelper 173 | 174 | @abstract Contains helper methods for dealing with strings 175 | that contain ANSI escape sequences for formatting (colors, 176 | underlining, bold etc.) 177 | */ 178 | @interface ANSIEscapeHelper : NSObject 179 | { 180 | NSFont *font; 181 | NSMutableDictionary *ansiColors; 182 | NSColor *defaultStringColor; 183 | } 184 | 185 | /*! 186 | @property defaultStringColor 187 | 188 | @abstract The default color used when creating an attributed string (default is black). 189 | */ 190 | @property(copy) NSColor *defaultStringColor; 191 | 192 | 193 | /*! 194 | @property font 195 | 196 | @abstract The font to use when creating string formatting attribute values. 197 | */ 198 | @property(copy) NSFont *font; 199 | 200 | /*! 201 | @property ansiColors 202 | 203 | @abstract The colors to use for displaying ANSI colors. 204 | 205 | @discussion Keys in this dictionary should be NSNumber objects containing SGR code 206 | values from the sgrCode enum. The corresponding values for these keys 207 | should be NSColor objects. If this property is nil or if it doesn't 208 | contain a key for a specific SGR code, the default color will be used 209 | instead. 210 | */ 211 | @property(retain) NSMutableDictionary *ansiColors; 212 | 213 | 214 | /*! 215 | @method attributedStringWithANSIEscapedString: 216 | 217 | @abstract Returns an attributed string that corresponds both in contents 218 | and formatting to a given string that contains ANSI escape 219 | sequences. 220 | 221 | @param aString A String containing ANSI escape sequences 222 | 223 | @result An attributed string that mimics as closely as possible 224 | the formatting of the given ANSI-escaped string. 225 | */ 226 | - (NSAttributedString*) attributedStringWithANSIEscapedString:(NSString*)aString; 227 | 228 | 229 | /*! 230 | @method ansiEscapedStringWithAttributedString: 231 | 232 | @abstract Returns a string containing ANSI escape sequences that corresponds 233 | both in contents and formatting to a given attributed string. 234 | 235 | @param aAttributedString An attributed string 236 | 237 | @result A string that mimics as closely as possible 238 | the formatting of the given attributed string with 239 | ANSI escape sequences. 240 | */ 241 | - (NSString*) ansiEscapedStringWithAttributedString:(NSAttributedString*)aAttributedString; 242 | 243 | 244 | /*! 245 | @method escapeCodesForString:cleanString: 246 | 247 | @abstract Returns an array of SGR codes and their locations from a 248 | string containing ANSI escape sequences as well as a "clean" 249 | version of the string (i.e. one without the ANSI escape 250 | sequences.) 251 | 252 | @param aString A String containing ANSI escape sequences 253 | @param aCleanString Upon return, contains a "clean" version of aString (i.e. aString 254 | without the ANSI escape sequences) 255 | 256 | @result An array of NSDictionary objects, each of which has 257 | an NSNumber value for the key "code" (specifying an SGR code) and 258 | another NSNumber value for the key "location" (specifying the 259 | location of the code within aCleanString.) 260 | */ 261 | - (NSArray*) escapeCodesForString:(NSString*)aString cleanString:(NSString**)aCleanString; 262 | 263 | 264 | /*! 265 | @method ansiEscapedStringWithCodesAndLocations:cleanString: 266 | 267 | @abstract Returns a string containing ANSI escape codes for formatting based 268 | on a string and an array of SGR codes and their locations within 269 | the given string. 270 | 271 | @param aCodesArray An array of NSDictionary objects, each of which should have 272 | an NSNumber value for the key "code" (specifying an SGR 273 | code) and another NSNumber value for the key "location" 274 | (specifying the location of this SGR code in aCleanString.) 275 | @param aCleanString The string to which to insert the ANSI escape codes 276 | described in aCodesArray. 277 | 278 | @result A string containing ANSI escape sequences. 279 | */ 280 | - (NSString*) ansiEscapedStringWithCodesAndLocations:(NSArray*)aCodesArray cleanString:(NSString*)aCleanString; 281 | 282 | 283 | /*! 284 | @method attributesForString:cleanString: 285 | 286 | @abstract Convert ANSI escape sequences in a string to string formatting attributes. 287 | 288 | @discussion Given a string with some ANSI escape sequences in it, this method returns 289 | attributes for formatting the specified string according to those ANSI 290 | escape sequences as well as a "clean" (i.e. free of the escape sequences) 291 | version of this string. 292 | 293 | @param aString A String containing ANSI escape sequences 294 | @param aCleanString Upon return, contains a "clean" version of aString (i.e. aString 295 | without the ANSI escape sequences.) Pass in NULL if you're not 296 | interested in this. 297 | 298 | @result An array containing NSDictionary objects, each of which has keys "range" 299 | (an NSValue containing an NSRange, specifying the range for the 300 | attribute within the "clean" version of aString), "attributeName" (an 301 | NSString) and "attributeValue" (an NSObject). You may use these as 302 | arguments for NSMutableAttributedString's methods for setting the 303 | visual formatting. 304 | */ 305 | - (NSArray*) attributesForString:(NSString*)aString cleanString:(NSString**)aCleanString; 306 | 307 | 308 | /*! 309 | @method sgrCode:endsFormattingIntroducedByCode: 310 | 311 | @abstract Whether the occurrence of a given SGR code would end the formatting run 312 | introduced by another SGR code. 313 | 314 | @discussion For example, SGRCodeFgReset, SGRCodeAllReset or any SGR code 315 | specifying a foreground color would end the formatting run 316 | introduced by a foreground color -specifying SGR code. 317 | 318 | @param endCode The SGR code to test as a candidate for ending the formatting run 319 | introduced by startCode 320 | @param startCode The SGR code that has introduced a formatting run 321 | 322 | @result YES if the occurrence of endCode would end the formatting run 323 | introduced by startCode, NO otherwise. 324 | */ 325 | - (BOOL) sgrCode:(enum sgrCode)endCode endsFormattingIntroducedByCode:(enum sgrCode)startCode; 326 | 327 | 328 | /*! 329 | @method colorForSGRCode: 330 | 331 | @abstract Returns the color to use for displaying a specific ANSI color. 332 | 333 | @discussion This method first considers the values set in the ansiColors 334 | property and only then the standard basic colors (NSColor's 335 | redColor, blueColor etc.) 336 | 337 | @param code An SGR code that specifies an ANSI color. 338 | 339 | @result The color to use for displaying the ANSI color specified by code. 340 | */ 341 | - (NSColor*) colorForSGRCode:(enum sgrCode)code; 342 | 343 | 344 | /*! 345 | @method sgrCodeForColor:isForegroundColor: 346 | 347 | @abstract Returns a color SGR code that corresponds to a given color. 348 | 349 | @discussion This method matches colors to their equivalent SGR codes 350 | by going through the colors specified in the ansiColors 351 | dictionary, and if ansiColors is null or if a match is 352 | not found there, by comparing the given color to the 353 | standard basic colors (NSColor's redColor, blueColor 354 | etc.) The comparison is done simply by checking for 355 | equality. 356 | 357 | @param aColor The color to get a corresponding SGR code for 358 | @param aForeground Whether you want a foreground or background color code 359 | 360 | @result SGR code that corresponds with aColor. 361 | */ 362 | - (enum sgrCode) sgrCodeForColor:(NSColor*)aColor isForegroundColor:(BOOL)aForeground; 363 | 364 | 365 | /*! 366 | @method closestSGRCodeForColor:isForegroundColor: 367 | 368 | @abstract Returns a color SGR code that represents the closest ANSI 369 | color to a given color. 370 | 371 | @discussion This method attempts to find the closest ANSI color to 372 | aColor and return its SGR code. 373 | 374 | @param aColor The color to get a closest color SGR code match for 375 | @param aForeground Whether you want a foreground or background color code 376 | 377 | @result SGR code for the ANSI color that is closest to aColor. 378 | */ 379 | - (enum sgrCode) closestSGRCodeForColor:(NSColor *)color isForegroundColor:(BOOL)foreground; 380 | 381 | 382 | 383 | @end 384 | -------------------------------------------------------------------------------- /setWeblocThumb.m: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | setWeblocThumb 4 | -------------- 5 | Sets custom icons for .webloc files that display a thumbnail of the 6 | web page that the URL contained by the file points to. 7 | 8 | Copyright (c) 2009-2013 Ali Rantakari (http://hasseg.org) 9 | 10 | -------------- 11 | 12 | Licensed under the Apache License, Version 2.0 (the "License"); you may 13 | not use this file except in compliance with the License. You may obtain 14 | a copy of the License at 15 | 16 | http://www.apache.org/licenses/LICENSE-2.0 17 | 18 | Unless required by applicable law or agreed to in writing, software 19 | distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 20 | WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 21 | License for the specific language governing permissions and limitations 22 | under the License. 23 | 24 | */ 25 | 26 | #include 27 | #import 28 | #import 29 | #import "MBBase64.h" 30 | #import "imgBase64.m" 31 | #import "HGCLIUtils.h" 32 | #import "launchAgentGen.h" 33 | 34 | 35 | #define GETURL_AS_FORMAT_STR @"tell the application \"Finder\" to return location of (POSIX file \"%@\" as file)" 36 | #define WEBVIEW_FRAME_RECT NSMakeRect(0, 0, 700, 700) 37 | #define WEBVIEW_SCREENSHOT_SIZE NSMakeSize(1280, 1024) 38 | #define THUMB_DRAWING_RECT NSMakeRect(95, 160, 320, 320) 39 | #define FAVICON_DRAWING_RECT NSMakeRect(390, 412, 100, 100) 40 | 41 | 42 | const int VERSION_MAJOR = 1; 43 | const int VERSION_MINOR = 0; 44 | const int VERSION_BUILD = 0; 45 | 46 | 47 | NSImage *baseIconImage = nil; 48 | BOOL arg_verbose = NO; 49 | WebPreferences *webViewPrefs = nil; 50 | double screenshotDelaySec = 0.0; 51 | 52 | 53 | NSString* versionNumberStr() 54 | { 55 | return [NSString stringWithFormat:@"%d.%d.%d", VERSION_MAJOR, VERSION_MINOR, VERSION_BUILD]; 56 | } 57 | 58 | 59 | BOOL fileHasCustomIcon(NSString *filePath) 60 | { 61 | FSRef fsRef; 62 | if (FSPathMakeRef((const UInt8 *)[filePath fileSystemRepresentation], &fsRef, NULL) != noErr) 63 | return NO; 64 | 65 | FSCatalogInfo fsCatalogInfo; 66 | if (FSGetCatalogInfo(&fsRef, kFSCatInfoFinderInfo, &fsCatalogInfo, NULL, NULL, NULL) == noErr) 67 | { 68 | FileInfo *fileInfo = (FileInfo*)(&fsCatalogInfo.finderInfo); 69 | UInt16 infoFlags = fileInfo->finderFlags; 70 | return ((infoFlags & kHasCustomIcon) != 0); 71 | } 72 | 73 | return NO; 74 | } 75 | 76 | 77 | NSString * getURLOfWeblocFile(NSString *path) 78 | { 79 | // try reading .webloc as plist 80 | NSString *ret = nil; 81 | NSDictionary *weblocDict = [NSDictionary dictionaryWithContentsOfFile:path]; 82 | ret = [weblocDict objectForKey:@"URL"]; 83 | if (ret != nil) 84 | return ret; 85 | 86 | // if not a plist, try asking Finder (slower) 87 | NSDictionary *appleScriptError; 88 | NSString *escapedPath = [path stringByReplacingOccurrencesOfString:@"\"" withString:@"\\\""]; 89 | NSString *asSource = [NSString stringWithFormat:GETURL_AS_FORMAT_STR, escapedPath]; 90 | NSAppleScript *getURLAppleScript = [[NSAppleScript alloc] initWithSource:asSource]; 91 | NSAppleEventDescriptor *ed = [getURLAppleScript executeAndReturnError:&appleScriptError]; 92 | [getURLAppleScript release]; 93 | return [ed stringValue]; 94 | } 95 | 96 | 97 | 98 | void VerbosePrintf(NSString *aStr, ...) 99 | { 100 | if (!arg_verbose) 101 | return; 102 | va_list argList; 103 | va_start(argList, aStr); 104 | NSString *str = [ 105 | [[NSString alloc] 106 | initWithFormat:aStr 107 | locale:[[NSUserDefaults standardUserDefaults] dictionaryRepresentation] 108 | arguments:argList 109 | ] autorelease 110 | ]; 111 | va_end(argList); 112 | 113 | [str writeToFile:@"/dev/stdout" atomically:NO encoding:outputStrEncoding error:NULL]; 114 | } 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | // a WeblocIconifier is responsible for loading the web page 123 | // that a .webloc file's URL points to, creating an icon with 124 | // the thumbnail of that web page and assigning it to the 125 | // .webloc file 126 | @interface WeblocIconifier:NSObject 127 | { 128 | WebView *webView; 129 | NSString *weblocFilePath; 130 | NSString *weblocURL; 131 | NSImage *favicon; 132 | BOOL doneIconizing; 133 | BOOL doneLoadingPage; 134 | BOOL loadFavicon; 135 | AliasHandle fileAliasHandle; 136 | } 137 | 138 | @property(retain) WebView *webView; 139 | @property(copy) NSString *weblocFilePath; 140 | @property(copy) NSString *weblocURL; 141 | @property(copy) NSImage *favicon; 142 | 143 | - (BOOL) doneIconizing; 144 | - (void) startLoadingWithFavicon:(BOOL)aLoadFavicon; 145 | - (void) setSelfAsDone; 146 | - (void) doneLoading; 147 | - (void) drawAndSetIcon; 148 | - (BOOL) saveAliasOfPath:(NSString *)path; 149 | - (NSString *) pathFromSavedAlias; 150 | 151 | @end 152 | 153 | @implementation WeblocIconifier 154 | 155 | @synthesize webView; 156 | @synthesize weblocFilePath; 157 | @synthesize weblocURL; 158 | @synthesize favicon; 159 | 160 | - (id) init 161 | { 162 | if (!(self = [super init])) 163 | return nil; 164 | 165 | doneIconizing = NO; 166 | doneLoadingPage = NO; 167 | loadFavicon = NO; 168 | fileAliasHandle = NULL; 169 | 170 | return self; 171 | } 172 | 173 | - (void) dealloc 174 | { 175 | self.webView = nil; 176 | self.weblocFilePath = nil; 177 | self.weblocURL = nil; 178 | self.favicon = nil; 179 | fileAliasHandle = NULL; 180 | [super dealloc]; 181 | } 182 | 183 | 184 | - (void) startLoadingWithFavicon:(BOOL)aLoadFavicon 185 | { 186 | VerbosePrintf(@"start: %@\n", self.weblocFilePath); 187 | 188 | NSAssert((self.weblocFilePath != nil), @"self.weblocFilePath is nil"); 189 | 190 | [self saveAliasOfPath:self.weblocFilePath]; 191 | 192 | loadFavicon = aLoadFavicon; 193 | 194 | // create webView and start loading the page 195 | if (self.webView == nil) 196 | { 197 | self.webView = [[WebView alloc] init]; 198 | [self.webView setFrame:WEBVIEW_FRAME_RECT]; 199 | [[[self.webView mainFrame] frameView] setAllowsScrolling:NO]; 200 | [self.webView setDrawsBackground:YES]; 201 | [self.webView setFrameLoadDelegate:self]; 202 | [self.webView setResourceLoadDelegate:self]; 203 | [self.webView setPreferences:webViewPrefs]; 204 | } 205 | self.weblocURL = getURLOfWeblocFile(weblocFilePath); 206 | VerbosePrintf(@" url: %@\n", self.weblocURL); 207 | if (self.weblocURL == nil) 208 | { 209 | PrintfErr(@" -> cannot get URL for: %@\n", self.weblocFilePath); 210 | doneIconizing = YES; 211 | } 212 | [self.webView setMainFrameURL:self.weblocURL]; 213 | } 214 | 215 | - (BOOL) doneIconizing 216 | { 217 | return doneIconizing; 218 | } 219 | 220 | - (void) setSelfAsDone 221 | { 222 | doneIconizing = YES; 223 | VerbosePrintf(@" -> done: %@\n", self.weblocFilePath); 224 | } 225 | 226 | 227 | 228 | - (void) doneLoading 229 | { 230 | if (!doneLoadingPage) 231 | return; 232 | 233 | [self drawAndSetIcon]; 234 | } 235 | 236 | 237 | - (NSImage *) generatedIconImage 238 | { 239 | // get screenshot from webView 240 | NSBitmapImageRep *webViewImageRep = [webView bitmapImageRepForCachingDisplayInRect:[webView frame]]; 241 | [webView cacheDisplayInRect:[webView frame] toBitmapImageRep:webViewImageRep]; 242 | NSImage *webViewImage = [[NSImage alloc] initWithSize:WEBVIEW_SCREENSHOT_SIZE]; 243 | [webViewImage addRepresentation:webViewImageRep]; 244 | 245 | // draw screenshot on top of base image 246 | NSImage *newIconImage = [[baseIconImage copy] autorelease]; 247 | [newIconImage lockFocus]; 248 | [webViewImage 249 | drawInRect:THUMB_DRAWING_RECT 250 | fromRect:NSZeroRect 251 | operation:NSCompositeCopy 252 | fraction:1.0 253 | ]; 254 | [newIconImage unlockFocus]; 255 | [webViewImage release]; 256 | 257 | // draw favicon on top of new icon 258 | if (self.favicon != nil) 259 | { 260 | [newIconImage lockFocus]; 261 | [favicon 262 | drawInRect:FAVICON_DRAWING_RECT 263 | fromRect:NSZeroRect 264 | operation:NSCompositeSourceOver 265 | fraction:1.0 266 | ]; 267 | [newIconImage unlockFocus]; 268 | } 269 | 270 | return newIconImage; 271 | } 272 | 273 | 274 | - (NSString *) currentWeblocFilePath 275 | { 276 | // resolve the file's alias handle (in case the 277 | // file has been moved within the same filesystem) 278 | NSString *resolvedWeblocFilePath = [self pathFromSavedAlias]; 279 | if (resolvedWeblocFilePath == nil) 280 | resolvedWeblocFilePath = self.weblocFilePath; 281 | 282 | if (![[NSFileManager defaultManager] fileExistsAtPath:resolvedWeblocFilePath]) 283 | { 284 | VerbosePrintf(@" -> can't find file even through its alias handle (moved to another volume?).\n"); 285 | VerbosePrintf(@" searching in containing folder for .weblocs with the same URL.\n"); 286 | 287 | // in desperation, go through containing folder 288 | // and try to find .webloc files that point to the same 289 | // URL we have and that don't have icons 290 | // 291 | NSString *parentDirPath = [resolvedWeblocFilePath stringByDeletingLastPathComponent]; 292 | NSArray *parentDirContents = [[NSFileManager defaultManager] 293 | contentsOfDirectoryAtPath:parentDirPath 294 | error:NULL 295 | ]; 296 | 297 | resolvedWeblocFilePath = nil; 298 | 299 | if (parentDirContents != nil) 300 | { 301 | for (NSString *aFileName in parentDirContents) 302 | { 303 | NSString *aFilePath = [parentDirPath stringByAppendingPathComponent:aFileName]; 304 | 305 | if ([[aFilePath pathExtension] isEqualToString:@"webloc"] 306 | && [self.weblocURL isEqualToString:getURLOfWeblocFile(aFilePath)] 307 | && !fileHasCustomIcon(aFilePath) 308 | ) 309 | { 310 | VerbosePrintf(@" found file with matching URL:\n %@\n", aFilePath); 311 | resolvedWeblocFilePath = aFilePath; 312 | break; 313 | } 314 | } 315 | } 316 | } 317 | 318 | return resolvedWeblocFilePath; 319 | } 320 | 321 | 322 | - (void) drawAndSetIcon 323 | { 324 | NSImage *newIconImage = [self generatedIconImage]; 325 | NSString *resolvedWeblocFilePath = [self currentWeblocFilePath]; 326 | 327 | if (resolvedWeblocFilePath == nil) 328 | { 329 | PrintfErr(@" -> FAIL: Cannot find file. Must have been moved to another volume or deleted.\n"); 330 | doneIconizing = YES; 331 | return; 332 | } 333 | 334 | // set icon to file 335 | [[NSWorkspace sharedWorkspace] 336 | setIcon:newIconImage 337 | forFile:resolvedWeblocFilePath 338 | options:0 339 | ]; 340 | 341 | [self setSelfAsDone]; 342 | } 343 | 344 | - (void) webView:(WebView *)sender didFinishLoadForFrame:(WebFrame *)frame 345 | { 346 | if ([self.webView isLoading] || doneIconizing || doneLoadingPage) 347 | return; 348 | 349 | doneLoadingPage = YES; 350 | 351 | if (screenshotDelaySec > 0) 352 | { 353 | NSInvocation *invocation = [NSInvocation 354 | invocationWithMethodSignature:[self methodSignatureForSelector:@selector(doneLoading)] 355 | ]; 356 | [invocation setTarget:self]; 357 | [invocation setSelector:@selector(doneLoading)]; 358 | [NSTimer 359 | scheduledTimerWithTimeInterval:screenshotDelaySec 360 | invocation:invocation 361 | repeats:NO 362 | ]; 363 | return; 364 | } 365 | 366 | [self doneLoading]; 367 | } 368 | 369 | - (void) webView:(WebView *)sender didFailProvisionalLoadWithError:(NSError *)error forFrame:(WebFrame *)frame 370 | { 371 | if ([error code] == NSURLErrorCancelled) 372 | return; 373 | PrintfErr(@" -> FAIL: %@\n %@\n %@\n %@\n", self.weblocFilePath, self.weblocURL, error, error.userInfo); 374 | doneIconizing = YES; 375 | } 376 | 377 | - (void) webView:(WebView *)sender didFailLoadWithError:(NSError *)error forFrame:(WebFrame *)frame 378 | { 379 | if ([error code] == NSURLErrorCancelled) 380 | return; 381 | PrintfErr(@" -> FAIL: %@\n %@\n %@\n %@\n", self.weblocFilePath, self.weblocURL, error, error.userInfo); 382 | doneIconizing = YES; 383 | } 384 | 385 | - (void) webView:(WebView *)sender didReceiveIcon:(NSImage *)image forFrame:(WebFrame *)frame 386 | { 387 | if (!loadFavicon) 388 | return; 389 | VerbosePrintf(@" -> got a favicon.\n"); 390 | self.favicon = image; 391 | [self doneLoading]; 392 | } 393 | 394 | - (void) webView:(WebView *)sender 395 | resource:(id)identifier 396 | didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge 397 | fromDataSource:(WebDataSource *)dataSource 398 | { 399 | [[challenge sender] cancelAuthenticationChallenge:challenge]; 400 | VerbosePrintf(@" -> FAIL: Server requests authentication.\n"); 401 | doneIconizing = YES; 402 | [self doneLoading]; 403 | } 404 | 405 | - (BOOL) saveAliasOfPath:(NSString *)path 406 | { 407 | OSErr r = FSNewAliasFromPath( 408 | NULL, // relative search root 409 | [path fileSystemRepresentation], // input path 410 | 0, // options 411 | &fileAliasHandle, // output AliasHandle 412 | false // is a dir? 413 | ); 414 | if (r != noErr) 415 | { 416 | fileAliasHandle = NULL; 417 | return NO; 418 | } 419 | return YES; 420 | } 421 | 422 | - (NSString *) pathFromSavedAlias 423 | { 424 | if (fileAliasHandle == NULL) 425 | return nil; 426 | 427 | // resolve alias 428 | Boolean wasChanged = false; 429 | FSRef fsRef; 430 | OSErr r = FSResolveAlias( 431 | NULL, // relative search root 432 | fileAliasHandle,// input AliasHandle 433 | &fsRef, // output FSRef 434 | &wasChanged // has the file moved? 435 | ); 436 | if (r != noErr) // probably fnfErr 437 | return nil; 438 | 439 | // FSRef -> NSString 440 | CFURLRef url = CFURLCreateFromFSRef(kCFAllocatorDefault, &fsRef); 441 | NSString* path = [(NSURL *)url path]; 442 | CFRelease(url); 443 | 444 | if (wasChanged) 445 | VerbosePrintf(@" -> file had moved: %@\n", path); 446 | 447 | return path; 448 | } 449 | 450 | @end 451 | 452 | 453 | 454 | int main(int argc, char *argv[]) 455 | { 456 | NSAutoreleasePool *autoReleasePool = [[NSAutoreleasePool alloc] init]; 457 | 458 | NSApplicationLoad(); // initialize some Cocoa stuff needed by the WebView 459 | 460 | char *myBasename = basename(argv[0]); 461 | if (argc == 1) 462 | { 463 | Printf( 464 | @"%s [options] \n" 465 | @"\n" 466 | @" Sets custom icons for .webloc files that display\n" 467 | @" a thumbnail of the web page that they point to.\n" 468 | @" may point to a .webloc file or a directory\n" 469 | @" that contains .webloc files.\n" 470 | @"\n" 471 | @" Options:\n" 472 | @"\n" 473 | @" -f Set icons also for files that already have a\n" 474 | @" custom icon (they are ignored by default)\n" 475 | @" -ni Do not load the site's favicon image and add it to\n" 476 | @" the generated icon\n" 477 | @" +j Set Java on when taking screenshots\n" 478 | @" -j Set Java off when taking screenshots (default)\n" 479 | @" +js Set JavaScript on when taking screenshots (default)\n" 480 | @" -js Set JavaScript off when taking screenshots\n" 481 | @" +p Set browser plugins on when taking screenshots\n" 482 | @" -p Set browser plugins off when taking screenshots (default)\n" 483 | @" -d Wait for seconds before taking the\n" 484 | @" screenshots\n" 485 | @" -v Make the output verbose.\n" 486 | @"\n" 487 | @"%s -a \n" 488 | @"\n" 489 | @" Create a user-specific launch agent for that\n" 490 | @" runs this program each time the contents of change\n" 491 | @"\n" 492 | @"%s -w\n" 493 | @"\n" 494 | @" List paths that are being watched by user-specific launch agents\n" 495 | @"\n" 496 | @"Version %@\n" 497 | @"Copyright (c) 2009-2013 Ali Rantakari\n" 498 | @"http://hasseg.org/setWeblocThumb\n" 499 | @"\n", 500 | myBasename, myBasename, myBasename, 501 | versionNumberStr() 502 | ); 503 | exit(0); 504 | } 505 | 506 | BOOL arg_forceRun = NO; 507 | BOOL arg_allowPlugins = NO; 508 | BOOL arg_allowJava = NO; 509 | BOOL arg_allowJavaScript = YES; 510 | BOOL arg_favicon = YES; 511 | BOOL arg_createLaunchAgent = NO; 512 | BOOL arg_printLaunchAgentWatchPaths = NO; 513 | NSMutableArray *weblocFilePaths = [NSMutableArray array]; 514 | 515 | NSString *providedPath = [[NSString stringWithUTF8String:argv[argc-1]] stringByStandardizingPath]; 516 | 517 | if (1 < argc) 518 | { 519 | int i; 520 | for (i = 1; i < argc; i++) 521 | { 522 | if (strcmp(argv[i], "-f") == 0) 523 | arg_forceRun = YES; 524 | else if (strcmp(argv[i], "-v") == 0) 525 | arg_verbose = YES; 526 | else if (strcmp(argv[i], "-w") == 0) 527 | arg_printLaunchAgentWatchPaths = YES; 528 | else if (strcmp(argv[i], "-a") == 0) 529 | arg_createLaunchAgent = YES; 530 | else if (strcmp(argv[i], "-ni") == 0) 531 | arg_favicon = NO; 532 | else if (strcmp(argv[i], "-js") == 0) 533 | arg_allowJavaScript = NO; 534 | else if (strcmp(argv[i], "+js") == 0) 535 | arg_allowJavaScript = YES; 536 | else if (strcmp(argv[i], "-j") == 0) 537 | arg_allowJava = NO; 538 | else if (strcmp(argv[i], "+j") == 0) 539 | arg_allowJava = YES; 540 | else if (strcmp(argv[i], "-p") == 0) 541 | arg_allowPlugins = NO; 542 | else if (strcmp(argv[i], "+p") == 0) 543 | arg_allowPlugins = YES; 544 | else if ((strcmp(argv[i], "-d") == 0) && (i+1 < argc)) 545 | screenshotDelaySec = abs([[NSString stringWithCString:argv[i+1] encoding:NSUTF8StringEncoding] doubleValue]); 546 | } 547 | } 548 | 549 | if (arg_printLaunchAgentWatchPaths) 550 | { 551 | printLaunchAgentWatchPaths(); 552 | return 0; 553 | } 554 | if (arg_createLaunchAgent) 555 | { 556 | BOOL success = generateLaunchAgent(providedPath); 557 | return success ? 0 : 1; 558 | } 559 | 560 | BOOL isDir = NO; 561 | if (![[NSFileManager defaultManager] fileExistsAtPath:providedPath isDirectory:&isDir]) 562 | { 563 | PrintfErr(@"Error: provided path does not exist:\n%s\n\n", [providedPath UTF8String]); 564 | exit(1); 565 | } 566 | if (!isDir && ![[providedPath pathExtension] isEqualToString:@"webloc"]) 567 | { 568 | PrintfErr(@"Error: specified filename does not have extension: .webloc\n\n"); 569 | exit(1); 570 | } 571 | 572 | if (!isDir) 573 | { 574 | [weblocFilePaths addObject:providedPath]; 575 | } 576 | else 577 | { 578 | NSArray *dirContents = [[NSFileManager defaultManager] 579 | contentsOfDirectoryAtPath:providedPath 580 | error:NULL 581 | ]; 582 | 583 | if (dirContents != nil) 584 | { 585 | NSString *aFile; 586 | for (aFile in dirContents) 587 | { 588 | if ([[aFile pathExtension] isEqualToString:@"webloc"]) 589 | [weblocFilePaths addObject:[providedPath stringByAppendingPathComponent:aFile]]; 590 | } 591 | } 592 | } 593 | 594 | 595 | webViewPrefs = [[[WebPreferences alloc] initWithIdentifier:@"setWeblocThumbWebViewPrefs"] autorelease]; 596 | [webViewPrefs setAllowsAnimatedImages:NO]; 597 | [webViewPrefs setPrivateBrowsingEnabled:YES]; 598 | [webViewPrefs setJavaEnabled:arg_allowJava]; 599 | [webViewPrefs setJavaScriptEnabled:arg_allowJavaScript]; 600 | [webViewPrefs setPlugInsEnabled:arg_allowPlugins]; 601 | 602 | 603 | NSMutableArray *weblocIconifiers = [NSMutableArray arrayWithCapacity:[weblocFilePaths count]]; 604 | 605 | NSString *aFilePath; 606 | for (aFilePath in weblocFilePaths) 607 | { 608 | if (!arg_forceRun && fileHasCustomIcon(aFilePath)) 609 | VerbosePrintf(@"File already has a custom icon: %@\n", aFilePath); 610 | else 611 | { 612 | WeblocIconifier *weblocIconifier = [[[WeblocIconifier alloc] init] autorelease]; 613 | weblocIconifier.weblocFilePath = aFilePath; 614 | [weblocIconifiers addObject:weblocIconifier]; 615 | } 616 | } 617 | 618 | if ([weblocIconifiers count] == 0) 619 | exit(0); 620 | 621 | 622 | baseIconImage = [[NSImage alloc] initWithData:(NSData *)[NSData dataWithBase64EncodedString:imgBase64]]; 623 | NSCAssert((baseIconImage != nil), @"baseIconImage is nil"); 624 | 625 | 626 | WeblocIconifier *aWeblocIconifier; 627 | for (aWeblocIconifier in weblocIconifiers) 628 | { 629 | [aWeblocIconifier startLoadingWithFavicon:arg_favicon]; 630 | } 631 | 632 | 633 | BOOL isRunning = YES; 634 | BOOL someStillLoading = YES; 635 | do 636 | { 637 | isRunning = [[NSRunLoop currentRunLoop] 638 | runMode:NSDefaultRunLoopMode 639 | beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.2] 640 | ]; 641 | 642 | someStillLoading = NO; 643 | for (aWeblocIconifier in weblocIconifiers) 644 | { 645 | someStillLoading = ![aWeblocIconifier doneIconizing]; 646 | if (someStillLoading) 647 | break; 648 | } 649 | } 650 | while(isRunning && someStillLoading); 651 | 652 | [baseIconImage release]; 653 | [autoReleasePool release]; 654 | exit(0); 655 | } 656 | 657 | 658 | -------------------------------------------------------------------------------- /ANSIEscapeHelper.m: -------------------------------------------------------------------------------- 1 | // 2 | // ANSIEscapeHelper.m 3 | // 4 | // Created by Ali Rantakari on 18.3.09. 5 | 6 | /* 7 | The MIT License 8 | 9 | Copyright (c) 2008-2009 Ali Rantakari 10 | 11 | Permission is hereby granted, free of charge, to any person obtaining a copy 12 | of this software and associated documentation files (the "Software"), to deal 13 | in the Software without restriction, including without limitation the rights 14 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 15 | copies of the Software, and to permit persons to whom the Software is 16 | furnished to do so, subject to the following conditions: 17 | 18 | The above copyright notice and this permission notice shall be included in 19 | all copies or substantial portions of the Software. 20 | 21 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 22 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 23 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 24 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 25 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 26 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 27 | THE SOFTWARE. 28 | */ 29 | 30 | /* 31 | todo: 32 | 33 | - don't add useless "reset" escape codes to the string in 34 | -ansiEscapedStringWithAttributedString: 35 | 36 | */ 37 | 38 | 39 | 40 | #import "ANSIEscapeHelper.h" 41 | 42 | 43 | @implementation ANSIEscapeHelper 44 | 45 | @synthesize font; 46 | @synthesize ansiColors; 47 | @synthesize defaultStringColor; 48 | 49 | - (id) init 50 | { 51 | if (( self = [super init] )) 52 | { 53 | self.font = [NSFont systemFontOfSize:[NSFont systemFontSize]]; 54 | self.defaultStringColor = [NSColor blackColor]; 55 | self.ansiColors = [NSMutableDictionary dictionary]; 56 | } 57 | 58 | return self; 59 | } 60 | 61 | - (void) dealloc 62 | { 63 | self.font = nil; 64 | self.ansiColors = nil; 65 | self.defaultStringColor = nil; 66 | [super dealloc]; 67 | } 68 | 69 | 70 | 71 | - (NSAttributedString*) attributedStringWithANSIEscapedString:(NSString*)aString 72 | { 73 | if (aString == nil) 74 | return nil; 75 | 76 | NSString *cleanString; 77 | NSArray *attributesAndRanges = [self attributesForString:aString cleanString:&cleanString]; 78 | NSMutableAttributedString *attributedString = [[[NSMutableAttributedString alloc] 79 | initWithString:cleanString 80 | attributes:[NSDictionary dictionaryWithObjectsAndKeys: 81 | self.font, NSFontAttributeName, 82 | self.defaultStringColor, NSForegroundColorAttributeName, 83 | nil 84 | ] 85 | ] autorelease]; 86 | 87 | NSDictionary *thisAttributeDict; 88 | for (thisAttributeDict in attributesAndRanges) 89 | { 90 | [attributedString 91 | addAttribute:[thisAttributeDict objectForKey:kAttrDictKey_attrName] 92 | value:[thisAttributeDict objectForKey:kAttrDictKey_attrValue] 93 | range:[[thisAttributeDict objectForKey:kAttrDictKey_range] rangeValue] 94 | ]; 95 | } 96 | 97 | return attributedString; 98 | } 99 | 100 | 101 | 102 | - (NSString*) ansiEscapedStringWithAttributedString:(NSAttributedString*)aAttributedString; 103 | { 104 | NSRange limitRange; 105 | NSRange effectiveRange; 106 | id attributeValue; 107 | 108 | NSMutableArray *codesAndLocations = [NSMutableArray array]; 109 | 110 | NSArray *attrNames = [NSArray arrayWithObjects: 111 | NSFontAttributeName, NSForegroundColorAttributeName, 112 | NSBackgroundColorAttributeName, NSUnderlineStyleAttributeName, 113 | nil 114 | ]; 115 | NSString *thisAttrName; 116 | for (thisAttrName in attrNames) 117 | { 118 | limitRange = NSMakeRange(0, [aAttributedString length]); 119 | while (limitRange.length > 0) 120 | { 121 | attributeValue = [aAttributedString 122 | attribute:thisAttrName 123 | atIndex:limitRange.location 124 | longestEffectiveRange:&effectiveRange 125 | inRange:limitRange 126 | ]; 127 | 128 | enum sgrCode thisSGRCode = SGRCodeNoneOrInvalid; 129 | 130 | if ([thisAttrName isEqualToString:NSForegroundColorAttributeName]) 131 | { 132 | if (attributeValue != nil) 133 | thisSGRCode = [self closestSGRCodeForColor:attributeValue isForegroundColor:YES]; 134 | else 135 | thisSGRCode = SGRCodeFgReset; 136 | } 137 | else if ([thisAttrName isEqualToString:NSBackgroundColorAttributeName]) 138 | { 139 | if (attributeValue != nil) 140 | thisSGRCode = [self closestSGRCodeForColor:attributeValue isForegroundColor:NO]; 141 | else 142 | thisSGRCode = SGRCodeBgReset; 143 | } 144 | else if ([thisAttrName isEqualToString:NSFontAttributeName]) 145 | { 146 | // we currently only use NSFontAttributeName for bolding so 147 | // here we assume that the formatting "type" in ANSI SGR 148 | // terms is indeed intensity 149 | if (attributeValue != nil) 150 | thisSGRCode = ([[NSFontManager sharedFontManager] weightOfFont:attributeValue] >= kBoldFontMinWeight) 151 | ? SGRCodeIntensityBold : SGRCodeIntensityNormal; 152 | else 153 | thisSGRCode = SGRCodeIntensityNormal; 154 | } 155 | else if ([thisAttrName isEqualToString:NSUnderlineStyleAttributeName]) 156 | { 157 | if (attributeValue != nil) 158 | { 159 | if ([attributeValue intValue] == NSUnderlineStyleSingle) 160 | thisSGRCode = SGRCodeUnderlineSingle; 161 | else if ([attributeValue intValue] == NSUnderlineStyleDouble) 162 | thisSGRCode = SGRCodeUnderlineDouble; 163 | else 164 | thisSGRCode = SGRCodeUnderlineNone; 165 | } 166 | else 167 | thisSGRCode = SGRCodeUnderlineNone; 168 | } 169 | 170 | if (thisSGRCode != SGRCodeNoneOrInvalid) 171 | { 172 | [codesAndLocations addObject: 173 | [NSDictionary dictionaryWithObjectsAndKeys: 174 | [NSNumber numberWithInt:thisSGRCode], kCodeDictKey_code, 175 | [NSNumber numberWithUnsignedInteger:effectiveRange.location], kCodeDictKey_location, 176 | nil 177 | ] 178 | ]; 179 | } 180 | 181 | limitRange = NSMakeRange(NSMaxRange(effectiveRange), 182 | NSMaxRange(limitRange) - NSMaxRange(effectiveRange)); 183 | } 184 | } 185 | 186 | NSString *ansiEscapedString = [self ansiEscapedStringWithCodesAndLocations:codesAndLocations cleanString:[aAttributedString string]]; 187 | 188 | return ansiEscapedString; 189 | } 190 | 191 | 192 | - (NSArray*) escapeCodesForString:(NSString*)aString cleanString:(NSString**)aCleanString 193 | { 194 | if (aString == nil) 195 | return nil; 196 | if ([aString length] <= [kANSIEscapeCSI length]) 197 | { 198 | *aCleanString = [NSString stringWithString:aString]; 199 | return [NSArray array]; 200 | } 201 | 202 | NSString *cleanString = @""; 203 | 204 | // find all escape sequence codes from aString and put them in this array 205 | // along with their start locations within the "clean" version of aString 206 | NSMutableArray *formatCodes = [NSMutableArray array]; 207 | 208 | NSUInteger aStringLength = [aString length]; 209 | NSUInteger coveredLength = 0; 210 | NSRange searchRange = NSMakeRange(0,aStringLength); 211 | NSRange thisEscapeSequenceRange; 212 | do 213 | { 214 | thisEscapeSequenceRange = [aString rangeOfString:kANSIEscapeCSI options:NSLiteralSearch range:searchRange]; 215 | if (thisEscapeSequenceRange.location != NSNotFound) 216 | { 217 | // adjust range's length so that it encompasses the whole ANSI escape sequence 218 | // and not just the Control Sequence Initiator (the "prefix") by finding the 219 | // final byte of the control sequence (one that has an ASCII decimal value 220 | // between 64 and 126.) at the same time, read all formatting codes from inside 221 | // this escape sequence (there may be several, separated by semicolons.) 222 | NSMutableArray *codes = [NSMutableArray array]; 223 | unsigned int code = 0; 224 | unsigned int lengthAddition = 1; 225 | NSUInteger thisIndex; 226 | for (;;) 227 | { 228 | thisIndex = (NSMaxRange(thisEscapeSequenceRange)+lengthAddition-1); 229 | if (thisIndex >= aStringLength) 230 | break; 231 | 232 | int c = (int)[aString characterAtIndex:thisIndex]; 233 | 234 | if ((48 <= c) && (c <= 57)) // 0-9 235 | { 236 | int digit = c-48; 237 | code = (code == 0) ? digit : code*10+digit; 238 | } 239 | 240 | // ASCII decimal 109 is the SGR (Select Graphic Rendition) final byte 241 | // ("m"). this means that the code value we've just read specifies formatting 242 | // for the output; exactly what we're interested in. 243 | if (c == 109) 244 | { 245 | [codes addObject:[NSNumber numberWithUnsignedInt:code]]; 246 | break; 247 | } 248 | else if ((64 <= c) && (c <= 126)) // any other valid final byte 249 | { 250 | [codes removeAllObjects]; 251 | break; 252 | } 253 | else if (c == 59) // semicolon (;) separates codes within the same sequence 254 | { 255 | [codes addObject:[NSNumber numberWithUnsignedInt:code]]; 256 | code = 0; 257 | } 258 | 259 | lengthAddition++; 260 | } 261 | thisEscapeSequenceRange.length += lengthAddition; 262 | 263 | NSUInteger locationInCleanString = coveredLength+thisEscapeSequenceRange.location-searchRange.location; 264 | 265 | NSUInteger iCode; 266 | for (iCode = 0; iCode < [codes count]; iCode++) 267 | { 268 | [formatCodes addObject: 269 | [NSDictionary dictionaryWithObjectsAndKeys: 270 | [codes objectAtIndex:iCode], kCodeDictKey_code, 271 | [NSNumber numberWithUnsignedInteger:locationInCleanString], kCodeDictKey_location, 272 | nil 273 | ] 274 | ]; 275 | } 276 | 277 | NSUInteger thisCoveredLength = thisEscapeSequenceRange.location-searchRange.location; 278 | if (thisCoveredLength > 0) 279 | cleanString = [cleanString stringByAppendingString:[aString substringWithRange:NSMakeRange(searchRange.location, thisCoveredLength)]]; 280 | 281 | coveredLength += thisCoveredLength; 282 | searchRange.location = NSMaxRange(thisEscapeSequenceRange); 283 | searchRange.length = aStringLength-searchRange.location; 284 | } 285 | } 286 | while(thisEscapeSequenceRange.location != NSNotFound); 287 | 288 | if (searchRange.length > 0) 289 | cleanString = [cleanString stringByAppendingString:[aString substringWithRange:searchRange]]; 290 | 291 | *aCleanString = cleanString; 292 | 293 | return formatCodes; 294 | } 295 | 296 | 297 | 298 | 299 | - (NSString*) ansiEscapedStringWithCodesAndLocations:(NSArray*)aCodesArray cleanString:(NSString*)aCleanString 300 | { 301 | NSMutableString* retStr = [NSMutableString stringWithCapacity:[aCleanString length]]; 302 | 303 | NSSortDescriptor *sortDescriptor = [[[NSSortDescriptor alloc] initWithKey:kCodeDictKey_location ascending:YES] autorelease]; 304 | NSArray *codesArray = [aCodesArray sortedArrayUsingDescriptors:[NSArray arrayWithObject:sortDescriptor]]; 305 | 306 | NSUInteger aCleanStringIndex = 0; 307 | NSUInteger aCleanStringLength = [aCleanString length]; 308 | NSDictionary *thisCodeDict; 309 | for (thisCodeDict in codesArray) 310 | { 311 | if (!( [[thisCodeDict allKeys] containsObject:kCodeDictKey_code] && 312 | [[thisCodeDict allKeys] containsObject:kCodeDictKey_location] 313 | )) 314 | continue; 315 | 316 | enum sgrCode thisCode = [[thisCodeDict objectForKey:kCodeDictKey_code] unsignedIntValue]; 317 | NSUInteger formattingRunStartLocation = [[thisCodeDict objectForKey:kCodeDictKey_location] unsignedIntegerValue]; 318 | 319 | if (formattingRunStartLocation > aCleanStringLength) 320 | continue; 321 | 322 | if (aCleanStringIndex < formattingRunStartLocation) 323 | [retStr appendString:[aCleanString substringWithRange:NSMakeRange(aCleanStringIndex, formattingRunStartLocation-aCleanStringIndex)]]; 324 | [retStr appendString:kANSIEscapeCSI]; 325 | [retStr appendString:[NSString stringWithFormat:@"%d", thisCode]]; 326 | [retStr appendString:kANSIEscapeSGREnd]; 327 | 328 | aCleanStringIndex = formattingRunStartLocation; 329 | } 330 | 331 | if (aCleanStringIndex < aCleanStringLength) 332 | [retStr appendString:[aCleanString substringFromIndex:aCleanStringIndex]]; 333 | 334 | return retStr; 335 | } 336 | 337 | 338 | 339 | 340 | 341 | - (NSArray*) attributesForString:(NSString*)aString cleanString:(NSString**)aCleanString 342 | { 343 | if (aString == nil) 344 | return nil; 345 | if ([aString length] <= [kANSIEscapeCSI length]) 346 | { 347 | if (aCleanString != NULL) 348 | *aCleanString = [NSString stringWithString:aString]; 349 | return [NSArray array]; 350 | } 351 | 352 | NSMutableArray *attrsAndRanges = [NSMutableArray array]; 353 | 354 | NSString *cleanString; 355 | 356 | NSArray *formatCodes = [self escapeCodesForString:aString cleanString:&cleanString]; 357 | 358 | // go through all the found escape sequence codes and for each one, create 359 | // the string formatting attribute name and value, find the next escape 360 | // sequence that specifies the end of the formatting run started by 361 | // the currently handled code, and generate a range from the difference 362 | // in those codes' locations within the clean aString. 363 | NSUInteger iCode; 364 | for (iCode = 0; iCode < [formatCodes count]; iCode++) 365 | { 366 | NSDictionary *thisCodeDict = [formatCodes objectAtIndex:iCode]; 367 | enum sgrCode thisCode = [[thisCodeDict objectForKey:kCodeDictKey_code] unsignedIntValue]; 368 | NSUInteger formattingRunStartLocation = [[thisCodeDict objectForKey:kCodeDictKey_location] unsignedIntegerValue]; 369 | 370 | // the attributed string attribute name for the formatting run introduced 371 | // by this code 372 | NSString *thisAttributeName = nil; 373 | 374 | // the attributed string attribute value for this formatting run introduced 375 | // by this code 376 | NSObject *thisAttributeValue = nil; 377 | 378 | // set attribute name 379 | switch(thisCode) 380 | { 381 | case SGRCodeFgBlack: 382 | case SGRCodeFgRed: 383 | case SGRCodeFgGreen: 384 | case SGRCodeFgYellow: 385 | case SGRCodeFgBlue: 386 | case SGRCodeFgMagenta: 387 | case SGRCodeFgCyan: 388 | case SGRCodeFgWhite: 389 | case SGRCodeFgBrightBlack: 390 | case SGRCodeFgBrightRed: 391 | case SGRCodeFgBrightGreen: 392 | case SGRCodeFgBrightYellow: 393 | case SGRCodeFgBrightBlue: 394 | case SGRCodeFgBrightMagenta: 395 | case SGRCodeFgBrightCyan: 396 | case SGRCodeFgBrightWhite: 397 | thisAttributeName = NSForegroundColorAttributeName; 398 | break; 399 | case SGRCodeBgBlack: 400 | case SGRCodeBgRed: 401 | case SGRCodeBgGreen: 402 | case SGRCodeBgYellow: 403 | case SGRCodeBgBlue: 404 | case SGRCodeBgMagenta: 405 | case SGRCodeBgCyan: 406 | case SGRCodeBgWhite: 407 | case SGRCodeBgBrightBlack: 408 | case SGRCodeBgBrightRed: 409 | case SGRCodeBgBrightGreen: 410 | case SGRCodeBgBrightYellow: 411 | case SGRCodeBgBrightBlue: 412 | case SGRCodeBgBrightMagenta: 413 | case SGRCodeBgBrightCyan: 414 | case SGRCodeBgBrightWhite: 415 | thisAttributeName = NSBackgroundColorAttributeName; 416 | break; 417 | case SGRCodeIntensityBold: 418 | case SGRCodeIntensityNormal: 419 | thisAttributeName = NSFontAttributeName; 420 | break; 421 | case SGRCodeUnderlineSingle: 422 | case SGRCodeUnderlineDouble: 423 | thisAttributeName = NSUnderlineStyleAttributeName; 424 | break; 425 | default: 426 | continue; 427 | break; 428 | } 429 | 430 | // set attribute value 431 | switch(thisCode) 432 | { 433 | case SGRCodeBgBlack: 434 | case SGRCodeFgBlack: 435 | case SGRCodeBgRed: 436 | case SGRCodeFgRed: 437 | case SGRCodeBgGreen: 438 | case SGRCodeFgGreen: 439 | case SGRCodeBgYellow: 440 | case SGRCodeFgYellow: 441 | case SGRCodeBgBlue: 442 | case SGRCodeFgBlue: 443 | case SGRCodeBgMagenta: 444 | case SGRCodeFgMagenta: 445 | case SGRCodeBgCyan: 446 | case SGRCodeFgCyan: 447 | case SGRCodeBgWhite: 448 | case SGRCodeFgWhite: 449 | case SGRCodeBgBrightBlack: 450 | case SGRCodeFgBrightBlack: 451 | case SGRCodeBgBrightRed: 452 | case SGRCodeFgBrightRed: 453 | case SGRCodeBgBrightGreen: 454 | case SGRCodeFgBrightGreen: 455 | case SGRCodeBgBrightYellow: 456 | case SGRCodeFgBrightYellow: 457 | case SGRCodeBgBrightBlue: 458 | case SGRCodeFgBrightBlue: 459 | case SGRCodeBgBrightMagenta: 460 | case SGRCodeFgBrightMagenta: 461 | case SGRCodeBgBrightCyan: 462 | case SGRCodeFgBrightCyan: 463 | case SGRCodeBgBrightWhite: 464 | case SGRCodeFgBrightWhite: 465 | thisAttributeValue = [self colorForSGRCode:thisCode]; 466 | break; 467 | case SGRCodeIntensityBold: 468 | { 469 | NSFont *boldFont = [[NSFontManager sharedFontManager] convertFont:self.font toHaveTrait:NSBoldFontMask]; 470 | thisAttributeValue = boldFont; 471 | } 472 | break; 473 | case SGRCodeIntensityNormal: 474 | { 475 | NSFont *unboldFont = [[NSFontManager sharedFontManager] convertFont:self.font toHaveTrait:NSUnboldFontMask]; 476 | thisAttributeValue = unboldFont; 477 | } 478 | break; 479 | case SGRCodeUnderlineSingle: 480 | thisAttributeValue = [NSNumber numberWithInteger:NSUnderlineStyleSingle]; 481 | break; 482 | case SGRCodeUnderlineDouble: 483 | thisAttributeValue = [NSNumber numberWithInteger:NSUnderlineStyleDouble]; 484 | break; 485 | default: 486 | break; 487 | } 488 | 489 | 490 | // find the next sequence that specifies the end of this formatting run 491 | NSInteger formattingRunEndLocation = -1; 492 | if (iCode < ([formatCodes count]-1)) 493 | { 494 | NSUInteger iEndCode; 495 | NSDictionary *thisEndCodeCandidateDict; 496 | unichar thisEndCodeCandidate; 497 | for (iEndCode = iCode+1; iEndCode < [formatCodes count]; iEndCode++) 498 | { 499 | thisEndCodeCandidateDict = [formatCodes objectAtIndex:iEndCode]; 500 | thisEndCodeCandidate = [[thisEndCodeCandidateDict objectForKey:kCodeDictKey_code] unsignedIntValue]; 501 | 502 | if ([self sgrCode:thisEndCodeCandidate endsFormattingIntroducedByCode:thisCode]) 503 | { 504 | formattingRunEndLocation = [[thisEndCodeCandidateDict objectForKey:kCodeDictKey_location] unsignedIntegerValue]; 505 | break; 506 | } 507 | } 508 | } 509 | if (formattingRunEndLocation == -1) 510 | formattingRunEndLocation = [cleanString length]; 511 | 512 | // add attribute name, attribute value and formatting run range 513 | // to the array we're going to return 514 | [attrsAndRanges addObject: 515 | [NSDictionary dictionaryWithObjectsAndKeys: 516 | [NSValue valueWithRange:NSMakeRange(formattingRunStartLocation, (formattingRunEndLocation-formattingRunStartLocation))], kAttrDictKey_range, 517 | thisAttributeName, kAttrDictKey_attrName, 518 | thisAttributeValue, kAttrDictKey_attrValue, 519 | nil 520 | ] 521 | ]; 522 | } 523 | 524 | if (aCleanString != NULL) 525 | *aCleanString = cleanString; 526 | 527 | return attrsAndRanges; 528 | } 529 | 530 | 531 | 532 | 533 | 534 | - (BOOL) sgrCode:(enum sgrCode)endCode endsFormattingIntroducedByCode:(enum sgrCode)startCode 535 | { 536 | switch(startCode) 537 | { 538 | case SGRCodeFgBlack: 539 | case SGRCodeFgRed: 540 | case SGRCodeFgGreen: 541 | case SGRCodeFgYellow: 542 | case SGRCodeFgBlue: 543 | case SGRCodeFgMagenta: 544 | case SGRCodeFgCyan: 545 | case SGRCodeFgWhite: 546 | case SGRCodeFgBrightBlack: 547 | case SGRCodeFgBrightRed: 548 | case SGRCodeFgBrightGreen: 549 | case SGRCodeFgBrightYellow: 550 | case SGRCodeFgBrightBlue: 551 | case SGRCodeFgBrightMagenta: 552 | case SGRCodeFgBrightCyan: 553 | case SGRCodeFgBrightWhite: 554 | return (endCode == SGRCodeAllReset || endCode == SGRCodeFgReset || 555 | endCode == SGRCodeFgBlack || endCode == SGRCodeFgRed || 556 | endCode == SGRCodeFgGreen || endCode == SGRCodeFgYellow || 557 | endCode == SGRCodeFgBlue || endCode == SGRCodeFgMagenta || 558 | endCode == SGRCodeFgCyan || endCode == SGRCodeFgWhite || 559 | endCode == SGRCodeFgBrightBlack || endCode == SGRCodeFgBrightRed || 560 | endCode == SGRCodeFgBrightGreen || endCode == SGRCodeFgBrightYellow || 561 | endCode == SGRCodeFgBrightBlue || endCode == SGRCodeFgBrightMagenta || 562 | endCode == SGRCodeFgBrightCyan || endCode == SGRCodeFgBrightWhite); 563 | break; 564 | case SGRCodeBgBlack: 565 | case SGRCodeBgRed: 566 | case SGRCodeBgGreen: 567 | case SGRCodeBgYellow: 568 | case SGRCodeBgBlue: 569 | case SGRCodeBgMagenta: 570 | case SGRCodeBgCyan: 571 | case SGRCodeBgWhite: 572 | case SGRCodeBgBrightBlack: 573 | case SGRCodeBgBrightRed: 574 | case SGRCodeBgBrightGreen: 575 | case SGRCodeBgBrightYellow: 576 | case SGRCodeBgBrightBlue: 577 | case SGRCodeBgBrightMagenta: 578 | case SGRCodeBgBrightCyan: 579 | case SGRCodeBgBrightWhite: 580 | return (endCode == SGRCodeAllReset || endCode == SGRCodeBgReset || 581 | endCode == SGRCodeBgBlack || endCode == SGRCodeBgRed || 582 | endCode == SGRCodeBgGreen || endCode == SGRCodeBgYellow || 583 | endCode == SGRCodeBgBlue || endCode == SGRCodeBgMagenta || 584 | endCode == SGRCodeBgCyan || endCode == SGRCodeBgWhite || 585 | endCode == SGRCodeBgBrightBlack || endCode == SGRCodeBgBrightRed || 586 | endCode == SGRCodeBgBrightGreen || endCode == SGRCodeBgBrightYellow || 587 | endCode == SGRCodeBgBrightBlue || endCode == SGRCodeBgBrightMagenta || 588 | endCode == SGRCodeBgBrightCyan || endCode == SGRCodeBgBrightWhite); 589 | break; 590 | case SGRCodeIntensityBold: 591 | case SGRCodeIntensityNormal: 592 | return (endCode == SGRCodeAllReset || endCode == SGRCodeIntensityNormal || 593 | endCode == SGRCodeIntensityBold || endCode == SGRCodeIntensityFaint); 594 | break; 595 | case SGRCodeUnderlineSingle: 596 | case SGRCodeUnderlineDouble: 597 | return (endCode == SGRCodeAllReset || endCode == SGRCodeUnderlineNone || 598 | endCode == SGRCodeUnderlineSingle || endCode == SGRCodeUnderlineDouble); 599 | break; 600 | default: 601 | return NO; 602 | break; 603 | } 604 | 605 | return NO; 606 | } 607 | 608 | 609 | 610 | 611 | - (NSColor*) colorForSGRCode:(enum sgrCode)code 612 | { 613 | if (self.ansiColors != nil) 614 | { 615 | NSColor *preferredColor = [self.ansiColors objectForKey:[NSNumber numberWithInt:code]]; 616 | if (preferredColor != nil) 617 | return preferredColor; 618 | } 619 | 620 | switch(code) 621 | { 622 | case SGRCodeFgBlack: 623 | return kDefaultANSIColorFgBlack; 624 | break; 625 | case SGRCodeFgRed: 626 | return kDefaultANSIColorFgRed; 627 | break; 628 | case SGRCodeFgGreen: 629 | return kDefaultANSIColorFgGreen; 630 | break; 631 | case SGRCodeFgYellow: 632 | return kDefaultANSIColorFgYellow; 633 | break; 634 | case SGRCodeFgBlue: 635 | return kDefaultANSIColorFgBlue; 636 | break; 637 | case SGRCodeFgMagenta: 638 | return kDefaultANSIColorFgMagenta; 639 | break; 640 | case SGRCodeFgCyan: 641 | return kDefaultANSIColorFgCyan; 642 | break; 643 | case SGRCodeFgWhite: 644 | return kDefaultANSIColorFgWhite; 645 | break; 646 | case SGRCodeFgBrightBlack: 647 | return kDefaultANSIColorFgBrightBlack; 648 | break; 649 | case SGRCodeFgBrightRed: 650 | return kDefaultANSIColorFgBrightRed; 651 | break; 652 | case SGRCodeFgBrightGreen: 653 | return kDefaultANSIColorFgBrightGreen; 654 | break; 655 | case SGRCodeFgBrightYellow: 656 | return kDefaultANSIColorFgBrightYellow; 657 | break; 658 | case SGRCodeFgBrightBlue: 659 | return kDefaultANSIColorFgBrightBlue; 660 | break; 661 | case SGRCodeFgBrightMagenta: 662 | return kDefaultANSIColorFgBrightMagenta; 663 | break; 664 | case SGRCodeFgBrightCyan: 665 | return kDefaultANSIColorFgBrightCyan; 666 | break; 667 | case SGRCodeFgBrightWhite: 668 | return kDefaultANSIColorFgBrightWhite; 669 | break; 670 | case SGRCodeBgBlack: 671 | return kDefaultANSIColorBgBlack; 672 | break; 673 | case SGRCodeBgRed: 674 | return kDefaultANSIColorBgRed; 675 | break; 676 | case SGRCodeBgGreen: 677 | return kDefaultANSIColorBgGreen; 678 | break; 679 | case SGRCodeBgYellow: 680 | return kDefaultANSIColorBgYellow; 681 | break; 682 | case SGRCodeBgBlue: 683 | return kDefaultANSIColorBgBlue; 684 | break; 685 | case SGRCodeBgMagenta: 686 | return kDefaultANSIColorBgMagenta; 687 | break; 688 | case SGRCodeBgCyan: 689 | return kDefaultANSIColorBgCyan; 690 | break; 691 | case SGRCodeBgWhite: 692 | return kDefaultANSIColorBgWhite; 693 | break; 694 | case SGRCodeBgBrightBlack: 695 | return kDefaultANSIColorBgBrightBlack; 696 | break; 697 | case SGRCodeBgBrightRed: 698 | return kDefaultANSIColorBgBrightRed; 699 | break; 700 | case SGRCodeBgBrightGreen: 701 | return kDefaultANSIColorBgBrightGreen; 702 | break; 703 | case SGRCodeBgBrightYellow: 704 | return kDefaultANSIColorBgBrightYellow; 705 | break; 706 | case SGRCodeBgBrightBlue: 707 | return kDefaultANSIColorBgBrightBlue; 708 | break; 709 | case SGRCodeBgBrightMagenta: 710 | return kDefaultANSIColorBgBrightMagenta; 711 | break; 712 | case SGRCodeBgBrightCyan: 713 | return kDefaultANSIColorBgBrightCyan; 714 | break; 715 | case SGRCodeBgBrightWhite: 716 | return kDefaultANSIColorBgBrightWhite; 717 | break; 718 | default: 719 | break; 720 | } 721 | 722 | return kDefaultANSIColorFgBlack; 723 | } 724 | 725 | 726 | - (enum sgrCode) sgrCodeForColor:(NSColor*)aColor isForegroundColor:(BOOL)aForeground 727 | { 728 | if (self.ansiColors != nil) 729 | { 730 | NSArray *codesForGivenColor = [self.ansiColors allKeysForObject:aColor]; 731 | 732 | if (codesForGivenColor != nil && [codesForGivenColor count] > 0) 733 | { 734 | NSNumber *thisCode; 735 | for (thisCode in codesForGivenColor) 736 | { 737 | BOOL thisIsForegroundColor = ([thisCode intValue] < 40); 738 | if (aForeground == thisIsForegroundColor) 739 | return [thisCode intValue]; 740 | } 741 | } 742 | } 743 | 744 | if (aForeground) 745 | { 746 | if ([aColor isEqual:kDefaultANSIColorFgBlack]) 747 | return SGRCodeFgBlack; 748 | else if ([aColor isEqual:kDefaultANSIColorFgRed]) 749 | return SGRCodeFgRed; 750 | else if ([aColor isEqual:kDefaultANSIColorFgGreen]) 751 | return SGRCodeFgGreen; 752 | else if ([aColor isEqual:kDefaultANSIColorFgYellow]) 753 | return SGRCodeFgYellow; 754 | else if ([aColor isEqual:kDefaultANSIColorFgBlue]) 755 | return SGRCodeFgBlue; 756 | else if ([aColor isEqual:kDefaultANSIColorFgMagenta]) 757 | return SGRCodeFgMagenta; 758 | else if ([aColor isEqual:kDefaultANSIColorFgCyan]) 759 | return SGRCodeFgCyan; 760 | else if ([aColor isEqual:kDefaultANSIColorFgWhite]) 761 | return SGRCodeFgWhite; 762 | else if ([aColor isEqual:kDefaultANSIColorFgBrightBlack]) 763 | return SGRCodeFgBrightBlack; 764 | else if ([aColor isEqual:kDefaultANSIColorFgBrightRed]) 765 | return SGRCodeFgBrightRed; 766 | else if ([aColor isEqual:kDefaultANSIColorFgBrightGreen]) 767 | return SGRCodeFgBrightGreen; 768 | else if ([aColor isEqual:kDefaultANSIColorFgBrightYellow]) 769 | return SGRCodeFgBrightYellow; 770 | else if ([aColor isEqual:kDefaultANSIColorFgBrightBlue]) 771 | return SGRCodeFgBrightBlue; 772 | else if ([aColor isEqual:kDefaultANSIColorFgBrightMagenta]) 773 | return SGRCodeFgBrightMagenta; 774 | else if ([aColor isEqual:kDefaultANSIColorFgBrightCyan]) 775 | return SGRCodeFgBrightCyan; 776 | else if ([aColor isEqual:kDefaultANSIColorFgBrightWhite]) 777 | return SGRCodeFgBrightWhite; 778 | } 779 | else 780 | { 781 | if ([aColor isEqual:kDefaultANSIColorBgBlack]) 782 | return SGRCodeBgBlack; 783 | else if ([aColor isEqual:kDefaultANSIColorBgRed]) 784 | return SGRCodeBgRed; 785 | else if ([aColor isEqual:kDefaultANSIColorBgGreen]) 786 | return SGRCodeBgGreen; 787 | else if ([aColor isEqual:kDefaultANSIColorBgYellow]) 788 | return SGRCodeBgYellow; 789 | else if ([aColor isEqual:kDefaultANSIColorBgBlue]) 790 | return SGRCodeBgBlue; 791 | else if ([aColor isEqual:kDefaultANSIColorBgMagenta]) 792 | return SGRCodeBgMagenta; 793 | else if ([aColor isEqual:kDefaultANSIColorBgCyan]) 794 | return SGRCodeBgCyan; 795 | else if ([aColor isEqual:kDefaultANSIColorBgWhite]) 796 | return SGRCodeBgWhite; 797 | else if ([aColor isEqual:kDefaultANSIColorBgBrightBlack]) 798 | return SGRCodeBgBrightBlack; 799 | else if ([aColor isEqual:kDefaultANSIColorBgBrightRed]) 800 | return SGRCodeBgBrightRed; 801 | else if ([aColor isEqual:kDefaultANSIColorBgBrightGreen]) 802 | return SGRCodeBgBrightGreen; 803 | else if ([aColor isEqual:kDefaultANSIColorBgBrightYellow]) 804 | return SGRCodeBgBrightYellow; 805 | else if ([aColor isEqual:kDefaultANSIColorBgBrightBlue]) 806 | return SGRCodeBgBrightBlue; 807 | else if ([aColor isEqual:kDefaultANSIColorBgBrightMagenta]) 808 | return SGRCodeBgBrightMagenta; 809 | else if ([aColor isEqual:kDefaultANSIColorBgBrightCyan]) 810 | return SGRCodeBgBrightCyan; 811 | else if ([aColor isEqual:kDefaultANSIColorBgBrightWhite]) 812 | return SGRCodeBgBrightWhite; 813 | } 814 | 815 | return SGRCodeNoneOrInvalid; 816 | } 817 | 818 | 819 | 820 | // helper struct typedef and a few functions for 821 | // -closestSGRCodeForColor:isForegroundColor: 822 | 823 | typedef struct _HSB { 824 | CGFloat hue; 825 | CGFloat saturation; 826 | CGFloat brightness; 827 | } HSB; 828 | 829 | HSB makeHSB(CGFloat hue, CGFloat saturation, CGFloat brightness) 830 | { 831 | HSB outHSB; 832 | outHSB.hue = hue; 833 | outHSB.saturation = saturation; 834 | outHSB.brightness = brightness; 835 | return outHSB; 836 | } 837 | 838 | HSB getHSBFromColor(NSColor *color) 839 | { 840 | CGFloat hue = 0.0; 841 | CGFloat saturation = 0.0; 842 | CGFloat brightness = 0.0; 843 | [[color colorUsingColorSpaceName:NSCalibratedRGBColorSpace] 844 | getHue:&hue 845 | saturation:&saturation 846 | brightness:&brightness 847 | alpha:NULL 848 | ]; 849 | return makeHSB(hue, saturation, brightness); 850 | } 851 | 852 | BOOL floatsEqual(CGFloat first, CGFloat second, CGFloat maxAbsError) 853 | { 854 | return (fabs(first-second)) < maxAbsError; 855 | } 856 | 857 | #define MAX_HUE_FLOAT_EQUALITY_ABS_ERROR 0.000001 858 | 859 | - (enum sgrCode) closestSGRCodeForColor:(NSColor *)color isForegroundColor:(BOOL)foreground 860 | { 861 | if (color == nil) 862 | return SGRCodeNoneOrInvalid; 863 | 864 | enum sgrCode closestColorSGRCode = [self sgrCodeForColor:color isForegroundColor:foreground]; 865 | if (closestColorSGRCode != SGRCodeNoneOrInvalid) 866 | return closestColorSGRCode; 867 | 868 | HSB givenColorHSB = getHSBFromColor(color); 869 | 870 | CGFloat closestColorHueDiff = FLT_MAX; 871 | CGFloat closestColorSaturationDiff = FLT_MAX; 872 | CGFloat closestColorBrightnessDiff = FLT_MAX; 873 | 874 | // (background SGR codes are +10 from foreground ones:) 875 | NSUInteger sgrCodeShift = (foreground)?0:10; 876 | NSArray *ansiFgColorCodes = [NSArray 877 | arrayWithObjects: 878 | [NSNumber numberWithInt:SGRCodeFgBlack+sgrCodeShift], 879 | [NSNumber numberWithInt:SGRCodeFgRed+sgrCodeShift], 880 | [NSNumber numberWithInt:SGRCodeFgGreen+sgrCodeShift], 881 | [NSNumber numberWithInt:SGRCodeFgYellow+sgrCodeShift], 882 | [NSNumber numberWithInt:SGRCodeFgBlue+sgrCodeShift], 883 | [NSNumber numberWithInt:SGRCodeFgMagenta+sgrCodeShift], 884 | [NSNumber numberWithInt:SGRCodeFgCyan+sgrCodeShift], 885 | [NSNumber numberWithInt:SGRCodeFgWhite+sgrCodeShift], 886 | [NSNumber numberWithInt:SGRCodeFgBrightBlack+sgrCodeShift], 887 | [NSNumber numberWithInt:SGRCodeFgBrightRed+sgrCodeShift], 888 | [NSNumber numberWithInt:SGRCodeFgBrightGreen+sgrCodeShift], 889 | [NSNumber numberWithInt:SGRCodeFgBrightYellow+sgrCodeShift], 890 | [NSNumber numberWithInt:SGRCodeFgBrightBlue+sgrCodeShift], 891 | [NSNumber numberWithInt:SGRCodeFgBrightMagenta+sgrCodeShift], 892 | [NSNumber numberWithInt:SGRCodeFgBrightCyan+sgrCodeShift], 893 | [NSNumber numberWithInt:SGRCodeFgBrightWhite+sgrCodeShift], 894 | nil 895 | ]; 896 | for (NSNumber *thisSGRCodeNumber in ansiFgColorCodes) 897 | { 898 | enum sgrCode thisSGRCode = [thisSGRCodeNumber intValue]; 899 | NSColor *thisColor = [self colorForSGRCode:thisSGRCode]; 900 | 901 | HSB thisColorHSB = getHSBFromColor(thisColor); 902 | 903 | CGFloat hueDiff = fabs(givenColorHSB.hue - thisColorHSB.hue); 904 | CGFloat saturationDiff = fabs(givenColorHSB.saturation - thisColorHSB.saturation); 905 | CGFloat brightnessDiff = fabs(givenColorHSB.brightness - thisColorHSB.brightness); 906 | 907 | // comparison depends on hue, saturation and brightness 908 | // (strictly in that order): 909 | 910 | if (!floatsEqual(hueDiff, closestColorHueDiff, MAX_HUE_FLOAT_EQUALITY_ABS_ERROR)) 911 | { 912 | if (hueDiff > closestColorHueDiff) 913 | continue; 914 | closestColorSGRCode = thisSGRCode; 915 | closestColorHueDiff = hueDiff; 916 | closestColorSaturationDiff = saturationDiff; 917 | closestColorBrightnessDiff = brightnessDiff; 918 | continue; 919 | } 920 | 921 | if (!floatsEqual(saturationDiff, closestColorSaturationDiff, MAX_HUE_FLOAT_EQUALITY_ABS_ERROR)) 922 | { 923 | if (saturationDiff > closestColorSaturationDiff) 924 | continue; 925 | closestColorSGRCode = thisSGRCode; 926 | closestColorHueDiff = hueDiff; 927 | closestColorSaturationDiff = saturationDiff; 928 | closestColorBrightnessDiff = brightnessDiff; 929 | continue; 930 | } 931 | 932 | if (!floatsEqual(brightnessDiff, closestColorBrightnessDiff, MAX_HUE_FLOAT_EQUALITY_ABS_ERROR)) 933 | { 934 | if (brightnessDiff > closestColorBrightnessDiff) 935 | continue; 936 | closestColorSGRCode = thisSGRCode; 937 | closestColorHueDiff = hueDiff; 938 | closestColorSaturationDiff = saturationDiff; 939 | closestColorBrightnessDiff = brightnessDiff; 940 | continue; 941 | } 942 | 943 | // If hue (especially hue!), saturation and brightness diffs all 944 | // are equal to some other color, we need to prefer one or the 945 | // other so we'll select the more 'distinctive' color of the 946 | // two (this is *very* subjective, obviously). I basically just 947 | // looked at the hue chart, went through all the points between 948 | // our main ANSI colors and decided which side the middle point 949 | // would lean on. (e.g. the purple color that is exactly between 950 | // the blue and magenta ANSI colors looks more magenta than 951 | // blue to me so I put magenta higher than blue in the list 952 | // below.) 953 | // 954 | // subjective ordering of colors from most to least 'distinctive': 955 | int colorDistinctivenessOrder[6] = { 956 | SGRCodeFgRed+sgrCodeShift, 957 | SGRCodeFgMagenta+sgrCodeShift, 958 | SGRCodeFgBlue+sgrCodeShift, 959 | SGRCodeFgGreen+sgrCodeShift, 960 | SGRCodeFgCyan+sgrCodeShift, 961 | SGRCodeFgYellow+sgrCodeShift 962 | }; 963 | int i; 964 | for (i = 0; i < 6; i++) 965 | { 966 | if (colorDistinctivenessOrder[i] == closestColorSGRCode) 967 | break; 968 | else if (colorDistinctivenessOrder[i] == thisSGRCode) 969 | { 970 | closestColorSGRCode = thisSGRCode; 971 | closestColorHueDiff = hueDiff; 972 | closestColorSaturationDiff = saturationDiff; 973 | closestColorBrightnessDiff = brightnessDiff; 974 | } 975 | } 976 | } 977 | 978 | return closestColorSGRCode; 979 | } 980 | 981 | 982 | 983 | @end 984 | --------------------------------------------------------------------------------