1
0
mirror of https://github.com/Toxblh/MTMR.git synced 2026-01-10 17:08:39 +00:00

Add new "shellScriptTitledButton" button type

This commit is contained in:
bobrosoft 2019-08-11 15:25:44 +02:00
parent 580f0275fc
commit 80b56779fb
10 changed files with 1476 additions and 13 deletions

View File

@ -18,6 +18,8 @@
36C2ECDD207C723B003CDA33 /* ParseConfigTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 36C2ECDC207C723B003CDA33 /* ParseConfigTests.swift */; };
36C2ECDE207C82DE003CDA33 /* ItemsParsing.swift in Sources */ = {isa = PBXBuildFile; fileRef = 36C2ECDA207C3FE7003CDA33 /* ItemsParsing.swift */; };
36C2ECE0207CB1B0003CDA33 /* defaultPreset.json in Resources */ = {isa = PBXBuildFile; fileRef = 36C2ECDF207CB1B0003CDA33 /* defaultPreset.json */; };
4CC9FEDC22FDEA65001512EB /* AMR_ANSIEscapeHelper.m in Sources */ = {isa = PBXBuildFile; fileRef = 4CC9FEDB22FDEA65001512EB /* AMR_ANSIEscapeHelper.m */; };
4CDC6E5022FCA93F0069ADD4 /* ShellScriptTouchBarItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CDC6E4F22FCA93F0069ADD4 /* ShellScriptTouchBarItem.swift */; };
4CFF5E5C22E623DD00BFB1EE /* YandexWeatherBarItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CFF5E5B22E623DD00BFB1EE /* YandexWeatherBarItem.swift */; };
60173D3E20C0031B002C305F /* LaunchAtLoginController.m in Sources */ = {isa = PBXBuildFile; fileRef = 60173D3D20C0031B002C305F /* LaunchAtLoginController.m */; };
6027D1B92080E52A004FFDC7 /* BrightnessViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6027D1B72080E52A004FFDC7 /* BrightnessViewController.swift */; };
@ -92,6 +94,9 @@
36C2ECDA207C3FE7003CDA33 /* ItemsParsing.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ItemsParsing.swift; sourceTree = "<group>"; };
36C2ECDC207C723B003CDA33 /* ParseConfigTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ParseConfigTests.swift; sourceTree = "<group>"; };
36C2ECDF207CB1B0003CDA33 /* defaultPreset.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = defaultPreset.json; sourceTree = "<group>"; };
4CC9FEDA22FDEA65001512EB /* AMR_ANSIEscapeHelper.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AMR_ANSIEscapeHelper.h; sourceTree = "<group>"; };
4CC9FEDB22FDEA65001512EB /* AMR_ANSIEscapeHelper.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AMR_ANSIEscapeHelper.m; sourceTree = "<group>"; };
4CDC6E4F22FCA93F0069ADD4 /* ShellScriptTouchBarItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShellScriptTouchBarItem.swift; sourceTree = "<group>"; };
4CFF5E5B22E623DD00BFB1EE /* YandexWeatherBarItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = YandexWeatherBarItem.swift; sourceTree = "<group>"; };
60173D3C20C0031B002C305F /* LaunchAtLoginController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = LaunchAtLoginController.h; sourceTree = "<group>"; };
60173D3D20C0031B002C305F /* LaunchAtLoginController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = LaunchAtLoginController.m; sourceTree = "<group>"; };
@ -222,6 +227,7 @@
B0A7E9A9205D6AA400EEF070 /* KeyPress.swift */,
B059D623205E04F3006E6B86 /* CustomButtonTouchBarItem.swift */,
36C2ECD8207B74B4003CDA33 /* AppleScriptTouchBarItem.swift */,
4CDC6E4F22FCA93F0069ADD4 /* ShellScriptTouchBarItem.swift */,
B059D621205E03F5006E6B86 /* TouchBarController.swift */,
36C2ECDA207C3FE7003CDA33 /* ItemsParsing.swift */,
B0008E542080286C003AD4DD /* SupportHelpers.swift */,
@ -266,6 +272,8 @@
B0B1743B207D6ED40004B740 /* CBridge */ = {
isa = PBXGroup;
children = (
4CC9FEDA22FDEA65001512EB /* AMR_ANSIEscapeHelper.h */,
4CC9FEDB22FDEA65001512EB /* AMR_ANSIEscapeHelper.m */,
B059D629205E13E5006E6B86 /* TouchBarPrivateApi.h */,
B059D62A205F0E7D006E6B86 /* TouchBarPrivateApi-Bridging.h */,
B0F87719207AC1EA00D6E430 /* TouchBarSupport.m */,
@ -459,6 +467,7 @@
B059D624205E04F3006E6B86 /* CustomButtonTouchBarItem.swift in Sources */,
60173D3E20C0031B002C305F /* LaunchAtLoginController.m in Sources */,
6027D1BA2080E52A004FFDC7 /* VolumeViewController.swift in Sources */,
4CDC6E5022FCA93F0069ADD4 /* ShellScriptTouchBarItem.swift in Sources */,
607EEA4B2087835F009DA5F0 /* WeatherBarItem.swift in Sources */,
B0F3112520C9E35F0076BB88 /* SupportNSTouchBar.swift in Sources */,
4CFF5E5C22E623DD00BFB1EE /* YandexWeatherBarItem.swift in Sources */,
@ -472,6 +481,7 @@
B0A7E9AA205D6AA400EEF070 /* KeyPress.swift in Sources */,
36C2ECD7207B6DAE003CDA33 /* TimeTouchBarItem.swift in Sources */,
607EEA4D2087A8DA009DA5F0 /* CurrencyBarItem.swift in Sources */,
4CC9FEDC22FDEA65001512EB /* AMR_ANSIEscapeHelper.m in Sources */,
6027D1B92080E52A004FFDC7 /* BrightnessViewController.swift in Sources */,
368EDDE720812A1D00E10953 /* ScrollViewItem.swift in Sources */,
B04B7BB72087398C00C835D0 /* BatteryBarItem.swift in Sources */,

View File

@ -0,0 +1,326 @@
//
// ANSIEscapeHelper.h
// AnsiColorsTest
//
// Created by Ali Rantakari on 18.3.09.
//
// Version 0.9.6
//
/*
The MIT License
Copyright (c) 2008-2009,2013 Ali Rantakari
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
#import <Cocoa/Cocoa.h>
#if !__has_feature(objc_arc)
#warning "This code requires ARC to be enabled."
#endif
// dictionary keys for the SGR code dictionaries that the array
// escapeCodesForString:cleanString: returns contains
#define kAMRCodeDictKey_code @"code"
#define kAMRCodeDictKey_location @"location"
// dictionary keys for the string formatting attribute
// dictionaries that the array attributesForString:cleanString:
// returns contains
#define kAMRAttrDictKey_range @"range"
#define kAMRAttrDictKey_attrName @"attributeName"
#define kAMRAttrDictKey_attrValue @"attributeValue"
/*!
@enum AMR_SGRCode
@abstract SGR (Select Graphic Rendition) ANSI control codes.
*/
typedef enum
{
AMR_SGRCodeNoneOrInvalid = -1,
AMR_SGRCodeAllReset = 0,
AMR_SGRCodeIntensityBold = 1,
AMR_SGRCodeIntensityFaint = 2,
AMR_SGRCodeIntensityNormal = 22,
AMR_SGRCodeItalicOn = 3,
AMR_SGRCodeUnderlineSingle = 4,
AMR_SGRCodeUnderlineDouble = 21,
AMR_SGRCodeUnderlineNone = 24,
AMR_SGRCodeFgBlack = 30,
AMR_SGRCodeFgRed = 31,
AMR_SGRCodeFgGreen = 32,
AMR_SGRCodeFgYellow = 33,
AMR_SGRCodeFgBlue = 34,
AMR_SGRCodeFgMagenta = 35,
AMR_SGRCodeFgCyan = 36,
AMR_SGRCodeFgWhite = 37,
AMR_SGRCodeFgReset = 39,
AMR_SGRCodeBgBlack = 40,
AMR_SGRCodeBgRed = 41,
AMR_SGRCodeBgGreen = 42,
AMR_SGRCodeBgYellow = 43,
AMR_SGRCodeBgBlue = 44,
AMR_SGRCodeBgMagenta = 45,
AMR_SGRCodeBgCyan = 46,
AMR_SGRCodeBgWhite = 47,
AMR_SGRCodeBgReset = 49,
AMR_SGRCodeFgBrightBlack = 90,
AMR_SGRCodeFgBrightRed = 91,
AMR_SGRCodeFgBrightGreen = 92,
AMR_SGRCodeFgBrightYellow = 93,
AMR_SGRCodeFgBrightBlue = 94,
AMR_SGRCodeFgBrightMagenta = 95,
AMR_SGRCodeFgBrightCyan = 96,
AMR_SGRCodeFgBrightWhite = 97,
AMR_SGRCodeBgBrightBlack = 100,
AMR_SGRCodeBgBrightRed = 101,
AMR_SGRCodeBgBrightGreen = 102,
AMR_SGRCodeBgBrightYellow = 103,
AMR_SGRCodeBgBrightBlue = 104,
AMR_SGRCodeBgBrightMagenta = 105,
AMR_SGRCodeBgBrightCyan = 106,
AMR_SGRCodeBgBrightWhite = 107
} AMR_SGRCode;
/*!
@class AMR_ANSIEscapeHelper
@abstract Contains helper methods for dealing with strings
that contain ANSI escape sequences for formatting (colors,
underlining, bold etc.)
*/
@interface AMR_ANSIEscapeHelper : NSObject
/*!
@property defaultStringColor
@abstract The default color used when creating an attributed string (default is black).
*/
@property(copy) NSColor *defaultStringColor;
/*!
@property font
@abstract The font to use when creating string formatting attribute values.
*/
@property(copy) NSFont *font;
/*!
@property ansiColors
@abstract The colors to use for displaying ANSI colors.
@discussion Keys in this dictionary should be NSNumber objects containing SGR code
values from the AMR_SGRCode enum. The corresponding values for these keys
should be NSColor objects. If this property is nil or if it doesn't
contain a key for a specific SGR code, the default color will be used
instead.
*/
@property(retain) NSMutableDictionary *ansiColors;
/*!
@method attributedStringWithANSIEscapedString:
@abstract Returns an attributed string that corresponds both in contents
and formatting to a given string that contains ANSI escape
sequences.
@param aString A String containing ANSI escape sequences
@result An attributed string that mimics as closely as possible
the formatting of the given ANSI-escaped string.
*/
- (NSAttributedString*) attributedStringWithANSIEscapedString:(NSString*)aString;
/*!
@method ansiEscapedStringWithAttributedString:
@abstract Returns a string containing ANSI escape sequences that corresponds
both in contents and formatting to a given attributed string.
@param aAttributedString An attributed string
@result A string that mimics as closely as possible
the formatting of the given attributed string with
ANSI escape sequences.
*/
- (NSString*) ansiEscapedStringWithAttributedString:(NSAttributedString*)aAttributedString;
/*!
@method escapeCodesForString:cleanString:
@abstract Returns an array of SGR codes and their locations from a
string containing ANSI escape sequences as well as a "clean"
version of the string (i.e. one without the ANSI escape
sequences.)
@param aString A String containing ANSI escape sequences
@param aCleanString Upon return, contains a "clean" version of aString (i.e. aString
without the ANSI escape sequences)
@result An array of NSDictionary objects, each of which has
an NSNumber value for the key "code" (specifying an SGR code) and
another NSNumber value for the key "location" (specifying the
location of the code within aCleanString.)
*/
- (NSArray*) escapeCodesForString:(NSString*)aString cleanString:(NSString**)aCleanString;
/*!
@method ansiEscapedStringWithCodesAndLocations:cleanString:
@abstract Returns a string containing ANSI escape codes for formatting based
on a string and an array of SGR codes and their locations within
the given string.
@param aCodesArray An array of NSDictionary objects, each of which should have
an NSNumber value for the key "code" (specifying an SGR
code) and another NSNumber value for the key "location"
(specifying the location of this SGR code in aCleanString.)
@param aCleanString The string to which to insert the ANSI escape codes
described in aCodesArray.
@result A string containing ANSI escape sequences.
*/
- (NSString*) ansiEscapedStringWithCodesAndLocations:(NSArray*)aCodesArray cleanString:(NSString*)aCleanString;
/*!
@method attributesForString:cleanString:
@abstract Convert ANSI escape sequences in a string to string formatting attributes.
@discussion Given a string with some ANSI escape sequences in it, this method returns
attributes for formatting the specified string according to those ANSI
escape sequences as well as a "clean" (i.e. free of the escape sequences)
version of this string.
@param aString A String containing ANSI escape sequences
@param aCleanString Upon return, contains a "clean" version of aString (i.e. aString
without the ANSI escape sequences.) Pass in NULL if you're not
interested in this.
@result An array containing NSDictionary objects, each of which has keys "range"
(an NSValue containing an NSRange, specifying the range for the
attribute within the "clean" version of aString), "attributeName" (an
NSString) and "attributeValue" (an NSObject). You may use these as
arguments for NSMutableAttributedString's methods for setting the
visual formatting.
*/
- (NSArray*) attributesForString:(NSString*)aString cleanString:(NSString**)aCleanString;
/*!
@method AMR_SGRCode:endsFormattingIntroducedByCode:
@abstract Whether the occurrence of a given SGR code would end the formatting run
introduced by another SGR code.
@discussion For example, AMR_SGRCodeFgReset, AMR_SGRCodeAllReset or any SGR code
specifying a foreground color would end the formatting run
introduced by a foreground color -specifying SGR code.
@param endCode The SGR code to test as a candidate for ending the formatting run
introduced by startCode
@param startCode The SGR code that has introduced a formatting run
@result YES if the occurrence of endCode would end the formatting run
introduced by startCode, NO otherwise.
*/
- (BOOL) AMR_SGRCode:(AMR_SGRCode)endCode endsFormattingIntroducedByCode:(AMR_SGRCode)startCode;
/*!
@method colorForSGRCode:
@abstract Returns the color to use for displaying a specific ANSI color.
@discussion This method first considers the values set in the ansiColors
property and only then the standard basic colors (NSColor's
redColor, blueColor etc.)
@param code An SGR code that specifies an ANSI color.
@result The color to use for displaying the ANSI color specified by code.
*/
- (NSColor*) colorForSGRCode:(AMR_SGRCode)code;
/*!
@method AMR_SGRCodeForColor:isForegroundColor:
@abstract Returns a color SGR code that corresponds to a given color.
@discussion This method matches colors to their equivalent SGR codes
by going through the colors specified in the ansiColors
dictionary, and if ansiColors is null or if a match is
not found there, by comparing the given color to the
standard basic colors (NSColor's redColor, blueColor
etc.) The comparison is done simply by checking for
equality.
@param aColor The color to get a corresponding SGR code for
@param aForeground Whether you want a foreground or background color code
@result SGR code that corresponds with aColor.
*/
- (AMR_SGRCode) AMR_SGRCodeForColor:(NSColor*)aColor isForegroundColor:(BOOL)aForeground;
/*!
@method closestSGRCodeForColor:isForegroundColor:
@abstract Returns a color SGR code that represents the closest ANSI
color to a given color.
@discussion This method attempts to find the closest ANSI color to
aColor and return its SGR code.
@param color The color to get a closest color SGR code match for
@param foreground Whether you want a foreground or background color code
@result SGR code for the ANSI color that is closest to aColor.
*/
- (AMR_SGRCode) closestSGRCodeForColor:(NSColor *)color isForegroundColor:(BOOL)foreground;
@end

View File

@ -0,0 +1,996 @@
//
// ANSIEscapeHelper.m
//
// Created by Ali Rantakari on 18.3.09.
/*
The MIT License
Copyright (c) 2008-2009,2013 Ali Rantakari
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
/*
todo:
- don't add useless "reset" escape codes to the string in
-ansiEscapedStringWithAttributedString:
*/
#import "AMR_ANSIEscapeHelper.h"
// the CSI (Control Sequence Initiator) -- i.e. "escape sequence prefix".
// (add your own CSI:Miami joke here)
#define kANSIEscapeCSI @"\033["
// the end byte of an SGR (Select Graphic Rendition)
// ANSI Escape Sequence
#define kANSIEscapeSGREnd @"m"
// color definition helper macros
#define kBrightColorBrightness 1.0
#define kBrightColorSaturation 0.4
#define kBrightColorAlpha 1.0
#define kBrightColorWithHue(h) [NSColor colorWithCalibratedHue:(h) saturation:kBrightColorSaturation brightness:kBrightColorBrightness alpha:kBrightColorAlpha]
// default colors
#define kDefaultANSIColorFgBlack NSColor.blackColor
#define kDefaultANSIColorFgRed NSColor.redColor
#define kDefaultANSIColorFgGreen NSColor.greenColor
#define kDefaultANSIColorFgYellow NSColor.yellowColor
#define kDefaultANSIColorFgBlue NSColor.blueColor
#define kDefaultANSIColorFgMagenta NSColor.magentaColor
#define kDefaultANSIColorFgCyan NSColor.cyanColor
#define kDefaultANSIColorFgWhite NSColor.whiteColor
#define kDefaultANSIColorFgBrightBlack [NSColor colorWithCalibratedWhite:0.337 alpha:1.0]
#define kDefaultANSIColorFgBrightRed kBrightColorWithHue(1.0)
#define kDefaultANSIColorFgBrightGreen kBrightColorWithHue(1.0/3.0)
#define kDefaultANSIColorFgBrightYellow kBrightColorWithHue(1.0/6.0)
#define kDefaultANSIColorFgBrightBlue kBrightColorWithHue(2.0/3.0)
#define kDefaultANSIColorFgBrightMagenta kBrightColorWithHue(5.0/6.0)
#define kDefaultANSIColorFgBrightCyan kBrightColorWithHue(0.5)
#define kDefaultANSIColorFgBrightWhite NSColor.whiteColor
#define kDefaultANSIColorBgBlack NSColor.blackColor
#define kDefaultANSIColorBgRed NSColor.redColor
#define kDefaultANSIColorBgGreen NSColor.greenColor
#define kDefaultANSIColorBgYellow NSColor.yellowColor
#define kDefaultANSIColorBgBlue NSColor.blueColor
#define kDefaultANSIColorBgMagenta NSColor.magentaColor
#define kDefaultANSIColorBgCyan NSColor.cyanColor
#define kDefaultANSIColorBgWhite NSColor.whiteColor
#define kDefaultANSIColorBgBrightBlack kDefaultANSIColorFgBrightBlack
#define kDefaultANSIColorBgBrightRed kDefaultANSIColorFgBrightRed
#define kDefaultANSIColorBgBrightGreen kDefaultANSIColorFgBrightGreen
#define kDefaultANSIColorBgBrightYellow kDefaultANSIColorFgBrightYellow
#define kDefaultANSIColorBgBrightBlue kDefaultANSIColorFgBrightBlue
#define kDefaultANSIColorBgBrightMagenta kDefaultANSIColorFgBrightMagenta
#define kDefaultANSIColorBgBrightCyan kDefaultANSIColorFgBrightCyan
#define kDefaultANSIColorBgBrightWhite kDefaultANSIColorFgBrightWhite
#define kDefaultFontSize [NSFont systemFontOfSize:NSFont.systemFontSize]
#define kDefaultForegroundColor NSColor.blackColor
// minimum weight for an NSFont for it to be considered bold
#define kBoldFontMinWeight 9
@implementation AMR_ANSIEscapeHelper
- (id) init
{
if (!(self = [super init]))
return nil;
self.ansiColors = [NSMutableDictionary dictionary];
return self;
}
- (NSAttributedString*) attributedStringWithANSIEscapedString:(NSString*)aString
{
if (aString == nil)
return nil;
NSString *cleanString;
NSArray *attributesAndRanges = [self attributesForString:aString cleanString:&cleanString];
NSMutableAttributedString *attributedString = [[NSMutableAttributedString alloc]
initWithString:cleanString
attributes:@{
NSFontAttributeName: self.font ?: kDefaultFontSize,
NSForegroundColorAttributeName: self.defaultStringColor ?: kDefaultForegroundColor
}];
for (NSDictionary *thisAttributeDict in attributesAndRanges)
{
[attributedString
addAttribute:thisAttributeDict[kAMRAttrDictKey_attrName]
value:thisAttributeDict[kAMRAttrDictKey_attrValue]
range:[thisAttributeDict[kAMRAttrDictKey_range] rangeValue]
];
}
return attributedString;
}
- (NSString*) ansiEscapedStringWithAttributedString:(NSAttributedString*)aAttributedString
{
NSMutableArray *codesAndLocations = [NSMutableArray array];
NSArray *attrNames = @[
NSFontAttributeName, NSForegroundColorAttributeName,
NSBackgroundColorAttributeName, NSUnderlineStyleAttributeName,
];
for (NSString *thisAttrName in attrNames)
{
NSRange limitRange = NSMakeRange(0, aAttributedString.length);
id attributeValue;
NSRange effectiveRange;
while (limitRange.length > 0)
{
attributeValue = [aAttributedString
attribute:thisAttrName
atIndex:limitRange.location
longestEffectiveRange:&effectiveRange
inRange:limitRange
];
AMR_SGRCode thisSGRCode = AMR_SGRCodeNoneOrInvalid;
if ([thisAttrName isEqualToString:NSForegroundColorAttributeName])
{
if (attributeValue != nil)
thisSGRCode = [self closestSGRCodeForColor:attributeValue isForegroundColor:YES];
else
thisSGRCode = AMR_SGRCodeFgReset;
}
else if ([thisAttrName isEqualToString:NSBackgroundColorAttributeName])
{
if (attributeValue != nil)
thisSGRCode = [self closestSGRCodeForColor:attributeValue isForegroundColor:NO];
else
thisSGRCode = AMR_SGRCodeBgReset;
}
else if ([thisAttrName isEqualToString:NSFontAttributeName])
{
// we currently only use NSFontAttributeName for bolding so
// here we assume that the formatting "type" in ANSI SGR
// terms is indeed intensity
if (attributeValue != nil)
thisSGRCode = ([NSFontManager.sharedFontManager weightOfFont:attributeValue] >= kBoldFontMinWeight)
? AMR_SGRCodeIntensityBold : AMR_SGRCodeIntensityNormal;
else
thisSGRCode = AMR_SGRCodeIntensityNormal;
}
else if ([thisAttrName isEqualToString:NSUnderlineStyleAttributeName])
{
if (attributeValue != nil)
{
if ([attributeValue intValue] == NSUnderlineStyleSingle)
thisSGRCode = AMR_SGRCodeUnderlineSingle;
else if ([attributeValue intValue] == NSUnderlineStyleDouble)
thisSGRCode = AMR_SGRCodeUnderlineDouble;
else
thisSGRCode = AMR_SGRCodeUnderlineNone;
}
else
thisSGRCode = AMR_SGRCodeUnderlineNone;
}
if (thisSGRCode != AMR_SGRCodeNoneOrInvalid)
{
[codesAndLocations addObject: @{
kAMRCodeDictKey_code: @(thisSGRCode),
kAMRCodeDictKey_location: @(effectiveRange.location),
}];
}
limitRange = NSMakeRange(NSMaxRange(effectiveRange),
NSMaxRange(limitRange) - NSMaxRange(effectiveRange));
}
}
return [self ansiEscapedStringWithCodesAndLocations:codesAndLocations cleanString:aAttributedString.string];
}
- (NSArray*) escapeCodesForString:(NSString*)aString cleanString:(NSString**)aCleanString
{
if (aString == nil)
return nil;
if (aString.length <= kANSIEscapeCSI.length)
{
if (aCleanString)
*aCleanString = aString.copy;
return @[];
}
NSString *cleanString = @"";
// find all escape sequence codes from aString and put them in this array
// along with their start locations within the "clean" version of aString
NSMutableArray *formatCodes = [NSMutableArray array];
NSUInteger aStringLength = aString.length;
NSUInteger coveredLength = 0;
NSRange searchRange = NSMakeRange(0,aStringLength);
NSRange thisEscapeSequenceRange;
do
{
thisEscapeSequenceRange = [aString rangeOfString:kANSIEscapeCSI options:NSLiteralSearch range:searchRange];
if (thisEscapeSequenceRange.location != NSNotFound)
{
// adjust range's length so that it encompasses the whole ANSI escape sequence
// and not just the Control Sequence Initiator (the "prefix") by finding the
// final byte of the control sequence (one that has an ASCII decimal value
// between 64 and 126.) at the same time, read all formatting codes from inside
// this escape sequence (there may be several, separated by semicolons.)
NSMutableArray *codes = [NSMutableArray array];
unsigned int code = 0;
unsigned int lengthAddition = 1;
NSUInteger thisIndex;
for (;;)
{
thisIndex = (NSMaxRange(thisEscapeSequenceRange)+lengthAddition-1);
if (thisIndex >= aStringLength)
break;
unichar c = [aString characterAtIndex:thisIndex];
if (('0' <= c) && (c <= '9'))
{
int digit = c - '0';
code = (code == 0) ? digit : code*10+digit;
}
// ASCII decimal 109 is the SGR (Select Graphic Rendition) final byte
// ("m"). this means that the code value we've just read specifies formatting
// for the output; exactly what we're interested in.
if (c == 'm')
{
[codes addObject:@(code)];
break;
}
else if ((64 <= c) && (c <= 126)) // any other valid final byte
{
[codes removeAllObjects];
break;
}
else if (c == ';') // separates codes within the same sequence
{
[codes addObject:@(code)];
code = 0;
}
lengthAddition++;
}
thisEscapeSequenceRange.length += lengthAddition;
NSUInteger locationInCleanString = coveredLength+thisEscapeSequenceRange.location-searchRange.location;
for (NSNumber *codeToAdd in codes)
{
[formatCodes addObject: @{
kAMRCodeDictKey_code: codeToAdd,
kAMRCodeDictKey_location: @(locationInCleanString)
}];
}
NSUInteger thisCoveredLength = thisEscapeSequenceRange.location-searchRange.location;
if (thisCoveredLength > 0)
cleanString = [cleanString stringByAppendingString:[aString substringWithRange:NSMakeRange(searchRange.location, thisCoveredLength)]];
coveredLength += thisCoveredLength;
searchRange.location = NSMaxRange(thisEscapeSequenceRange);
searchRange.length = aStringLength-searchRange.location;
}
}
while(thisEscapeSequenceRange.location != NSNotFound);
if (searchRange.length > 0)
cleanString = [cleanString stringByAppendingString:[aString substringWithRange:searchRange]];
if (aCleanString)
*aCleanString = cleanString;
return formatCodes;
}
- (NSString*) ansiEscapedStringWithCodesAndLocations:(NSArray*)aCodesArray cleanString:(NSString*)aCleanString
{
NSMutableString* retStr = [NSMutableString stringWithCapacity:aCleanString.length];
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:kAMRCodeDictKey_location ascending:YES];
NSArray *codesArray = [aCodesArray sortedArrayUsingDescriptors:@[sortDescriptor]];
NSUInteger aCleanStringIndex = 0;
NSUInteger aCleanStringLength = aCleanString.length;
for (NSDictionary *thisCodeDict in codesArray)
{
if (!( thisCodeDict[kAMRCodeDictKey_code] &&
thisCodeDict[kAMRCodeDictKey_location]
))
continue;
AMR_SGRCode thisCode = [thisCodeDict[kAMRCodeDictKey_code] unsignedIntValue];
NSUInteger formattingRunStartLocation = [thisCodeDict[kAMRCodeDictKey_location] unsignedIntegerValue];
if (formattingRunStartLocation > aCleanStringLength)
continue;
if (aCleanStringIndex < formattingRunStartLocation)
[retStr appendString:[aCleanString substringWithRange:NSMakeRange(aCleanStringIndex, formattingRunStartLocation-aCleanStringIndex)]];
[retStr appendFormat:@"%@%d%@", kANSIEscapeCSI, thisCode, kANSIEscapeSGREnd];
aCleanStringIndex = formattingRunStartLocation;
}
if (aCleanStringIndex < aCleanStringLength)
[retStr appendString:[aCleanString substringFromIndex:aCleanStringIndex]];
[retStr appendFormat:@"%@%d%@", kANSIEscapeCSI, AMR_SGRCodeAllReset, kANSIEscapeSGREnd];
return retStr;
}
- (NSArray*) attributesForString:(NSString*)aString cleanString:(NSString**)aCleanString
{
if (aString == nil)
return nil;
if (aString.length <= kANSIEscapeCSI.length)
{
if (aCleanString)
*aCleanString = aString.copy;
return @[];
}
NSMutableArray *attrsAndRanges = [NSMutableArray array];
NSString *cleanString;
NSArray *formatCodes = [self escapeCodesForString:aString cleanString:&cleanString];
// go through all the found escape sequence codes and for each one, create
// the string formatting attribute name and value, find the next escape
// sequence that specifies the end of the formatting run started by
// the currently handled code, and generate a range from the difference
// in those codes' locations within the clean aString.
for (NSUInteger iCode = 0; iCode < formatCodes.count; iCode++)
{
NSDictionary *thisCodeDict = formatCodes[iCode];
AMR_SGRCode thisCode = [thisCodeDict[kAMRCodeDictKey_code] unsignedIntValue];
NSUInteger formattingRunStartLocation = [thisCodeDict[kAMRCodeDictKey_location] unsignedIntegerValue];
// the attributed string attribute name for the formatting run introduced
// by this code
NSString *thisAttributeName = nil;
// the attributed string attribute value for this formatting run introduced
// by this code
NSObject *thisAttributeValue = nil;
// set attribute name
switch(thisCode)
{
case AMR_SGRCodeFgBlack:
case AMR_SGRCodeFgRed:
case AMR_SGRCodeFgGreen:
case AMR_SGRCodeFgYellow:
case AMR_SGRCodeFgBlue:
case AMR_SGRCodeFgMagenta:
case AMR_SGRCodeFgCyan:
case AMR_SGRCodeFgWhite:
case AMR_SGRCodeFgBrightBlack:
case AMR_SGRCodeFgBrightRed:
case AMR_SGRCodeFgBrightGreen:
case AMR_SGRCodeFgBrightYellow:
case AMR_SGRCodeFgBrightBlue:
case AMR_SGRCodeFgBrightMagenta:
case AMR_SGRCodeFgBrightCyan:
case AMR_SGRCodeFgBrightWhite:
thisAttributeName = NSForegroundColorAttributeName;
break;
case AMR_SGRCodeBgBlack:
case AMR_SGRCodeBgRed:
case AMR_SGRCodeBgGreen:
case AMR_SGRCodeBgYellow:
case AMR_SGRCodeBgBlue:
case AMR_SGRCodeBgMagenta:
case AMR_SGRCodeBgCyan:
case AMR_SGRCodeBgWhite:
case AMR_SGRCodeBgBrightBlack:
case AMR_SGRCodeBgBrightRed:
case AMR_SGRCodeBgBrightGreen:
case AMR_SGRCodeBgBrightYellow:
case AMR_SGRCodeBgBrightBlue:
case AMR_SGRCodeBgBrightMagenta:
case AMR_SGRCodeBgBrightCyan:
case AMR_SGRCodeBgBrightWhite:
thisAttributeName = NSBackgroundColorAttributeName;
break;
case AMR_SGRCodeIntensityBold:
case AMR_SGRCodeIntensityNormal:
case AMR_SGRCodeIntensityFaint:
thisAttributeName = NSFontAttributeName;
break;
case AMR_SGRCodeUnderlineSingle:
case AMR_SGRCodeUnderlineDouble:
case AMR_SGRCodeUnderlineNone:
thisAttributeName = NSUnderlineStyleAttributeName;
break;
case AMR_SGRCodeAllReset:
case AMR_SGRCodeFgReset:
case AMR_SGRCodeBgReset:
case AMR_SGRCodeNoneOrInvalid:
case AMR_SGRCodeItalicOn:
continue;
}
// set attribute value
switch(thisCode)
{
case AMR_SGRCodeBgBlack:
case AMR_SGRCodeFgBlack:
case AMR_SGRCodeBgRed:
case AMR_SGRCodeFgRed:
case AMR_SGRCodeBgGreen:
case AMR_SGRCodeFgGreen:
case AMR_SGRCodeBgYellow:
case AMR_SGRCodeFgYellow:
case AMR_SGRCodeBgBlue:
case AMR_SGRCodeFgBlue:
case AMR_SGRCodeBgMagenta:
case AMR_SGRCodeFgMagenta:
case AMR_SGRCodeBgCyan:
case AMR_SGRCodeFgCyan:
case AMR_SGRCodeBgWhite:
case AMR_SGRCodeFgWhite:
case AMR_SGRCodeBgBrightBlack:
case AMR_SGRCodeFgBrightBlack:
case AMR_SGRCodeBgBrightRed:
case AMR_SGRCodeFgBrightRed:
case AMR_SGRCodeBgBrightGreen:
case AMR_SGRCodeFgBrightGreen:
case AMR_SGRCodeBgBrightYellow:
case AMR_SGRCodeFgBrightYellow:
case AMR_SGRCodeBgBrightBlue:
case AMR_SGRCodeFgBrightBlue:
case AMR_SGRCodeBgBrightMagenta:
case AMR_SGRCodeFgBrightMagenta:
case AMR_SGRCodeBgBrightCyan:
case AMR_SGRCodeFgBrightCyan:
case AMR_SGRCodeBgBrightWhite:
case AMR_SGRCodeFgBrightWhite:
thisAttributeValue = [self colorForSGRCode:thisCode];
break;
case AMR_SGRCodeIntensityBold:
{
NSFont *boldFont = [NSFontManager.sharedFontManager convertFont:self.font toHaveTrait:NSBoldFontMask];
thisAttributeValue = boldFont;
}
break;
case AMR_SGRCodeIntensityNormal:
case AMR_SGRCodeIntensityFaint:
{
NSFont *unboldFont = [NSFontManager.sharedFontManager convertFont:self.font toHaveTrait:NSUnboldFontMask];
thisAttributeValue = unboldFont;
}
break;
case AMR_SGRCodeUnderlineSingle:
thisAttributeValue = @(NSUnderlineStyleSingle);
break;
case AMR_SGRCodeUnderlineDouble:
thisAttributeValue = @(NSUnderlineStyleDouble);
break;
case AMR_SGRCodeUnderlineNone:
thisAttributeValue = @(NSUnderlineStyleNone);
break;
case AMR_SGRCodeAllReset:
case AMR_SGRCodeFgReset:
case AMR_SGRCodeBgReset:
case AMR_SGRCodeNoneOrInvalid:
case AMR_SGRCodeItalicOn:
break;
}
// find the next sequence that specifies the end of this formatting run
NSInteger formattingRunEndLocation = -1;
if (iCode < (formatCodes.count - 1))
{
NSDictionary *thisEndCodeCandidateDict;
unichar thisEndCodeCandidate;
for (NSUInteger iEndCode = iCode+1; iEndCode < formatCodes.count; iEndCode++)
{
thisEndCodeCandidateDict = formatCodes[iEndCode];
thisEndCodeCandidate = [thisEndCodeCandidateDict[kAMRCodeDictKey_code] unsignedIntValue];
if ([self AMR_SGRCode:thisEndCodeCandidate endsFormattingIntroducedByCode:thisCode])
{
formattingRunEndLocation = [thisEndCodeCandidateDict[kAMRCodeDictKey_location] unsignedIntegerValue];
break;
}
}
}
if (formattingRunEndLocation == -1)
formattingRunEndLocation = cleanString.length;
if (thisAttributeName && thisAttributeValue)
{
[attrsAndRanges addObject:@{
kAMRAttrDictKey_range: [NSValue valueWithRange:NSMakeRange(formattingRunStartLocation, (formattingRunEndLocation-formattingRunStartLocation))],
kAMRAttrDictKey_attrName: thisAttributeName,
kAMRAttrDictKey_attrValue: thisAttributeValue,
}];
}
}
if (aCleanString)
*aCleanString = cleanString;
return attrsAndRanges;
}
- (BOOL) AMR_SGRCode:(AMR_SGRCode)endCode endsFormattingIntroducedByCode:(AMR_SGRCode)startCode
{
switch(startCode)
{
case AMR_SGRCodeFgBlack:
case AMR_SGRCodeFgRed:
case AMR_SGRCodeFgGreen:
case AMR_SGRCodeFgYellow:
case AMR_SGRCodeFgBlue:
case AMR_SGRCodeFgMagenta:
case AMR_SGRCodeFgCyan:
case AMR_SGRCodeFgWhite:
case AMR_SGRCodeFgBrightBlack:
case AMR_SGRCodeFgBrightRed:
case AMR_SGRCodeFgBrightGreen:
case AMR_SGRCodeFgBrightYellow:
case AMR_SGRCodeFgBrightBlue:
case AMR_SGRCodeFgBrightMagenta:
case AMR_SGRCodeFgBrightCyan:
case AMR_SGRCodeFgBrightWhite:
return (endCode == AMR_SGRCodeAllReset || endCode == AMR_SGRCodeFgReset ||
endCode == AMR_SGRCodeFgBlack || endCode == AMR_SGRCodeFgRed ||
endCode == AMR_SGRCodeFgGreen || endCode == AMR_SGRCodeFgYellow ||
endCode == AMR_SGRCodeFgBlue || endCode == AMR_SGRCodeFgMagenta ||
endCode == AMR_SGRCodeFgCyan || endCode == AMR_SGRCodeFgWhite ||
endCode == AMR_SGRCodeFgBrightBlack || endCode == AMR_SGRCodeFgBrightRed ||
endCode == AMR_SGRCodeFgBrightGreen || endCode == AMR_SGRCodeFgBrightYellow ||
endCode == AMR_SGRCodeFgBrightBlue || endCode == AMR_SGRCodeFgBrightMagenta ||
endCode == AMR_SGRCodeFgBrightCyan || endCode == AMR_SGRCodeFgBrightWhite);
case AMR_SGRCodeBgBlack:
case AMR_SGRCodeBgRed:
case AMR_SGRCodeBgGreen:
case AMR_SGRCodeBgYellow:
case AMR_SGRCodeBgBlue:
case AMR_SGRCodeBgMagenta:
case AMR_SGRCodeBgCyan:
case AMR_SGRCodeBgWhite:
case AMR_SGRCodeBgBrightBlack:
case AMR_SGRCodeBgBrightRed:
case AMR_SGRCodeBgBrightGreen:
case AMR_SGRCodeBgBrightYellow:
case AMR_SGRCodeBgBrightBlue:
case AMR_SGRCodeBgBrightMagenta:
case AMR_SGRCodeBgBrightCyan:
case AMR_SGRCodeBgBrightWhite:
return (endCode == AMR_SGRCodeAllReset || endCode == AMR_SGRCodeBgReset ||
endCode == AMR_SGRCodeBgBlack || endCode == AMR_SGRCodeBgRed ||
endCode == AMR_SGRCodeBgGreen || endCode == AMR_SGRCodeBgYellow ||
endCode == AMR_SGRCodeBgBlue || endCode == AMR_SGRCodeBgMagenta ||
endCode == AMR_SGRCodeBgCyan || endCode == AMR_SGRCodeBgWhite ||
endCode == AMR_SGRCodeBgBrightBlack || endCode == AMR_SGRCodeBgBrightRed ||
endCode == AMR_SGRCodeBgBrightGreen || endCode == AMR_SGRCodeBgBrightYellow ||
endCode == AMR_SGRCodeBgBrightBlue || endCode == AMR_SGRCodeBgBrightMagenta ||
endCode == AMR_SGRCodeBgBrightCyan || endCode == AMR_SGRCodeBgBrightWhite);
case AMR_SGRCodeIntensityBold:
case AMR_SGRCodeIntensityNormal:
return (endCode == AMR_SGRCodeAllReset || endCode == AMR_SGRCodeIntensityNormal ||
endCode == AMR_SGRCodeIntensityBold || endCode == AMR_SGRCodeIntensityFaint);
case AMR_SGRCodeUnderlineSingle:
case AMR_SGRCodeUnderlineDouble:
return (endCode == AMR_SGRCodeAllReset || endCode == AMR_SGRCodeUnderlineNone ||
endCode == AMR_SGRCodeUnderlineSingle || endCode == AMR_SGRCodeUnderlineDouble);
case AMR_SGRCodeNoneOrInvalid:
case AMR_SGRCodeItalicOn:
case AMR_SGRCodeUnderlineNone:
case AMR_SGRCodeIntensityFaint:
case AMR_SGRCodeAllReset:
case AMR_SGRCodeBgReset:
case AMR_SGRCodeFgReset:
return NO;
}
return NO;
}
- (NSColor*) colorForSGRCode:(AMR_SGRCode)code
{
if (self.ansiColors)
{
NSColor *preferredColor = self.ansiColors[@(code)];
if (preferredColor)
return preferredColor;
}
switch(code)
{
case AMR_SGRCodeFgBlack:
return kDefaultANSIColorFgBlack;
case AMR_SGRCodeFgRed:
return kDefaultANSIColorFgRed;
case AMR_SGRCodeFgGreen:
return kDefaultANSIColorFgGreen;
case AMR_SGRCodeFgYellow:
return kDefaultANSIColorFgYellow;
case AMR_SGRCodeFgBlue:
return kDefaultANSIColorFgBlue;
case AMR_SGRCodeFgMagenta:
return kDefaultANSIColorFgMagenta;
case AMR_SGRCodeFgCyan:
return kDefaultANSIColorFgCyan;
case AMR_SGRCodeFgWhite:
return kDefaultANSIColorFgWhite;
case AMR_SGRCodeFgBrightBlack:
return kDefaultANSIColorFgBrightBlack;
case AMR_SGRCodeFgBrightRed:
return kDefaultANSIColorFgBrightRed;
case AMR_SGRCodeFgBrightGreen:
return kDefaultANSIColorFgBrightGreen;
case AMR_SGRCodeFgBrightYellow:
return kDefaultANSIColorFgBrightYellow;
case AMR_SGRCodeFgBrightBlue:
return kDefaultANSIColorFgBrightBlue;
case AMR_SGRCodeFgBrightMagenta:
return kDefaultANSIColorFgBrightMagenta;
case AMR_SGRCodeFgBrightCyan:
return kDefaultANSIColorFgBrightCyan;
case AMR_SGRCodeFgBrightWhite:
return kDefaultANSIColorFgBrightWhite;
case AMR_SGRCodeBgBlack:
return kDefaultANSIColorBgBlack;
case AMR_SGRCodeBgRed:
return kDefaultANSIColorBgRed;
case AMR_SGRCodeBgGreen:
return kDefaultANSIColorBgGreen;
case AMR_SGRCodeBgYellow:
return kDefaultANSIColorBgYellow;
case AMR_SGRCodeBgBlue:
return kDefaultANSIColorBgBlue;
case AMR_SGRCodeBgMagenta:
return kDefaultANSIColorBgMagenta;
case AMR_SGRCodeBgCyan:
return kDefaultANSIColorBgCyan;
case AMR_SGRCodeBgWhite:
return kDefaultANSIColorBgWhite;
case AMR_SGRCodeBgBrightBlack:
return kDefaultANSIColorBgBrightBlack;
case AMR_SGRCodeBgBrightRed:
return kDefaultANSIColorBgBrightRed;
case AMR_SGRCodeBgBrightGreen:
return kDefaultANSIColorBgBrightGreen;
case AMR_SGRCodeBgBrightYellow:
return kDefaultANSIColorBgBrightYellow;
case AMR_SGRCodeBgBrightBlue:
return kDefaultANSIColorBgBrightBlue;
case AMR_SGRCodeBgBrightMagenta:
return kDefaultANSIColorBgBrightMagenta;
case AMR_SGRCodeBgBrightCyan:
return kDefaultANSIColorBgBrightCyan;
case AMR_SGRCodeBgBrightWhite:
return kDefaultANSIColorBgBrightWhite;
case AMR_SGRCodeNoneOrInvalid:
case AMR_SGRCodeItalicOn:
case AMR_SGRCodeUnderlineNone:
case AMR_SGRCodeIntensityFaint:
case AMR_SGRCodeAllReset:
case AMR_SGRCodeBgReset:
case AMR_SGRCodeFgReset:
case AMR_SGRCodeIntensityBold:
case AMR_SGRCodeIntensityNormal:
case AMR_SGRCodeUnderlineSingle:
case AMR_SGRCodeUnderlineDouble:
break;
}
return kDefaultANSIColorFgBlack;
}
- (AMR_SGRCode) AMR_SGRCodeForColor:(NSColor*)aColor isForegroundColor:(BOOL)aForeground
{
if (self.ansiColors)
{
NSArray *codesForGivenColor = [self.ansiColors allKeysForObject:aColor];
if (codesForGivenColor != nil && 0 < codesForGivenColor.count)
{
for (NSNumber *thisCode in codesForGivenColor)
{
BOOL thisIsForegroundColor = (thisCode.intValue < 40);
if (aForeground == thisIsForegroundColor)
return thisCode.intValue;
}
}
}
if (aForeground)
{
if ([aColor isEqual:kDefaultANSIColorFgBlack])
return AMR_SGRCodeFgBlack;
else if ([aColor isEqual:kDefaultANSIColorFgRed])
return AMR_SGRCodeFgRed;
else if ([aColor isEqual:kDefaultANSIColorFgGreen])
return AMR_SGRCodeFgGreen;
else if ([aColor isEqual:kDefaultANSIColorFgYellow])
return AMR_SGRCodeFgYellow;
else if ([aColor isEqual:kDefaultANSIColorFgBlue])
return AMR_SGRCodeFgBlue;
else if ([aColor isEqual:kDefaultANSIColorFgMagenta])
return AMR_SGRCodeFgMagenta;
else if ([aColor isEqual:kDefaultANSIColorFgCyan])
return AMR_SGRCodeFgCyan;
else if ([aColor isEqual:kDefaultANSIColorFgWhite])
return AMR_SGRCodeFgWhite;
else if ([aColor isEqual:kDefaultANSIColorFgBrightBlack])
return AMR_SGRCodeFgBrightBlack;
else if ([aColor isEqual:kDefaultANSIColorFgBrightRed])
return AMR_SGRCodeFgBrightRed;
else if ([aColor isEqual:kDefaultANSIColorFgBrightGreen])
return AMR_SGRCodeFgBrightGreen;
else if ([aColor isEqual:kDefaultANSIColorFgBrightYellow])
return AMR_SGRCodeFgBrightYellow;
else if ([aColor isEqual:kDefaultANSIColorFgBrightBlue])
return AMR_SGRCodeFgBrightBlue;
else if ([aColor isEqual:kDefaultANSIColorFgBrightMagenta])
return AMR_SGRCodeFgBrightMagenta;
else if ([aColor isEqual:kDefaultANSIColorFgBrightCyan])
return AMR_SGRCodeFgBrightCyan;
else if ([aColor isEqual:kDefaultANSIColorFgBrightWhite])
return AMR_SGRCodeFgBrightWhite;
}
else
{
if ([aColor isEqual:kDefaultANSIColorBgBlack])
return AMR_SGRCodeBgBlack;
else if ([aColor isEqual:kDefaultANSIColorBgRed])
return AMR_SGRCodeBgRed;
else if ([aColor isEqual:kDefaultANSIColorBgGreen])
return AMR_SGRCodeBgGreen;
else if ([aColor isEqual:kDefaultANSIColorBgYellow])
return AMR_SGRCodeBgYellow;
else if ([aColor isEqual:kDefaultANSIColorBgBlue])
return AMR_SGRCodeBgBlue;
else if ([aColor isEqual:kDefaultANSIColorBgMagenta])
return AMR_SGRCodeBgMagenta;
else if ([aColor isEqual:kDefaultANSIColorBgCyan])
return AMR_SGRCodeBgCyan;
else if ([aColor isEqual:kDefaultANSIColorBgWhite])
return AMR_SGRCodeBgWhite;
else if ([aColor isEqual:kDefaultANSIColorBgBrightBlack])
return AMR_SGRCodeBgBrightBlack;
else if ([aColor isEqual:kDefaultANSIColorBgBrightRed])
return AMR_SGRCodeBgBrightRed;
else if ([aColor isEqual:kDefaultANSIColorBgBrightGreen])
return AMR_SGRCodeBgBrightGreen;
else if ([aColor isEqual:kDefaultANSIColorBgBrightYellow])
return AMR_SGRCodeBgBrightYellow;
else if ([aColor isEqual:kDefaultANSIColorBgBrightBlue])
return AMR_SGRCodeBgBrightBlue;
else if ([aColor isEqual:kDefaultANSIColorBgBrightMagenta])
return AMR_SGRCodeBgBrightMagenta;
else if ([aColor isEqual:kDefaultANSIColorBgBrightCyan])
return AMR_SGRCodeBgBrightCyan;
else if ([aColor isEqual:kDefaultANSIColorBgBrightWhite])
return AMR_SGRCodeBgBrightWhite;
}
return AMR_SGRCodeNoneOrInvalid;
}
// helper struct typedef and a few functions for
// -closestSGRCodeForColor:isForegroundColor:
typedef struct {
CGFloat hue;
CGFloat saturation;
CGFloat brightness;
} AMR_HSB;
AMR_HSB makeHSB(CGFloat hue, CGFloat saturation, CGFloat brightness)
{
AMR_HSB outHSB;
outHSB.hue = hue;
outHSB.saturation = saturation;
outHSB.brightness = brightness;
return outHSB;
}
AMR_HSB getHSBFromColor(NSColor *color)
{
CGFloat hue = 0.0;
CGFloat saturation = 0.0;
CGFloat brightness = 0.0;
[[color colorUsingColorSpaceName:NSCalibratedRGBColorSpace]
getHue:&hue
saturation:&saturation
brightness:&brightness
alpha:NULL
];
return makeHSB(hue, saturation, brightness);
}
BOOL floatsEqual(CGFloat first, CGFloat second, CGFloat maxAbsError)
{
return (fabs(first-second)) < maxAbsError;
}
#define MAX_HUE_FLOAT_EQUALITY_ABS_ERROR 0.000001
- (AMR_SGRCode) closestSGRCodeForColor:(NSColor *)color isForegroundColor:(BOOL)foreground
{
if (color == nil)
return AMR_SGRCodeNoneOrInvalid;
AMR_SGRCode closestColorSGRCode = [self AMR_SGRCodeForColor:color isForegroundColor:foreground];
if (closestColorSGRCode != AMR_SGRCodeNoneOrInvalid)
return closestColorSGRCode;
AMR_HSB givenColorHSB = getHSBFromColor(color);
CGFloat closestColorHueDiff = FLT_MAX;
CGFloat closestColorSaturationDiff = FLT_MAX;
CGFloat closestColorBrightnessDiff = FLT_MAX;
// (background SGR codes are +10 from foreground ones:)
NSUInteger AMR_SGRCodeShift = (foreground)?0:10;
NSArray *ansiFgColorCodes = @[
@(AMR_SGRCodeFgBlack+AMR_SGRCodeShift),
@(AMR_SGRCodeFgRed+AMR_SGRCodeShift),
@(AMR_SGRCodeFgGreen+AMR_SGRCodeShift),
@(AMR_SGRCodeFgYellow+AMR_SGRCodeShift),
@(AMR_SGRCodeFgBlue+AMR_SGRCodeShift),
@(AMR_SGRCodeFgMagenta+AMR_SGRCodeShift),
@(AMR_SGRCodeFgCyan+AMR_SGRCodeShift),
@(AMR_SGRCodeFgWhite+AMR_SGRCodeShift),
@(AMR_SGRCodeFgBrightBlack+AMR_SGRCodeShift),
@(AMR_SGRCodeFgBrightRed+AMR_SGRCodeShift),
@(AMR_SGRCodeFgBrightGreen+AMR_SGRCodeShift),
@(AMR_SGRCodeFgBrightYellow+AMR_SGRCodeShift),
@(AMR_SGRCodeFgBrightBlue+AMR_SGRCodeShift),
@(AMR_SGRCodeFgBrightMagenta+AMR_SGRCodeShift),
@(AMR_SGRCodeFgBrightCyan+AMR_SGRCodeShift),
@(AMR_SGRCodeFgBrightWhite+AMR_SGRCodeShift),
];
for (NSNumber *thisSGRCodeNumber in ansiFgColorCodes)
{
AMR_SGRCode thisSGRCode = thisSGRCodeNumber.intValue;
NSColor *thisColor = [self colorForSGRCode:thisSGRCode];
AMR_HSB thisColorHSB = getHSBFromColor(thisColor);
CGFloat hueDiff = fabs(givenColorHSB.hue - thisColorHSB.hue);
CGFloat saturationDiff = fabs(givenColorHSB.saturation - thisColorHSB.saturation);
CGFloat brightnessDiff = fabs(givenColorHSB.brightness - thisColorHSB.brightness);
// comparison depends on hue, saturation and brightness
// (strictly in that order):
if (!floatsEqual(hueDiff, closestColorHueDiff, MAX_HUE_FLOAT_EQUALITY_ABS_ERROR))
{
if (hueDiff > closestColorHueDiff)
continue;
closestColorSGRCode = thisSGRCode;
closestColorHueDiff = hueDiff;
closestColorSaturationDiff = saturationDiff;
closestColorBrightnessDiff = brightnessDiff;
continue;
}
if (!floatsEqual(saturationDiff, closestColorSaturationDiff, MAX_HUE_FLOAT_EQUALITY_ABS_ERROR))
{
if (saturationDiff > closestColorSaturationDiff)
continue;
closestColorSGRCode = thisSGRCode;
closestColorHueDiff = hueDiff;
closestColorSaturationDiff = saturationDiff;
closestColorBrightnessDiff = brightnessDiff;
continue;
}
if (!floatsEqual(brightnessDiff, closestColorBrightnessDiff, MAX_HUE_FLOAT_EQUALITY_ABS_ERROR))
{
if (brightnessDiff > closestColorBrightnessDiff)
continue;
closestColorSGRCode = thisSGRCode;
closestColorHueDiff = hueDiff;
closestColorSaturationDiff = saturationDiff;
closestColorBrightnessDiff = brightnessDiff;
continue;
}
// If hue (especially hue!), saturation and brightness diffs all
// are equal to some other color, we need to prefer one or the
// other so we'll select the more 'distinctive' color of the
// two (this is *very* subjective, obviously). I basically just
// looked at the hue chart, went through all the points between
// our main ANSI colors and decided which side the middle point
// would lean on. (e.g. the purple color that is exactly between
// the blue and magenta ANSI colors looks more magenta than
// blue to me so I put magenta higher than blue in the list
// below.)
//
// subjective ordering of colors from most to least 'distinctive':
long colorDistinctivenessOrder[6] = {
AMR_SGRCodeFgRed+AMR_SGRCodeShift,
AMR_SGRCodeFgMagenta+AMR_SGRCodeShift,
AMR_SGRCodeFgBlue+AMR_SGRCodeShift,
AMR_SGRCodeFgGreen+AMR_SGRCodeShift,
AMR_SGRCodeFgCyan+AMR_SGRCodeShift,
AMR_SGRCodeFgYellow+AMR_SGRCodeShift
};
for (int i = 0; i < 6; i++)
{
if (colorDistinctivenessOrder[i] == closestColorSGRCode)
break;
else if (colorDistinctivenessOrder[i] == thisSGRCode)
{
closestColorSGRCode = thisSGRCode;
closestColorHueDiff = hueDiff;
closestColorSaturationDiff = saturationDiff;
closestColorBrightnessDiff = brightnessDiff;
}
}
}
return closestColorSGRCode;
}
@end

View File

@ -6,6 +6,7 @@
// Copyright © 2018 Anton Palgunov. All rights reserved.
//
#import "AMR_ANSIEscapeHelper.h"
#import "TouchBarPrivateApi.h"
#import "TouchBarSupport.h"
#import "DeprecatedCarbonAPI.h"

View File

@ -85,6 +85,7 @@ class CustomButtonTouchBarItem: NSCustomTouchBarItem, NSGestureRecognizerDelegat
if let color = backgroundColor {
cell.isBordered = true
button.bezelColor = color
button.bezelStyle = .rounded
cell.backgroundColor = color
} else {
button.isBordered = isBordered
@ -158,6 +159,10 @@ class CustomButtonCell: NSButtonCell {
}
}
override func drawingRect(forBounds rect: NSRect) -> NSRect {
return rect // need that so content may better fit in button with very limited width
}
required init(coder _: NSCoder) {
fatalError("init(coder:) has not been implemented")
}

View File

@ -343,6 +343,7 @@ class SupportedTypesHolder {
enum ItemType: Decodable {
case staticButton(title: String)
case appleScriptTitledButton(source: SourceProtocol, refreshInterval: Double)
case shellScriptTitledButton(source: SourceProtocol, refreshInterval: Double)
case timeButton(formatTemplate: String, timeZone: String?)
case battery()
case dock(autoResize: Bool)
@ -387,6 +388,7 @@ enum ItemType: Decodable {
enum ItemTypeRaw: String, Decodable {
case staticButton
case appleScriptTitledButton
case shellScriptTitledButton
case timeButton
case battery
case dock
@ -414,6 +416,11 @@ enum ItemType: Decodable {
let interval = try container.decodeIfPresent(Double.self, forKey: .refreshInterval) ?? 1800.0
self = .appleScriptTitledButton(source: source, refreshInterval: interval)
case .shellScriptTitledButton:
let source = try container.decode(Source.self, forKey: .source)
let interval = try container.decodeIfPresent(Double.self, forKey: .refreshInterval) ?? 1800.0
self = .shellScriptTitledButton(source: source, refreshInterval: interval)
case .staticButton:
let title = try container.decode(String.self, forKey: .title)
self = .staticButton(title: title)

View File

@ -0,0 +1,74 @@
//
// ShellScriptTouchBarItem.swift
// MTMR
//
// Created by bobr on 08/08/2019.
// Copyright © 2019 Anton Palgunov. All rights reserved.
//
import Foundation
class ShellScriptTouchBarItem: CustomButtonTouchBarItem {
private let interval: TimeInterval
private let source: String
private var forceHideConstraint: NSLayoutConstraint!
init?(identifier: NSTouchBarItem.Identifier, source: SourceProtocol, interval: TimeInterval) {
self.interval = interval
self.source = source.string ?? "echo No \"source\""
super.init(identifier: identifier, title: "")
forceHideConstraint = view.widthAnchor.constraint(equalToConstant: 0)
DispatchQueue.shellScriptQueue.async {
self.refreshAndSchedule()
}
}
required init?(coder _: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func refreshAndSchedule() {
// Execute script and get result
let scriptResult = execute(source)
// Apply returned text attributes (if they were returned) to our result string
let helper = AMR_ANSIEscapeHelper.init()
helper.defaultStringColor = NSColor.white
helper.font = "1".defaultTouchbarAttributedString.attribute(.font, at: 0, effectiveRange: nil) as? NSFont
let title = NSMutableAttributedString.init(attributedString: helper.attributedString(withANSIEscapedString: scriptResult) ?? NSAttributedString(string: ""))
title.addAttributes([.baselineOffset: 1], range: NSRange(location: 0, length: title.length))
let newBackgoundColor = title.attribute(.backgroundColor, at: 0, effectiveRange: nil) as? NSColor
// Update UI
DispatchQueue.main.async { [weak self, newBackgoundColor] in
self?.backgroundColor = newBackgoundColor
self?.attributedTitle = title
self?.forceHideConstraint.isActive = scriptResult == ""
}
// Schedule next update
DispatchQueue.shellScriptQueue.asyncAfter(deadline: .now() + interval) { [weak self] in
self?.refreshAndSchedule()
}
}
func execute(_ command: String) -> String {
let task = Process()
task.launchPath = "/bin/bash"
task.arguments = ["-c", command]
let pipe = Pipe()
task.standardOutput = pipe
task.launch()
let data = pipe.fileHandleForReading.readDataToEndOfFile()
let output: String = NSString(data: data, encoding: String.Encoding.utf8.rawValue) as String? ?? ""
return output.replacingOccurrences(of: "\\n+$", with: "", options: .regularExpression)
}
}
extension DispatchQueue {
static let shellScriptQueue = DispatchQueue(label: "mtmr.shellscript")
}

View File

@ -23,6 +23,8 @@ extension ItemType {
return "com.toxblh.mtmr.staticButton."
case .appleScriptTitledButton(source: _):
return "com.toxblh.mtmr.appleScriptButton."
case .shellScriptTitledButton(source: _):
return "com.toxblh.mtmr.shellScriptButton."
case .timeButton(formatTemplate: _, timeZone: _):
return "com.toxblh.mtmr.timeButton."
case .battery():
@ -251,6 +253,8 @@ class TouchBarController: NSObject, NSTouchBarDelegate {
barItem = CustomButtonTouchBarItem(identifier: identifier, title: title)
case let .appleScriptTitledButton(source: source, refreshInterval: interval):
barItem = AppleScriptTouchBarItem(identifier: identifier, source: source, interval: interval)
case let .shellScriptTitledButton(source: source, refreshInterval: interval):
barItem = ShellScriptTouchBarItem(identifier: identifier, source: source, interval: interval)
case let .timeButton(formatTemplate: template, timeZone: timeZone):
barItem = TimeTouchBarItem(identifier: identifier, formatTemplate: template, timeZone: timeZone)
case .battery():

View File

@ -12,7 +12,7 @@ import CoreLocation
class YandexWeatherBarItem: CustomButtonTouchBarItem, CLLocationManagerDelegate {
private let activity: NSBackgroundActivityScheduler
private let unitsStr = "°C"
private let iconsSource = ["Ясно": "☀️", "Малооблачно": "🌤", "Облачно с прояснениями": "⛅️", "Пасмурно": "☁️", "Небольшой дождь": "🌦", "Дождь": "🌧", "Ливень": "", "Гроза": "🌩", "Небольшой снег": "❄️", "Снег": "🌨", "Туман": "🌫"]
private let iconsSource = ["Ясно": "☀️", "Малооблачно": "🌤", "Облачно с прояснениями": "⛅️", "Пасмурно": "☁️", "Небольшой дождь": "🌦", "Дождь": "🌧", "Ливень": "", "Гроза": "🌩", "Дождь со снегом": "☔️", "Небольшой снег": "❄️", "Снег": "🌨", "Туман": "🌫"]
private var location: CLLocation!
private var prevLocation: CLLocation!
private var manager: CLLocationManager!

View File

@ -98,6 +98,13 @@ The pre-installed configuration contains less or more than you'll probably want,
- sleep
- displaySleep
> Custom buttons
- staticButton
- appleScriptTitledButton
- shellScriptTitledButton
- timeButton
## Gestures on central part:
- two finger slide: change you Volume
@ -110,16 +117,17 @@ The pre-installed configuration contains less or more than you'll probably want,
### You can also make custom buttons using these types
- `staticButton`
#### `staticButton`
```json
"type": "staticButton",
"title": "esc",
```
- `appleScriptTitledButton`
#### `appleScriptTitledButton`
```js
{
"type": "appleScriptTitledButton",
"refreshInterval": 60, //optional
"source": {
@ -128,14 +136,43 @@ The pre-installed configuration contains less or more than you'll probably want,
"inline": "tell application \"Finder\"\rmake new Finder window\rset target of front window to path to home folder as string\ractivate\rend tell",
// or
"base64": "StringInbase64"
},
}
}
```
- `timeButton`
#### `shellScriptTitledButton`
> Note: script may return also colors using escape sequences (read more here https://misc.flogisoft.com/bash/tip_colors_and_formatting)
> Only "16 Colors" mode supported atm. If background color returned, button will pick it up as own background color.
Example of "CPU load" button which also changes color based on load value.
```js
{
"type": "shellScriptTitledButton",
"width": 80,
"refreshInterval": 2,
"source": {
"inline": "top -l 2 -n 0 -F | egrep -o ' \\d*\\.\\d+% idle' | tail -1 | awk -F% '{p = 100 - $1; if (p > 30) c = \"\\033[33m\"; if (p > 70) c = \"\\033[30;43m\"; printf \"%s%4.1f%%\\n\", c, p}'"
},
"action": "appleScript",
"actionAppleScript": {
"inline": "activate application \"Activity Monitor\"\rtell application \"System Events\"\r\ttell process \"Activity Monitor\"\r\t\ttell radio button \"CPU\" of radio group 1 of group 2 of toolbar 1 of window 1 to perform action \"AXPress\"\r\tend tell\rend tell"
},
"align": "right",
"image": {
"base64":
"iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAMAAACdt4HsAAAABGdBTUEAALGPC/xhBQAAACBjSFJNAAB6JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAAA/1BMVEUAAADaACbYACfYACfjABzXACjYACfXACjYACfYACfYACfYACfdACLYACfXACjYACfVACv/AADXACjYACfYACfXACjYACfXACjaACXYACfYACfVACvYACfYACfZACbZACbYACfYACfZACb/AADYACfYACfVACrXACjVACu/AEDYACfYACfYACfXACjXACjYACfXACjYACfYACfYACfXACjYACfXACjYACfYACfZACbYACfYACfMADPYACfYACfYACfYACfYACfZACbXACjYACfYACfRAC7XACjYACfZACbWACnXACjXACjYACfTACzZACb/AADYACfYACfYACcAAAA+zneGAAAAU3RSTlMAItK+CVPjh3xUxPwPiGDQGAMtSKmN3Vk+wPQG/e26oIJBnwJCdiuAHgTmw+6BX+IgfaqLUvKOW8VKnagK+vBwYrhlc/urCznvhSyUbOEXPAFjGh/ektAAAAABYktHRACIBR1IAAAACXBIWXMAAA3XAAAN1wFCKJt4AAAAB3RJTUUH4ggWETQWgEDcSgAAAqVJREFUWMPtl4ly2jAQhsUNNlcw5r4SICEHLSQhCQRyX73T/u//LpUlLIyxbMAznWmn/0ywo5U+27tr7ZoQuwLBUJidRKIxPhKLRtgxHAoGiLfiQIKdKFCTxjGpQmEDCSC+BiAFpNlJBsgaxyyQYQNpIPUf8AcAOzktD+iaoQJQNI5FoMAGdCCv5XZclpfKFXiqUi5Jllf1mvdyQzW96gigd4h6o+mhRp1O0x3vvwa1VSWeqrZU1Jyeogy01ggSVQsoO/i/gjq9/u6u+2LDXq2jshqLHNCgdsCVwO0NILdi0oDmuoAmoImhQDzFRPNnb36L7U43NVfc2EH2D9h5t9OePyIF5IU9uIhvkyN7iiXmQUIOj8x/lB6f0bTaQ3ZA+9iaNCH2Lpg6btsBIRJOpJl0E9ABTvof5kqEGeCjMaN/AnRMgM5XJcI2J1J1gf6S48Tb2Ae6JkAjdgmAeJ1XAOJ1Xg8wGJ6elXwAzkeGjy62BgxG3MuXnoCIkmEq8EQyAUPgajyhPxJAga9SIiRqzwMOuAbGZDrDjQRgKkpiqiPgFphM74B7d4BKy2cyy1RcBvSodUb/HiSAIl+VlEfh8cm4wvPL9nnw+gbc+kkkUVioO95etwe8PBuP8vQoBzg7UQAe5t7syZwoCaMA3AN30wlzh3MYJYkkADeYTckYuJYlkiSVBeCKZtSY/gxlqezlxEt+pdFg6zBesPXn1ih8Aj5vkAels9PhYCkPsl++kg0AQu4dyuqmugIQm+qS5Nv6N+D7wm7d1skPc4xu666Fhd6BxU6r+jub8tNaWNxK29EhsdpR/sVn7FlLm0txPdgni+JrFNd3p+K67MQtyrsp3w2G7xbHd5Plv83z3Wj6b3V9N9ssFv7afaa//ZPn3wD4/vje8PP/N7TebS0hgZhEAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDE4LTA4LTIyVDE3OjUyOjIyKzAyOjAwc2qUYAAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxOC0wOC0yMlQxNzo1MjoyMiswMjowMAI3LNwAAAAZdEVYdFNvZnR3YXJlAHd3dy5pbmtzY2FwZS5vcmeb7jwaAAAAAElFTkSuQmCC"
},
"bordered": false
}
```
#### `timeButton`
```js
{
"type": "timeButton",
"formatTemplate": "HH:mm" //optional
}
```
## Groups
@ -147,7 +184,10 @@ The pre-installed configuration contains less or more than you'll probably want,
"bordered": true,
"title": "stats",
"items": [
{ "type": "play" }, { "type": "mute" }, ...]
{ "type": "play" },
{ "type": "mute" },
...
]
}
```