feat(mac): install script

First steps towards a clean install script
for Keyman for Mac. Some steps still out-
of-order for now, particularly opening
System Preferences to allow Keyman before
Keyman has run for first time.
This commit is contained in:
Marc Durdin 2020-01-29 10:12:26 +11:00
parent 071dd868f2
commit f8ea457325
94 changed files with 396 additions and 15 deletions

View file

@ -17,11 +17,11 @@
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>Keyman $(PRODUCT_VERSION) for macOS</string>
<string>13.0.99</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>$(PRODUCT_VERSION)</string>
<string>13.0.99</string>
<key>LSMinimumSystemVersion</key>
<string>$(MACOSX_DEPLOYMENT_TARGET)</string>
<key>NSHumanReadableCopyright</key>

Binary file not shown.

View file

@ -2,8 +2,6 @@
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>NSAppleEventsUsageDescription</key>
<string>Keyman needs to send key events to the target application.</string>
<key>CFBundleDevelopmentRegion</key>
<string>English</string>
<key>CFBundleDocumentTypes</key>
@ -25,6 +23,8 @@
</array>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleGetInfoString</key>
<string>$(GET_INFO_STRING)</string>
<key>CFBundleIconFile</key>
<string></string>
<key>CFBundleIdentifier</key>
@ -36,7 +36,7 @@
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>$(PRODUCT_VERSION)</string>
<string>13.0.99</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleURLTypes</key>
@ -51,13 +51,11 @@
</dict>
</array>
<key>CFBundleVersion</key>
<string>$(PRODUCT_VERSION)</string>
<key>CFBundleGetInfoString</key>
<string>$(GET_INFO_STRING)</string>
<string>13.0.99</string>
<key>Fabric</key>
<dict>
<key>APIKey</key>
<string>68056571ada44ad2d93864380272808ada308b24</string>
<string>4efced22a226f98e05442a7e472b377d89fcc294</string>
<key>Kits</key>
<array>
<dict>
@ -80,6 +78,8 @@
<true/>
<key>LSMinimumSystemVersion</key>
<string>$(MACOSX_DEPLOYMENT_TARGET)</string>
<key>NSAppleEventsUsageDescription</key>
<string>Keyman needs to send key events to the target application.</string>
<key>NSHumanReadableCopyright</key>
<string>$(COPYRIGHT_STRING)</string>
<key>NSMainNibFile</key>

View file

@ -44,11 +44,11 @@ popd > /dev/null
. $KEYMAN_MACIM_BASE_PATH/../bashHelperFunctions.sh
KM_APP_NAME="Keyman.app"
SOURCE_KM_APP="$KEYMAN_MACIM_BASE_PATH/build/Release/$KM_APP_NAME"
KM_APP_NAME="Install Keyman.app"
OUTPUT_DIR="$KEYMAN_MACIM_BASE_PATH/output"
STAGING_DIR="$OUTPUT_DIR/temp"
DEST_DIR="$OUTPUT_DIR/upload"
SOURCE_KM_APP="$OUTPUT_DIR/$KM_APP_NAME"
ADD_VERSION_TO_DEST_DIR=true
TEMPLATE_IMAGE="$KEYMAN_MACIM_BASE_PATH/Keyman-template.dmg"
VERBOSITY=""
@ -139,6 +139,8 @@ elif [[ ! -d "$DEST_DIR" ]]; then
fail "Destination dir exists but is not a directory: $2"
fi
# TODO: Check that no Keyman volume is already mounted.
# Step 1 - Copy template to working copy to prevent unintended changes
WORKING_COPY_OF_IMAGE="$OUTPUT_DIR/Keyman-temp-$KM_VERSION.dmg"
displayInfo "Copying \"$TEMPLATE_IMAGE\" to \"$WORKING_COPY_OF_IMAGE\"..."
@ -165,9 +167,13 @@ fi
# Step 3 - Replace existing application files with new version
displayInfo "Copying files from \"$SOURCE_KM_APP\"..."
find "$DEST_KM_APP" -mindepth 1 -maxdepth 1
echo "---------"
find "$DEST_KM_APP" -mindepth 1 -maxdepth 1 -print0 | xargs -0 rm -rf
cp -fR "$SOURCE_KM_APP/" "$DEST_KM_APP"
ls -la /Volumes/Keyman/background/
# Step 4 - Detach/unmount the temporary image/staging area
displayInfo "Detaching \"$WORKING_COPY_OF_IMAGE\""
hdiutil detach $STAGING_DIR $VERBOSITY

View file

