Skip to content

Instantly share code, notes, and snippets.

@Noitidart
Last active August 29, 2015 14:06
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Noitidart/6a2cbe0b4c74499765be to your computer and use it in GitHub Desktop.
Save Noitidart/6a2cbe0b4c74499765be to your computer and use it in GitHub Desktop.
_ff-addon-snippet-MakeShortcutMacOS - Use OS.File like NativeApp.jsm to create Mac OS .app file as a shortcut with a custom icon.
Cu.import('resource://gre/modules/osfile.jsm');
Cu.import('resource://gre/modules/FileUtils.jsm');
var shortcutName = 'night time';
var shortcutIconPath = OS.Path.join(OS.Constants.Path.desktopDir, 'nightly.icns');
var shortcutTarget = FileUtils.getFile('XREExeF', []).path + ' -P -no-remote'; //what shortcut should open
var shortcutLocation = OS.Constants.Path.desktopDir; //where to place the shortcut file
var promise_createApp = makeShortcutMacOS(shortcutName, shortcutIconPath, shortcutTarget, shortcutLocation);
promise_createApp.then(
function(aVal) {
console.log('promise_createApp successfully exuted');
},
function(aReason) {
console.error('promise_createApp FAILED, aReason:', aReason);
}
);
function makeShortcutMacOS(name, iconpath, target, locpath) {
var dirApp = OS.Path.join(shortcutLocation, shortcutName + '.app');
var dirContents = OS.Path.join(dirApp, 'Contents');
var dirMacOS = OS.Path.join(dirContents, 'MacOS');
var fileScript = OS.Path.join(dirMacOS, name);
var dirResources = OS.Path.join(dirContents, 'Resources');
var fileIcon = OS.Path.join(dirResources, 'appicon.icns');
var fileZip = OS.Path.join(dirResources, 'application.zip');
var filePlist = OS.Path.join(dirContents, 'Info.plist');
var promise_makeApp = OS.File.makeDir(dirApp, {unixMode: FileUtils.PERMS_DIRECTORY, ignoreExisting: true});
return promise_makeApp.then(
function(aVal) {
console.log('app folder succesfully made');
var promise_makeContents = OS.File.makeDir(dirContents, {unixMode: FileUtils.PERMS_DIRECTORY, ignoreExisting: true});
return promise_makeContents.then(
function(aVal) {
console.log('contents folder succesfully made');
var promiseAll_make_MacOS_Resoruces_Plist;
var deferred_writeScript = Promise.defer();
var deferred_setPermsScript = Promise.defer();
var doPromiseScriptSetPerm = function() {
var promise_setPermsScript = OS.File.setPermissions(fileScript, {unixMode: FileUtils.PERMS_DIRECTORY});
promise_setPermsScript.then(
function(aVal) {
console.log('promise_setPermsScript success, aVal:', aVal);
deferred_setPermsScript.resolve('promise_setPermsScript success');
},
function(aReason) {
console.error('promise_setPermsScript failed, aReason:', aReason);
deferred_setPermsScript.reject('promise_setPermsScript failed, aReason:' + aReason);
}
);
};
var promise_makeMacOS = OS.File.makeDir(dirMacOS, {unixMode: FileUtils.PERMS_DIRECTORY, ignoreExisting: true});
promise_makeMacOS.then(
function(aVal) {
console.log('macos folder succesfully made');
var promise_writeScript = OS.File.writeAtomic(fileScript, '#!/bin/sh\nexec ' + target, {encoding:'utf-8', /*unixMode: FileUtils.PERMS_DIRECTORY,*/ noOverwrite: true});
promise_writeScript.then(
function(aVal) {
console.log('script succesfully wrote, aVal:', aVal);
deferred_writeScript.resolve('script succesfully made');
doPromiseScriptSetPerm();
},
function(aReason) {
if (aReason.becauseExists) {
console.log('scriptFile already exists so just go to success func, i dont know how so i go to doScriptSetPerm');
deferred_writeScript.resolve('script succesfully made');
doPromiseScriptSetPerm();
} else {
console.error('script failed to write, aReason:', aReason);
deferred_writeScript.reject('script folder failed to make, aReason:' + aReason);
}
}
);
},
function(aReason) {
console.error('macos folder failed to make, aReason:', aReason);
return Promise.reject('macos folder failed to make, aReason:' + aReason);
}
);
var deferred_copyIcon = Promise.defer();
var promise_makeResources = OS.File.makeDir(dirResources, {unixMode: FileUtils.PERMS_DIRECTORY, ignoreExisting: true});
promise_makeResources.then(
function(aVal) {
console.log('resources folder succesfully made');
var promise_copyIcon = OS.File.copy(iconpath, fileIcon, {noOverwrite:false}); //we want the icon overwritten as user may have badged it
return promise_copyIcon.then(
function(aVal) {
console.log('icon file succesfully copied');
deferred_copyIcon.resolve('icon file succesfully copied');
},
function(aReason) {
console.error('icon file failed to copy, aReason:', aReason);
deferred_copyIcon.reject('icon file failed to copy, aReason:' + aReason);
}
);
},
function(aReason) {
console.error('resources folder failed to make, aReason:', aReason);
return Promise.reject('resources folder failed to make, aReason:' + aReason);
}
);
var doPromisePlistSetPerm = function(aVal) {
var promise_setPermPlist = OS.File.setPermissions(filePlist, {unixMode: FileUtils.PERMS_DIRECTORY});
return promise_setPermPlist.then(
function(aVal) {
console.log('plist setperm successfully done');
//return Promise.resolve('plist setperm successfully done');
deferred_setPermPlist.resolve('plist setperm success');
},
function(aReason) {
console.error('failed to setPermissions on filePlist, aReason:', aReason);
deferred_setPermPlist.reject('failed to setPermissions on filePlist, aReason:' + aReason);
}
);
}
var deferred_makePlist = Promise.defer(); //need this because if push promise_makePlist, if rejected because it exists, then it rejects the promise.all
var deferred_setPermPlist = Promise.defer(); //need this because if i do return doPromisePlistSetPerm() in promise_makePlist it doesnt chain the Promise.all completes prematurely
var promise_makePlist = OS.File.writeAtomic(filePlist, plistContent({ CFBundleExecutable:name, CFBundleIdentifier:name + Math.random(), CFBundleName:name }), {encoding:'utf-8', /*unixMode: FileUtils.PERMS_DIRECTORY,*/ noOverwrite: true}); //note: i dont think writeAtomic has unixMode option so i do setPermissions
promise_makePlist.then(
function(aVal) {
console.log('plist file succesfully made');
deferred_makePlist.resolve('plist file succesfully made');
doPromisePlistSetPerm();
},
function(aReason) {
if (aReason.becauseExists) {
console.log('plistInfo already exists so just go to success func, i dont know how so i go to doPromisePlistSetPerm');
deferred_makePlist.resolve('plist file succesfully made');
doPromisePlistSetPerm();
} else {
console.error('plist file failed to make, aReason:', aReason);
return Promise.reject('plist file failed to make, aReason:' + aReason);
}
}
);
promiseAll_make_MacOS_Resoruces = [promise_makeMacOS, promise_makeResources, deferred_makePlist.promise, deferred_copyIcon.promise, deferred_setPermPlist.promise, deferred_writeScript.promise, deferred_setPermsScript.promise];
return Promise.all(promiseAll_make_MacOS_Resoruces).then(
function(aVal) {
console.log('promiseAll_make_MacOS_Resoruces succeeded');
var promise_removeXattr = Promise.defer();
//start - remove "unidentified developer" on launch thing
try {
var xattr = Cc['@mozilla.org/file/local;1'].createInstance(Ci.nsILocalFile);
xattr.initWithPath('/usr/bin/xattr');
var proc = Cc['@mozilla.org/process/util;1'].createInstance(Ci.nsIProcess);
proc.init(xattr);
var procFinXATTR = {
observe: function(aSubject, aTopic, aData) {
console.log('incoming procFinXATTR', 'aSubject:', aSubject, 'aTopic:', aTopic, 'aData', aData);
console.log('xattr removed promise succes');
promise_removeXattr.resolve('xatrr removed');
}
};
var args = ['-d', 'com.apple.quarantine', dirApp];
proc.runAsync(args, args.length, procFinXATTR);
} catch (ex) {
console.error('error during xattr removed promise, ex:', ex);
promise_removeXattr.reject('error during xattr removed, ex:' + ex);
}
//end - remove "unidentified developer" on launch thing
return promise_removeXattr.promise;
},
function(aReason) {
console.error('promiseAll_make_MacOS_Resoruces failed, aReason:', aReason);
return Promise.reject('promiseAll_make_MacOS_Resoruces failed, aReason:' + aReason);
}
);
},
function(aReason) {
console.error('contents folder failed to make, aReason:', aReason);
return Promise.reject('contents folder failed to make, aReason:' + aReason);
}
);
},
function(aReason) {
console.error('app folder failed to make, aReason:', aReason);
return Promise.reject('app folder failed to make, aReason:' + aReason);
}
);
}
function plistContent(contentObj) {
return '<?xml version="1.0" encoding="UTF-8"?>\n\
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">\n\
<plist version="1.0">\n\
<dict>\n\
<key>CFBundleAllowMixedLocalizations</key>\n\
<true/>\n\
<key>CFBundleDevelopmentRegion</key>\n\
<string>English</string>\n\
<key>CFBundleExecutable</key>\n\
<string>' + escapeXML(contentObj.CFBundleExecutable) + '</string>\n\
<key>CFBundleIconFile</key>\n\
<string>appicon</string>\n\
<key>CFBundleIdentifier</key>\n\
<string>com.apple.ScriptEditor.id.' + escapeXML(contentObj.CFBundleIdentifier) + '</string>\n\
<key>CFBundleInfoDictionaryVersion</key>\n\
<string>6.0</string>\n\
<key>CFBundleName</key>\n\
<string>' + escapeXML(contentObj.CFBundleName) + '</string>\n\
<key>CFBundlePackageType</key>\n\
<string>APPL</string>\n\
<key>CFBundleShortVersionString</key>\n\
<string>1.0</string>\n\
<key>CFBundleSignature</key>\n\
<string>aplt</string>\n\
</dict>\n\
</plist>';
}
function escapeXML(aStr) {
return aStr.toString()
.replace(/&/g, '&amp;')
.replace(/"/g, '&quot;')
.replace(/'/g, '&apos;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;');
}
@Noitidart
Copy link
Author

README

See Also: Noitidart / _ff-addon-snippet-OSACompile_nsIFile.js - Use OSACompile command/process to create shortcut

Rev1

  • Based on method by zimbra

Rev2

  • Promise not triggering in right order, because Promise.all is triggering even though I'm chaining promise_setPerm to promise_writePlist AND promise_createResources is completing before promise_copyIcon

Rev3

  • Fixed the promise trigger order and even writeAtomic rejection on becauseExists reroute by using Promise.defer

Rev4

  • On error properly returns Promise.reject or deferred.reject appropriately
  • Added writeScript

Rev5

  • Tested on Mac OS X 10.9 till it worked
    • I had to setPerms properly to FileUtils.PERMS_DIRECTORY I was doing to FileUtils.PERMS_FILE which was wrong
    • I had to update the CFBundleExecutable in Info.plist to be name
    • Had to remove FirefoxBinary otherwise icon would not show, it would show like this:
      icon doesnt show if use FirefoxBinary in plist
      • can succesfully stil right click and open it though
        even if include FirefoxBinary can right click open
    • Gives the "unidentified developer" error on launch:
      unident dev error
      • can open by right clicking and doing open
        have to right click > open > open

Rev6

  • Is correctly using contentObj.CFBundleIdentifier, I had commented out before and accidentally left it commented
  • Renamed shortcut from my sc to Firefox - Profile Manager

Rev7

  • Added importing of os.file and fileutils

Rev8

  • Renamed to makeShortcutMacOS

Rev9

  • Added to description with custom icon.

Rev10

@yajd
Copy link

yajd commented Sep 19, 2014

Testing if adding LSFileQuarantineEnabled<key><false/> will bypas the "unidentifeid eveloper" error. This test shows it did not, but i didnt try hard maybe i can get it to work, maybe it needs some other key names like unique idneitifer alogn with it to work:

Cu.import('resource://gre/modules/osfile.jsm');
Cu.import('resource://gre/modules/FileUtils.jsm');

var shortcutName = 'no remote profile manger ff';
var shortcutIconPath =  OS.Path.join(OS.Constants.Path.desktopDir, 'aurora.icns');
var shortcutTarget = FileUtils.getFile('XREExeF', []).path + ' -P -no-remote'; //what shortcut should open
var shortcutLocation = OS.Constants.Path.desktopDir; //where to place the shortcut file
var promise_createApp = makeShortcutMacOS(shortcutName, shortcutIconPath, shortcutTarget, shortcutLocation);
promise_createApp.then(
  function(aVal) {
    console.log('promise_createApp successfully exuted');
  },
  function(aReason) {
    console.error('promise_createApp FAILED, aReason:', aReason);
  }
);
function makeShortcutMacOS(name, iconpath, target, locpath) {
  var dirApp = OS.Path.join(shortcutLocation, shortcutName + '.app');
  var dirContents = OS.Path.join(dirApp, 'Contents');
  var dirMacOS = OS.Path.join(dirContents, 'MacOS');
  var fileScript = OS.Path.join(dirMacOS, name);
  var dirResources = OS.Path.join(dirContents, 'Resources');
  var fileIcon = OS.Path.join(dirResources, 'appicon.icns');
  var fileZip = OS.Path.join(dirResources, 'application.zip');
  var filePlist = OS.Path.join(dirContents, 'Info.plist');

  var promise_makeApp = OS.File.makeDir(dirApp, {unixMode: FileUtils.PERMS_DIRECTORY, ignoreExisting: true});
  return promise_makeApp.then(
   function(aVal) {
     console.log('app folder succesfully made');
     var promise_makeContents = OS.File.makeDir(dirContents, {unixMode: FileUtils.PERMS_DIRECTORY, ignoreExisting: true});
     return promise_makeContents.then(
       function(aVal) {
         console.log('contents folder succesfully made');
         var promiseAll_make_MacOS_Resoruces_Plist;
         var deferred_writeScript = Promise.defer();
         var deferred_setPermsScript = Promise.defer();
         var doPromiseScriptSetPerm = function() {
           var promise_setPermsScript = OS.File.setPermissions(fileScript, {unixMode: FileUtils.PERMS_DIRECTORY});
           promise_setPermsScript.then(
             function(aVal) {
               console.log('promise_setPermsScript success, aVal:', aVal);
               deferred_setPermsScript.resolve('promise_setPermsScript success');
             },
             function(aReason) {
               console.error('promise_setPermsScript failed, aReason:', aReason);
               deferred_setPermsScript.reject('promise_setPermsScript failed, aReason:' + aReason);
             }
           );
         };
         var promise_makeMacOS = OS.File.makeDir(dirMacOS, {unixMode: FileUtils.PERMS_DIRECTORY, ignoreExisting: true});
         promise_makeMacOS.then(
           function(aVal) {
             console.log('macos folder succesfully made');
             var promise_writeScript = OS.File.writeAtomic(fileScript, '#!/bin/sh\nexec ' + target, {encoding:'utf-8', /*unixMode: FileUtils.PERMS_DIRECTORY,*/ noOverwrite: true});
             promise_writeScript.then(
              function(aVal) {
                console.log('script succesfully wrote, aVal:', aVal);
                deferred_writeScript.resolve('script succesfully made');
                doPromiseScriptSetPerm();
              },
              function(aReason) {
                if (aReason.becauseExists) {
                  console.log('scriptFile already exists so just go to success func, i dont know how so i go to doScriptSetPerm');
                  deferred_writeScript.resolve('script succesfully made');
                  doPromiseScriptSetPerm();
                } else {
                  console.error('script failed to write, aReason:', aReason);
                  deferred_writeScript.reject('script folder failed to make, aReason:' + aReason);
                }
              }
             );
           },
           function(aReason) {
             console.error('macos folder failed to make, aReason:', aReason);
             return Promise.reject('macos folder failed to make, aReason:' + aReason);
           }
         );
         var deferred_copyIcon = Promise.defer();
         var promise_makeResources = OS.File.makeDir(dirResources, {unixMode: FileUtils.PERMS_DIRECTORY, ignoreExisting: true});
         promise_makeResources.then(
           function(aVal) {
             console.log('resources folder succesfully made');
             var promise_copyIcon = OS.File.copy(iconpath, fileIcon, {noOverwrite:false}); //we want the icon overwritten as user may have badged it
             return promise_copyIcon.then(
               function(aVal) {
                 console.log('icon file succesfully copied');
                 deferred_copyIcon.resolve('icon file succesfully copied');
               },
               function(aReason) {
                 console.error('icon file failed to copy, aReason:', aReason);
                 deferred_copyIcon.reject('icon file failed to copy, aReason:' + aReason);
               }
             );
           },
           function(aReason) {
             console.error('resources folder failed to make, aReason:', aReason);
             return Promise.reject('resources folder failed to make, aReason:' + aReason);
           }
         );
         var doPromisePlistSetPerm = function(aVal) {
           var promise_setPermPlist = OS.File.setPermissions(filePlist, {unixMode: FileUtils.PERMS_DIRECTORY});
           return promise_setPermPlist.then(
             function(aVal) {
               console.log('plist setperm successfully done');
               //return Promise.resolve('plist setperm successfully done');
               deferred_setPermPlist.resolve('plist setperm success');
             },
             function(aReason) {
               console.error('failed to setPermissions on filePlist, aReason:', aReason);
               deferred_setPermPlist.reject('failed to setPermissions on filePlist, aReason:' + aReason);
             }
           );
         }
         var deferred_makePlist = Promise.defer(); //need this because if push promise_makePlist, if rejected because it exists, then it rejects the promise.all
         var deferred_setPermPlist = Promise.defer(); //need this because if i do return doPromisePlistSetPerm() in promise_makePlist it doesnt chain the Promise.all completes prematurely
         var promise_makePlist = OS.File.writeAtomic(filePlist, plistContent({ CFBundleExecutable:name, CFBundleIdentifier:name + Math.random(), CFBundleName:name }), {encoding:'utf-8', /*unixMode: FileUtils.PERMS_DIRECTORY,*/ noOverwrite: true}); //note: i dont think writeAtomic has unixMode option so i do setPermissions
         promise_makePlist.then(
           function(aVal) {
             console.log('plist file succesfully made');
             deferred_makePlist.resolve('plist file succesfully made');
             doPromisePlistSetPerm();
           },
           function(aReason) {
             if (aReason.becauseExists) {
               console.log('plistInfo already exists so just go to success func, i dont know how so i go to doPromisePlistSetPerm');
               deferred_makePlist.resolve('plist file succesfully made');
               doPromisePlistSetPerm();
             } else {
              console.error('plist file failed to make, aReason:', aReason);
              return Promise.reject('plist file failed to make, aReason:' + aReason);
             }
           }
         );
         promiseAll_make_MacOS_Resoruces = [promise_makeMacOS, promise_makeResources, deferred_makePlist.promise, deferred_copyIcon.promise, deferred_setPermPlist.promise, deferred_writeScript.promise, deferred_setPermsScript.promise];
         return Promise.all(promiseAll_make_MacOS_Resoruces).then(
           function(aVal) {
             console.log('promiseAll_make_MacOS_Resoruces succeeded');
             /*
             var promise_removeXattr = Promise.defer();

              //start - remove "unidentified developer" on launch thing
             try {
              var xattr = Cc['@mozilla.org/file/local;1'].createInstance(Ci.nsILocalFile);
              xattr.initWithPath('/usr/bin/xattr');

              var proc = Cc['@mozilla.org/process/util;1'].createInstance(Ci.nsIProcess);
              proc.init(xattr);

              var procFinXATTR = {
                observe: function(aSubject, aTopic, aData) {
                  console.log('incoming procFinXATTR', 'aSubject:', aSubject, 'aTopic:', aTopic, 'aData', aData);
                  console.log('xattr removed promise succes');
                  promise_removeXattr.resolve('xatrr removed');
                }
              };

              var args = ['-d', 'com.apple.quarantine', dirApp];
              proc.runAsync(args, args.length, procFinXATTR);
             } catch (ex) {
               console.error('error during xattr removed promise, ex:', ex);
               promise_removeXattr.reject('error during xattr removed, ex:' + ex);
             }
              //end - remove "unidentified developer" on launch thing
             return promise_removeXattr.promise;
             */
           },
           function(aReason) {
             console.error('promiseAll_make_MacOS_Resoruces failed, aReason:', aReason);
             return Promise.reject('promiseAll_make_MacOS_Resoruces failed, aReason:' + aReason);
           }
         );
       },
       function(aReason) {
         console.error('contents folder failed to make, aReason:', aReason);
         return Promise.reject('contents folder failed to make, aReason:' + aReason);
       }
     );
   },
   function(aReason) {
     console.error('app folder failed to make, aReason:', aReason);
     return Promise.reject('app folder failed to make, aReason:' + aReason);
   }
  );
}

function plistContent(contentObj) {
return '<?xml version="1.0" encoding="UTF-8"?>\n\
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">\n\
<plist version="1.0">\n\
<dict>\n\
    <key>CFBundleAllowMixedLocalizations</key>\n\
    <true/>\n\
    <key>CFBundleDevelopmentRegion</key>\n\
    <string>English</string>\n\
    <key>CFBundleExecutable</key>\n\
    <string>' + escapeXML(contentObj.CFBundleExecutable) + '</string>\n\
    <key>CFBundleIconFile</key>\n\
    <string>appicon</string>\n\
    <key>CFBundleIdentifier</key>\n\
    <string>com.apple.ScriptEditor.id.' + escapeXML(contentObj.CFBundleIdentifier) + '</string>\n\
    <key>CFBundleInfoDictionaryVersion</key>\n\
    <string>6.0</string>\n\
    <key>CFBundleName</key>\n\
    <string>' + escapeXML(contentObj.CFBundleName) + '</string>\n\
    <key>CFBundlePackageType</key>\n\
    <string>APPL</string>\n\
    <key>CFBundleShortVersionString</key>\n\
    <string>1.0</string>\n\
    <key>CFBundleSignature</key>\n\
    <string>aplt</string>\n\
    <key>LSFileQuarantineEnabled</key>\n\
    <false/>\n\
</dict>\n\
</plist>';
}

function escapeXML(aStr) {
  return aStr.toString()
             .replace(/&/g, '&amp;')
             .replace(/"/g, '&quot;')
             .replace(/'/g, '&apos;')
             .replace(/</g, '&lt;')
             .replace(/>/g, '&gt;');
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment