FireMonkey и MacApi

в 3:44, , рубрики: Delphi, Delphi XE2, FireMonkey, mac os x, метки: , , ,

Многие из нас знают, что в Delphi XE2 от Embarcadero появилась платформа FireMonkey, позволяющая писать приложения сразу под Windows, Mac OS X и iOS. А в дальнейшем нам обещают расширить список поддерживаемых операционных систем.

Сам я не прикасался к программированию на Delphi порядка 8 лет, но когда встала задача написания программы-клиента к моему онлайн сервису, мой выбор остановился именно на платформе FireMonkey. Желание один раз написать ПО сразу под все ОС меня подкупило, так как времени на изучение новых языков программирования под каждую ОС у меня просто нет.

Программы-клиенты имеют одну особенность, они обычно прячутся в системные треи около часов и запускаются при старте ОС. Все эти мелкие фишки реализуются за счет API операционной системы. Если при реализации всех этих фишек в Windows у меня особых проблем не возникло, то с реализацией их на Mac OS X мне пришлось долго возиться.

Если для использования WinApi в сети существует множество примеров на delphi, то вот для использования MacApi их почти нет. Но существует множество примеров использования MacApi на Objective-C и мы можем их брать за основу, этакие карточки логики, для написания своего кода на Delphi.

Возьмем как пример реализацию автозапуска программы при старте ОС в Mac OS X. Вот тут я нашел следующий пример на Objective-C:

@implementation NSUserDefaults (Additions)

- (BOOL)addApplicationToLoginItems:(NSString *)path {
    NSDictionary *domain = [self persistentDomainForName:@"loginwindow"];
    NSArray *apps = [domain objectForKey:@"AutoLaunchedApplicationDictionary"];
    NSArray *matchingApps = [apps filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:@"Path CONTAINS %@", path]];
    if ([matchingApps count] == 0) {
        NSMutableDictionary *newDomain = [domain mutableCopy];
        NSMutableArray *newApps = [[apps mutableCopy] autorelease];
        NSDictionary *app = [NSDictionary dictionaryWithObjectsAndKeys:path, @"Path", [NSNumber numberWithBool:NO], @"Hide", nil];
        [newApps addObject:app];
        [newDomain setObject:newApps forKey:@"AutoLaunchedApplicationDictionary"];
        [self setPersistentDomain:newDomain forName:@"loginwindow"];
        return [self synchronize];
    }
    return NO;
}

- (BOOL)removeApplicationFromLoginItems:(NSString *)name {
    NSDictionary *domain = [self persistentDomainForName:@"loginwindow"];
    NSArray *apps = [domain objectForKey:@"AutoLaunchedApplicationDictionary"];
    NSArray *newApps = [apps filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:@"not Path CONTAINS %@", name]];
    if (![apps isEqualToArray:newApps]) {
        NSMutableDictionary *newDomain = [domain mutableCopy];
        [newDomain setObject:newApps forKey:@"AutoLaunchedApplicationDictionary"];
        [self setPersistentDomain:newDomain forName:@"loginwindow"];
        return [self synchronize];
    }
    return NO;
}

@end

Это две функции, которые добавляют и удаляют приложение из автозапуска в Mac OS X. Теперь, используя их за основу, я могу написать свой код на Delphi для реализации этой фишки у себя в программе:

procedure TForm1.save_btnClick(Sender: TObject);
var
    i: integer;
    UserDefaults:NSUserDefaults;
    domain: NSDictionary;
    my_app: NSDictionary;
    apps_autoloadlist_p: Pointer;
    apps_autoloadlist: NSArray;
    apps: NSArray;
    apps_path: NSString;
    in_autoloadlist:boolean;
    newDomain:NSMutableDictionary;
    newApps:NSMutableArray;
    newValue:NSMutableArray;
    newKey:NSMutableArray;
    newValue_a:NSArray;
    newKey_a:NSArray;
begin
    in_autoloadlist:=false;
{ Делаю проверку, есть ли моя программа в автозапуске}
{ @implementation NSUserDefaults (Additions) }
    UserDefaults:=TNSUserDefaults.Wrap(TNSUserDefaults.OCClass.standardUserDefaults);

{ NSDictionary *domain = [self persistentDomainForName:@"loginwindow"]; }
    domain:=UserDefaults.persistentDomainForName(NSSTR('loginwindow'));

{ NSArray *apps = [domain objectForKey:@"AutoLaunchedApplicationDictionary"]; }
    apps_autoloadlist_p:=domain.objectForKey(NSSTR('AutoLaunchedApplicationDictionary').init);
    apps_autoloadlist:= TNSArray.Wrap(apps_autoloadlist_p);

{
Далее должна быть строка 
NSArray *matchingApps = [apps filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:@"Path CONTAINS %@", path]];