@ -6,6 +6,8 @@
<string>en</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleGetInfoString</key>
<string>$(GET_INFO_STRING)</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
@ -15,13 +17,11 @@
<key>CFBundlePackageType</key>
<string>FMWK</string>
<key>CFBundleShortVersionString</key>
<string>Keyman Engine $(PRODUCT_VERSION) for macOS</string>
<key>CFBundleGetInfoString</key>
<string>$(GET_INFO_STRING)</string>
<string>13.0.99</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>$(PRODUCT_VERSION)</string>
<string>13.0.99</string>
<key>NSHumanReadableCopyright</key>
<string>$(COPYRIGHT_STRING_RIGHTS)</string>
<key>NSPrincipalClass</key>

View file

@ -1,5 +1,7 @@
#!/bin/sh
set -e
# Include our resource functions; they're pretty useful!
. ../resources/shellHelperFunctions.sh
@ -445,6 +447,10 @@ if $LOCALDEPLOY ; then
elif $PREPRELEASE ; then
echo_heading "Preparing files for release deployment..."
# Create the disk image
pushd setup
eval "./build.sh"
popd
eval "$KM4MIM_BASE_PATH/make-km-dmg.sh" -version $KM_VERSION $QUIET_FLAG
if [ $? == 0 ]; then
displayInfo "Creating disk image succeeded!" ""

View file

@ -0,0 +1,114 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>BuildMachineOSBuild</key>
<string>18A391019</string>
<key>CFBundleDevelopmentRegion</key>
<string>English</string>
<key>CFBundleDocumentTypes</key>
<array>
<dict>
<key>CFBundleTypeExtensions</key>
<array>
<string>*</string>
</array>
<key>CFBundleTypeOSTypes</key>
<array>
<string>****</string>
</array>
<key>CFBundleTypeRole</key>
<string>Viewer</string>
</dict>
</array>
<key>CFBundleExecutable</key>
<string>CocoaApplet</string>
<key>CFBundleIconFile</key>
<string>applet</string>
<key>CFBundleIdentifier</key>
<string>com.keyman.mac.installKeyman</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>Install Keyman</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleSignature</key>
<string>aplt</string>
<key>CFBundleSupportedPlatforms</key>
<array>
<string>MacOSX</string>
</array>
<key>CFBundleVersion</key>
<string>1.0</string>
<key>DTCompiler</key>
<string>com.apple.compilers.llvm.clang.1_0</string>
<key>DTPlatformBuild</key>
<string>11M392v</string>
<key>DTPlatformName</key>
<string>macosx</string>
<key>DTPlatformVersion</key>
<string>10.15</string>
<key>DTSDKBuild</key>
<string>19C38</string>
<key>DTSDKName</key>
<string>macosx10.15internal</string>
<key>DTXcode</key>
<string>1100</string>
<key>DTXcodeBuild</key>
<string>11M392v</string>
<key>LSApplicationCategoryType</key>
<string>public.app-category.utilities</string>
<key>LSMinimumSystemVersion</key>
<string>10.15</string>
<key>NSAppleEventsUsageDescription</key>
<string>This script needs to control other applications to run.</string>
<key>NSAppleMusicUsageDescription</key>
<string>This script needs access to your music to run.</string>
<key>NSCalendarsUsageDescription</key>
<string>This script needs access to your calendars to run.</string>
<key>NSCameraUsageDescription</key>
<string>This script needs access to your camera to run.</string>
<key>NSContactsUsageDescription</key>
<string>This script needs access to your contacts to run.</string>
<key>NSHomeKitUsageDescription</key>
<string>This script needs access to your HomeKit Home to run.</string>
<key>NSHumanReadableCopyright</key>
<string>Copyright 2020 SIL International.</string>
<key>NSMainNibFile</key>
<string>MainMenu</string>
<key>NSMicrophoneUsageDescription</key>
<string>This script needs access to your microphone to run.</string>
<key>NSPhotoLibraryUsageDescription</key>
<string>This script needs access to your photos to run.</string>
<key>NSPrincipalClass</key>
<string>NSApplication</string>
<key>NSRemindersUsageDescription</key>
<string>This script needs access to your reminders to run.</string>
<key>NSSiriUsageDescription</key>
<string>This script needs access to Siri to run.</string>
<key>NSSystemAdministrationUsageDescription</key>
<string>This script needs access to administer this system to run.</string>
<key>WindowState</key>
<dict>
<key>bundleDividerCollapsed</key>
<false/>
<key>bundlePositionOfDivider</key>
<real>1162</real>
<key>dividerCollapsed</key>
<false/>
<key>eventLogLevel</key>
<integer>2</integer>
<key>name</key>
<string>ScriptWindowState</string>
<key>positionOfDivider</key>
<real>594</real>
<key>savedFrame</key>
<string>148 235 1461 732 0 0 1920 1057 </string>
<key>selectedTab</key>
<string>result</string>
</dict>
</dict>
</plist>

Binary file not shown.

View file

@ -0,0 +1 @@
APPLaplt

View file

@ -0,0 +1,5 @@
{\rtf1\ansi\ansicpg1252\cocoartf2511
\cocoatextscaling0\cocoaplatform0{\fonttbl}
{\colortbl;\red255\green255\blue255;}
{\*\expandedcolortbl;;}
}

Binary file not shown.

Binary file not shown.

101
mac/setup/build.sh Executable file
View file

@ -0,0 +1,101 @@
#!/bin/bash
set -e
set -u
# Include our resource functions; they're pretty useful!
. ../../resources/shellHelperFunctions.sh
# Please note that this build script (understandably) assumes that it is running on Mac OS X.
verify_on_mac
#
# Constants
#
SOURCEAPP="./Install Keyman.app"
TARGETPATH=../Keyman4MacIM/output
TARGETAPP="$TARGETPATH/Install Keyman.app"
KEYMANAPP=../Keyman4MacIM/build/Release/Keyman.app
#
# Build textinputsource
#
pushd textinputsource
./build.sh
popd
#
# Build install candidate of "Install Keyman"
#
# TODO: get script folder so we can run this from anywhere safely
# TODO: set version number in install image
# Remove old install image
mkdir -p "$TARGETPATH"
rm -rf "$TARGETAPP" || true
# Copy new install image to output foldercer
cp -R "$SOURCEAPP" "$TARGETPATH/"
# Copy Keyman.app into install image
cp -R "$KEYMANAPP" "$TARGETAPP/Contents/MacOS/Keyman.app"
# Copy textinputsource into install image
cp textinputsource/textinputsource "$TARGETAPP/Contents/MacOS/textinputsource"
# NOW TO notarize...
xattr -rc "$TARGETAPP"
codesign --force --options runtime --deep --sign "${CERTIFICATE_ID}" "$TARGETAPP"
#
# Notarize the app (copied from ../build.sh)
# TODO: merge notarization into function
#
TARGET_ZIP_PATH="$TARGETPATH/Install Keyman.zip"
TARGET_APP_PATH="$TARGETAPP"
ALTOOL_LOG_PATH="$TARGETPATH/altool.log"
echo_heading "Zipping Install Keyman.app for notarization to $TARGET_ZIP_PATH"
/usr/bin/ditto -c -k --keepParent "$TARGET_APP_PATH" "$TARGET_ZIP_PATH"
echo_heading "Uploading Install Keyman.zip to Apple for notarization"
xcrun altool --notarize-app --primary-bundle-id "com.Keyman.install.zip" --asc-provider "$APPSTORECONNECT_PROVIDER" --username "$APPSTORECONNECT_USERNAME" --password @env:APPSTORECONNECT_PASSWORD --file "$TARGET_ZIP_PATH" --output-format xml > $ALTOOL_LOG_PATH || fail "altool failed"
cat "$ALTOOL_LOG_PATH"
ALTOOL_UUID=$(/usr/libexec/PlistBuddy -c "Print notarization-upload:RequestUUID" "$ALTOOL_LOG_PATH")
ALTOOL_FINISHED=0
while [ $ALTOOL_FINISHED -eq 0 ]
do
# We'll sleep 30 seconds before checking status, to give the altool server time to process the archive
echo "Waiting 30 seconds for status"
sleep 30
xcrun altool --notarization-info "$ALTOOL_UUID" --username "$APPSTORECONNECT_USERNAME" --password @env:APPSTORECONNECT_PASSWORD --output-format xml > "$ALTOOL_LOG_PATH" || fail "altool failed"
ALTOOL_STATUS=$(/usr/libexec/PlistBuddy -c "Print notarization-info:Status" "$ALTOOL_LOG_PATH")
if [ "$ALTOOL_STATUS" == "success" ]; then
ALTOOL_FINISHED=1
elif [ "$ALTOOL_STATUS" != "in progress" ]; then
# Probably failing with 'invalid'
cat "$ALTOOL_LOG_PATH"
ALTOOL_LOG_URL=$(/usr/libexec/PlistBuddy -c "Print notarization-info:LogFileURL" "$ALTOOL_LOG_PATH")
curl "$ALTOOL_LOG_URL"
fail "Notarization failed with $ALTOOL_STATUS; check log at $ALTOOL_LOG_PATH"
fi
done
echo_heading "Notarization completed successfully. Review logs below for any warnings."
cat "$ALTOOL_LOG_PATH"
ALTOOL_LOG_URL=$(/usr/libexec/PlistBuddy -c "Print notarization-info:LogFileURL" "$ALTOOL_LOG_PATH")
curl "$ALTOOL_LOG_URL"
echo
echo_heading "Attempting to staple notarization to Install Keyman.app"
xcrun stapler staple "$TARGET_APP_PATH" || fail "stapler failed"
# Done.
# Now, we can add "Install Keyman.app" to the .dmg for distribution!

