Set Custom KeyEquivalent in Services Menu - objective-c

OmniFocus has a Cocoa Service that allows you to create tasks based upon selected items.
It has a preference that allows you to set the keyboard shortcut that triggers the Service. This is not just a global hotkey, it's a bona fide Service that shows up in the menu.
You can the keyboard shortcut to pretty much any combination, including combinations with ⌥ and ^. This functionality is not documented - the docs seem to say that KeyEquivalents must be a ⌘+[⇧]+someKey.
Once this is set, I observe three things:
The OmniFocus Info.plist file does not contain a KeyEquivalent listed. This is not surprising, as the file is read-only.
The pbs -dump_pboard utility lists NSKeyEquivalent = {}; for the service.
Using NSDebugServices lists this interesting line that does not show up with most debugging sessions (Obviously, for keyboard shortcut ⌃⌥⌘M): OmniFocus: Send to Inbox (com.omnigroup.OmniFocus) has a custom key equivalent: <NSKeyboardShortcut: 0x7fb18a0d18f0 (⌃⌥⌘M)>.
So my questions are twofold, and I suspect they are related:
How do you dynamically change a service's KeyEquivalent?
How do you set the KeyEquivalent to a combination including ⌃ and ⌥
Thank you!

Figured it out. The basic process is described here: Register NSService with Command Alt NSKeyEquivalent
The code is this:
//Bundle identifier from Info.plist
NSString* bundleIdentifier = #"com.whatever.MyApp";
//Services -> Menu -> Menu item title from Info.plist
NSString* appServiceName = #"Launch My Service";
//Services -> Instance method name from Info.plist
NSString* methodNameForService = #"myServiceMethod";
//The key equivalent
NSString* keyEquivalent = #"#~r";
CFStringRef serviceStatusName = (CFStringRef)[NSString stringWithFormat:#"%# - %# - %#", bundleIdentifier, appServiceName, methodNameForService];
CFStringRef serviceStatusRoot = CFSTR("NSServicesStatus");
CFPropertyListRef pbsAllServices = (CFPropertyListRef) CFMakeCollectable ( CFPreferencesCopyAppValue(serviceStatusRoot, CFSTR("pbs")) );
// the user did not configure any custom services
BOOL otherServicesDefined = pbsAllServices != NULL;
BOOL ourServiceDefined = NO;
if ( otherServicesDefined ) {
ourServiceDefined = NULL != CFDictionaryGetValue((CFDictionaryRef)pbsAllServices, serviceStatusName);
}
NSUpdateDynamicServices();
NSMutableDictionary *pbsAllServicesNew = nil;
if (otherServicesDefined) {
pbsAllServicesNew = [NSMutableDictionary dictionaryWithDictionary:(NSDictionary*)pbsAllServices];
} else {
pbsAllServicesNew = [NSMutableDictionary dictionaryWithCapacity:1];
}
NSDictionary *serviceStatus = [NSDictionary dictionaryWithObjectsAndKeys:
(id)kCFBooleanTrue, #"enabled_context_menu",
(id)kCFBooleanTrue, #"enabled_services_menu",
keyEquivalent, #"key_equivalent", nil];
[pbsAllServicesNew setObject:serviceStatus forKey:(NSString*)serviceStatusName];
CFPreferencesSetAppValue (
serviceStatusRoot,
(CFPropertyListRef) pbsAllServicesNew,
CFSTR("pbs"));
Boolean result = CFPreferencesAppSynchronize(CFSTR("pbs"));
if (result) {
NSUpdateDynamicServices();
NSLog(#"successfully installed our alt-command-r service");
} else {
NSLog(#"couldn't install our alt-command-r service");
}
If the code succeeds, you can view this in ~/Library/Preferences/pbs.plist
You should see something like:
NSServicesStatus = {
"com.whatever.MyApp - Launch My Service - myServiceMethod" = {
enabled_context_menu = :true;
enabled_services_menu = :true;
key_equivalent = "#~r";
};

Related

Printing with the system dialog when using PMPrintSession

Below is the code I we are using to print on mac. Is there an easy way to allow printing using the system dialog? It looks like at one time PMSessionBeginDocument & PMSessionBeginPage were a thing, but now all I can find is the NoDialog options.
Are these calls still usable with the latest frameworks? Or is there another way to print using the system dialog?
PMPrintSession lPrintSession;
PMCreateSession(&lPrintSession);
PMPrintSettings lPrintSettings;
PMCreatePrintSettings(&lPrintSettings);
PMSessionDefaultPrintSettings(lPrintSession, lPrintSettings);
...
PMSessionSetCurrentPMPrinter(lPrintSession, lPrinter);
...
PMSetPageRange(lPrintSettings, 1, 1);
PMSetCopies(lPrintSettings, inCopies, false);
if (!inUseSystemDialog) {
PMSessionBeginCGDocumentNoDialog(lPrintSession, lPrintSettings, lPageFormat);
PMSessionBeginPageNoDialog(lPrintSession, lPageFormat, NULL);
} else {
// TODO: What do we do here? Are these calls usable?
// PMSessionBeginDocument(lPrintSession, lPrintSettings, lPageFormat);
// PMSessionBeginPage(lPrintSession, lPageFormat, NULL);
}
CGContextRef lGraphics;
PMSessionGetCGGraphicsContext(lPrintSession, &lGraphics);
...
PMSessionEndPageNoDialog(lPrintSession);
PMSessionEndDocumentNoDialog(lPrintSession);
You can run an NSPrintPanel to show the system print dialog. For that, you also need to set up an NSPrintInfo object:
NSPrintInfo* printInfo = [NSPrintInfo new];
// set printInfo.printer if you want to override the default
PMPrintSettings printSettings = printInfo.PMPrintSettings;
// configure printSettings
[printInfo updateFromPMPrintSettings];
PMPageFormat pageFormat = printInfo.PMPageFormat;
// configure pageFormat
[printInfo updateFromPMPageFormat];
Create the panel and run it with that info object:
NSPrintPanel* panel = [NSPrintPanel printPanel];
// configure panel; for example, set its options property
NSInteger result = [panel runModalWithPrintInfo:printInfo];
Use the info as the basis of your print session:
if (result == NSOKButton)
{
PMPrintSession session = printInfo.PMPrintSession;
printSettings = printInfo.PMPrintSettings;
pageFormat = printInfo.PMPageFormat;
PMSessionBeginCGDocumentNoDialog(session, printSettings, pageFormat);
PMSessionBeginPageNoDialog(session, pageFormat, NULL);
CGContextRef lGraphics;
PMSessionGetCGGraphicsContext(session, &lGraphics);
...
PMSessionEndPageNoDialog(session);
PMSessionEndDocumentNoDialog(session);
}

Core Data Cocoa Mac OS delete object Objective-C

I have a menu displaying files that are stored in a Core Data model. I'm able to add a new object to the model and display it on the menu. Now, I would like to delete one file from the menu when right clicking on it and choose delete, everything I tried didn't work so far:
- (IBAction)RemoveSelectedFile:(id)sender {
if ([((NSMenuItem *)sender).menu isEqual:self.fileRecordContextMenu]) {
// get the indices that have been clicked on
NSIndexSet * indices = [self _indexesToProcessForContextMenuForTable:self.fileRecordsTable];
ClassFileRecord * fileRecord = (self.document && self.document.fileRecordsController && self.document.fileRecordsController.arrangedObjects && indices && indices.count > 0) ? [self.document.fileRecordsController.arrangedObjects objectAtIndex:indices.firstIndex] : nil;
if (fileRecord) {
// Path to the file
NSString * u = fileRecord.sourceFilePath;
if (u) {
// Delete Object
}
}
}
}
UPDATE:
I found an easy solution using the NSArrayController:
NSArray * selectedObjects = self.fileRecordsController.selectedObjects;
[self.fileRecordsController removeObjects:selectedObjects];

How to read file comment field

In OS X Finder there is 'Comment' file property. It can be checked in finder by adding 'Comment' column or edited/checked after right clicking on file or folder and selecting 'Get info'.
How to read this value in swift or objective-c?
I checked already NSURL and none of them seems to be the right ones
Do not use the low-level extended attributes API to read Spotlight metadata. There's a proper Spotlight API for that. (It's called the File Metadata API.) Not only is it a pain in the neck, there's no guarantee that Apple will keep using the same extended attribute to store this information.
Use MDItemCreateWithURL() to create an MDItem for the file. Use MDItemCopyAttribute() with kMDItemFinderComment to obtain the Finder comment for the item.
Putting the pieces together (Ken Thomases reading answer above and writing answer link) you can extend URL with a computed property with a getter and a setter to read/write comments to your files:
update: Xcode 8.2.1 • Swift 3.0.2
extension URL {
var finderComment: String? {
get {
guard isFileURL else { return nil }
return MDItemCopyAttribute(MDItemCreateWithURL(kCFAllocatorDefault, self as CFURL), kMDItemFinderComment) as? String
}
set {
guard isFileURL, let newValue = newValue else { return }
let script = "tell application \"Finder\"\n" +
String(format: "set filePath to \"%#\" as posix file \n", absoluteString) +
String(format: "set comment of (filePath as alias) to \"%#\" \n", newValue) +
"end tell"
guard let appleScript = NSAppleScript(source: script) else { return }
var error: NSDictionary?
appleScript.executeAndReturnError(&error)
if let error = error {
print(error[NSAppleScript.errorAppName] as! String)
print(error[NSAppleScript.errorBriefMessage] as! String)
print(error[NSAppleScript.errorMessage] as! String)
print(error[NSAppleScript.errorNumber] as! NSNumber)
print(error[NSAppleScript.errorRange] as! NSRange)
}
}
}
}
As explained in the various answers to Mac OS X : add a custom meta data field to any file,
Finder comments can be read and set programmatically with getxattr() and setxattr(). They are stored as extended attribute
"com.apple.metadata:kMDItemFinderComment", and the value is a property
list.
This works even for files not indexed by Spotlight, such as those on a network server volume.
From the Objective-C code here
and here I made this simple Swift function
to read the Finder comment (now updated for Swift 4 and later):
func finderComment(url : URL) -> String? {
let XAFinderComment = "com.apple.metadata:kMDItemFinderComment"
let data = url.withUnsafeFileSystemRepresentation { fileSystemPath -> Data? in
// Determine attribute size:
let length = getxattr(fileSystemPath, XAFinderComment, nil, 0, 0, 0)
guard length >= 0 else { return nil }
// Create buffer with required size:
var data = Data(count: length)
// Retrieve attribute:
let result = data.withUnsafeMutableBytes { [count = data.count] in
getxattr(fileSystemPath, XAFinderComment, $0.baseAddress, count, 0, 0)
}
guard result >= 0 else { return nil }
return data
}
// Deserialize to String:
guard let data = data, let comment = try? PropertyListSerialization.propertyList(from: data,
options: [], format: nil) as? String else {
return nil
}
return comment
}
Example usage:
let url = URL(fileURLWithPath: "/path/to/file")
if let comment = finderComment(url: url) {
print(comment)
}
The function returns an optional string which is nil if the file
has no Finder comment, or if anything went wrong while retrieving it.

How to retrieve vCard in xmpp , which delegate method and flow is use for get vCard in cocoa os x app?

I have implement xmpp framework in my cocoa os x application. currently am working on vCard. i have done work on set all required field's data of login user and it stored successfully, but i have no proper solution for how to get login user's stored vcard and am not aware of it. Please give me solution for this problem. i have been suffering for this from last 3 days. help me as soon as
Thanx in advance
i have used below code to set vCard field
dispatch_queue_t queue = dispatch_queue_create("queue", DISPATCH_QUEUE_PRIORITY_DEFAULT);
dispatch_async(queue, ^
{
XMPPvCardCoreDataStorage *xmppvCardStorage = [XMPPvCardCoreDataStorage sharedInstance];
XMPPvCardTempModule *xmppvCardTempModule = [[XMPPvCardTempModule alloc] initWithvCardStorage:xmppvCardStorage];
[xmppvCardTempModule activate:[self xmppStream]];
XMPPvCardTemp *myvCardTemp = [xmppvCardTempModule myvCardTemp];
if (!myvCardTemp)
{
NSXMLElement *vCardXML = [NSXMLElement elementWithName:#"vCard" xmlns:#"vcard-temp"];
XMPPvCardTemp *newvCardTemp = [XMPPvCardTemp vCardTempFromElement:vCardXML];
[newvCardTemp setName:#"vCard"];
[newvCardTemp setNickname:lbl.stringValue];
[newvCardTemp setFormattedName:#"abc"];
[newvCardTemp setDesc:lbl_abt.stringValue];
[xmppvCardTempModule updateMyvCardTemp:newvCardTemp];
}
else
{
[myvCardTemp setName:#"vCard"];
[myvCardTemp setNickname:lbl.stringValue];
[myvCardTemp setFormattedName:#"abc"];
[myvCardTemp setDesc:lbl_abt.stringValue];
[xmppvCardTempModule updateMyvCardTemp:myvCardTemp];
}
});
}
And i also tried below code to retrieve vCard
/* XMPPvCardTempModule *xmppvCardTempModule;
XMPPvCardTemp *vCard =[xmppvCardTempModule vCardTempForJID:[XMPPJID jidWithString:#"xxxx"] shouldFetch:YES];
NSLog(#"V CARD :%#",vCard.nickname);*/
XMPPvCardCoreDataStorage* xmppvCardStorage = [XMPPvCardCoreDataStorage sharedInstance];
XMPPvCardTempModule* m = [[XMPPvCardTempModule alloc]initWithvCardStorage:xmppvCardStorage];
[m fetchvCardTempForJID:[XMPPJID jidWithString:#"xxxx"]ignoreStorage:YES];
NSLog(#"%#",xmppvCardStorage.description);
Please suggest me proper way to solve this problem :

Get the Date, when the file was added to the mobile documents folder?

Is it possible/Is there a file attribute to get the date, when a file was added to the mobile documents folder/icloud?
I've found the answer in another question here at stackoverflow:
Where does the Finder obtain the "date added" of an item in a folder?
The date-added attribute is in the Spotlight metadata:
NSDate *dateAdded(NSURL *url)
{
NSDate *rslt = nil;
MDItemRef inspectedRef = nil;
inspectedRef = MDItemCreateWithURL(kCFAllocatorDefault, (CFURLRef)url);
if (inspectedRef){
CFTypeRef cfRslt = MDItemCopyAttribute(inspectedRef, (CFStringRef)#"kMDItemDateAdded");
if (cfRslt) {
rslt = (NSDate *)cfRslt;
}
}
return rslt;
}