Но я реализовал проверку наличия программы в автозапуске обычным перебором
}
    if apps_autoloadlist.count>0 then
    begin
      for i := 0 to apps_autoloadlist.count-1 do
      begin
          apps:=TNSArray.Wrap(apps_autoloadlist.objectAtIndex(i));
          apps_path:=TNSString.Wrap(apps.valueForKey(NSSTR('Path')));
         // В переменной main.prgm_path хранится путь к моей программе
          if String(apps_path.UTF8String)=main.prgm_path then in_autoloadlist:=true;
      end;
    end;


    if startup_switch.IsChecked then
    begin
    { если у меня в программе выбран параметр автозапуска }
      if not in_autoloadlist then
      begin
      //Если программы нету в автозапуске, то добавляем
{ NSMutableDictionary *newDomain = [domain mutableCopy]; }
      newDomain:=TNSMutableDictionary.Wrap(CFDictionaryCreateMutableCopy(nil, 0, (domain as ILocalObject).GetObjectID));

{ NSMutableArray *newApps = [[apps mutableCopy] autorelease]; }
      newApps:=TNSMutableArray.Wrap(CFArrayCreateMutableCopy(nil, 0, (apps_autoloadlist as ILocalObject).GetObjectID));

{ NSDictionary *app = [NSDictionary dictionaryWithObjectsAndKeys:path, @"Path", [NSNumber numberWithBool:NO], @"Hide", nil]; }
      newValue:=TNSMutableArray.Create;
      newValue.addObject(NSSTR(main.prgm_path).init);
      newValue.addObject(TNSNumber.OCClass.numberWithBool(true));
      newKey:=TNSMutableArray.Create;
      newKey.addObject(NSSTR('Path').init);
      newKey.addObject(NSSTR('Hide').init);
      newValue_a:=TNSArray.Wrap((newValue as ILocalObject).GetObjectID);
      newKey_a:=TNSArray.Wrap((newKey as ILocalObject).GetObjectID);
      my_app:=TNSDictionary.Wrap(TNSDictionary.OCClass.dictionaryWithObjects(newValue_a,newKey_a));

{ [newApps addObject:app]; }
      newApps.addObject((my_app as ILocalObject).GetObjectID);

{ [newDomain setObject:newApps forKey:@"AutoLaunchedApplicationDictionary"]; }
      newDomain.setObject((newApps as ILocalObject).GetObjectID ,NSSTR('AutoLaunchedApplicationDictionary').init);

{ [self setPersistentDomain:newDomain forName:@"loginwindow"]; }
      UserDefaults.setPersistentDomain(TNSDictionary.Wrap((newDomain as ILocalObject).GetObjectID),NSSTR('loginwindow'));

{ return [self synchronize]; }
      UserDefaults.synchronize;
      end;
    end
    else
    begin
    { если у меня в программе не выбран параметр автозапуска }
      if in_autoloadlist then
      begin
      // Если программа присутствует в автозапуске, то удалям её
{ NSMutableDictionary *newDomain = [domain mutableCopy]; }
      newDomain:=TNSMutableDictionary.Wrap(CFDictionaryCreateMutableCopy(nil, 0, (domain as ILocalObject).GetObjectID));

{ NSMutableArray *newApps = [[apps mutableCopy] autorelease]; }
      newApps:=TNSMutableArray.Wrap(CFArrayCreateMutableCopy(nil, 0, (apps_autoloadlist as ILocalObject).GetObjectID));

{ Далее пробегаемся по списку и удаляем записи с автозапуском моей программы, в оригинале это NSArray *newApps = [apps filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:@"not Path CONTAINS %@", name]]; }
      if newApps.count>0 then
        begin
          for i := newApps.count-1 DownTo 0 do
          begin
              apps:=TNSArray.Wrap(apps_autoloadlist.objectAtIndex(i));
              apps_path:=TNSString.Wrap(apps.valueForKey(NSSTR('Path')));
              if String(apps_path.UTF8String)=main.prgm_path then
                begin
                  newApps.removeObjectAtIndex(i);
                end;
          end;
        end;

{ [newDomain setObject:newApps forKey:@"AutoLaunchedApplicationDictionary"]; }
      newDomain.setObject((newApps as ILocalObject).GetObjectID ,NSSTR('AutoLaunchedApplicationDictionary').init);

{ [self setPersistentDomain:newDomain forName:@"loginwindow"]; }
      UserDefaults.setPersistentDomain(TNSDictionary.Wrap((newDomain as ILocalObject).GetObjectID),NSSTR('loginwindow'));

{ return [self synchronize]; }
      UserDefaults.synchronize;
      end;
    end;

end;

Используя за основу 2 функции на Objective-C я написал одну процедуру на Delphi, которая реализует возможность автозапуска моей программы в Mac OS X.

Надеюсь, этот пример даст вам наиболее полное представление о возможности работы с MacApi из Delphi.

Автор: neoksi

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js