View file

@ -0,0 +1,4 @@
#!/bin/bash
set -e
set -u
gcc -framework Carbon -o textinputsource main.c

View file

@ -0,0 +1,144 @@
/*
file
main.c
function
to manipulate text input source, i.e.,
- print currently selected source,
- select specified source (enable it as needed)
- enable specified source,
- disable specified source,
- toggle enabled/disabled state of specified source.
compile
gcc -framework Carbon -o textinputsource main.c
usage e.g.
./textinputsource [-s|e|d|t name]
given no arguments, it will print the current source name.
options:
-s : select source name (enable it as needed)
-e : enable source name
-d : disable source name
-t : toggle enabled/disabled on source name
Originally found at https://discussions.apple.com/thread/5610262
*/
#include <Carbon/Carbon.h>
#include <libgen.h> // basename
TISInputSourceRef getInputSourceByName(char *);
int
main (int argc, char * argv[])
{
int ret = -1;
int c;
TISInputSourceRef tis;
CFStringRef name;
OSStatus err = 0;
while ((c = getopt(argc, argv, "s:e:d:t:h")) != -1)
{
switch (c)
{
case 's':
tis = getInputSourceByName(optarg);
if (tis)
{
CFBooleanRef enabled = TISGetInputSourceProperty(tis, kTISPropertyInputSourceIsEnabled);
if (enabled == kCFBooleanFalse)
TISEnableInputSource(tis);
err = TISSelectInputSource(tis);
CFRelease(tis);
}
ret = tis ? (int) err : 1;
break;
case 'e':
tis = getInputSourceByName(optarg);
if (tis)
{
err = TISEnableInputSource(tis);
CFRelease(tis);
}
ret = tis ? (int) err : 1;
break;
case 'd':
tis = getInputSourceByName(optarg);
if (tis)
{
err = TISDisableInputSource(tis);
CFRelease(tis);
}
ret = tis ? (int) err : 1;
break;
case 't':
tis = getInputSourceByName(optarg);
if (tis)
{
CFBooleanRef enabled = TISGetInputSourceProperty(tis, kTISPropertyInputSourceIsEnabled);
if (enabled == kCFBooleanTrue)
err = TISDisableInputSource(tis);
else
err = TISEnableInputSource(tis);
CFRelease(tis);
}
ret = tis ? (int) err : 1;
break;
case 'h':
case '?':
default:
fprintf(stderr, "Usage: %s %s\n\t\%s\n\t%s\n\t%s\n\t%s\n\t%s\n",
basename(argv[0]),
"[-s|e|d|t name]",
"-s : select source name (enable it as needed)",
"-e : enable source name",
"-d : disable source name",
"-t : toggle enabled/disabled of source name",
"no arguments : print current source name"
);
ret = 1;
break;
}
}
if (ret == -1) // no args: print current keyboard input source
{
tis = TISCopyCurrentKeyboardInputSource();
name = TISGetInputSourceProperty(tis, kTISPropertyLocalizedName);
int len = CFStringGetLength(name) * 4 + 1;
char cname[len];
bool b = CFStringGetCString(name, cname, len, kCFStringEncodingUTF8);
printf("%s\n", b ? cname : "");
ret = b ? 0 : 1;
}
if (err != noErr)
fprintf(stderr, "Text Input Source Services error: %d\n", (int) err);
return ret;
}
TISInputSourceRef
getInputSourceByName(char *cname)
{
// char *cname : input source name in UTF-8 terminated by null character
// return TISInputSourceRef or NULL : text input source reference (retained)
CFStringRef name = CFStringCreateWithCString(kCFAllocatorDefault, cname, kCFStringEncodingUTF8);
CFStringRef keys[] = { kTISPropertyLocalizedName };
CFStringRef values[] = { name };
CFDictionaryRef dict = CFDictionaryCreate(kCFAllocatorDefault, (const void **)keys, (const void **)values, 1, NULL, NULL);
CFArrayRef array = TISCreateInputSourceList(dict, true);
CFRelease(dict);
CFRelease(name);
if (!array)
{
fprintf(stderr, "No such text input source: %s\n", optarg);
return NULL;
}
TISInputSourceRef tis = (TISInputSourceRef) CFArrayGetValueAtIndex(array, 0);
CFRetain(tis);
CFRelease(array);
return tis;
}