cocoadock 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b4e20bb778be0635e07ee877840cee13af33fce3d809dcb561b73fad2d6b753b
4
- data.tar.gz: '039819d9514d0a4c74b466363ba864cdb25489f28317e4f9c862d6076083304c'
3
+ metadata.gz: dd9b3b0da8d7451cf3d5bb0c8b3f7094aa8d3e799e98b496bca70bd33c288d85
4
+ data.tar.gz: 0f485accdde26fe341e0f1a4fbc789e6148b54720deb57f07a5475cc1e6aed6a
5
5
  SHA512:
6
- metadata.gz: e058a48e31c7a80115fc71aedf39213e2644d5fad98604e5106ae59ff0e36ba237db1d6392077df87ae380e4c1934241296685fede7f0a9faa93087157ba4f0a
7
- data.tar.gz: b0b07f1c8f8812e0f0f7052be2dac354e8e538e74b6f3805959e4a4c16061097778d722563ef69f6bcdc9c775f9be58cd7d9a9ad379216be3ce70aa9e432e7a0
6
+ metadata.gz: 2338f7ca8fa704d59d04d70fd85b49dbeafed777fea482eb2e529238fe9a013262584779fd631b28b36ae744dd2b87556a633aed16bedf8173d633487274ca77
7
+ data.tar.gz: de6373a9858118b3812f7287061775a298c54166f6322416c7c484b2dc3f3926deb4e346343c00c571a7d421bd06bda283744eec77d8bf4ac91f3e6dc34f4e40
@@ -5,71 +5,235 @@ VALUE rb_mCocoadock;
5
5
  VALUE rb_mCocoadockClass;
6
6
 
7
7
  VALUE cocoadock_initialize(VALUE self);
8
+ VALUE cocoadock_is_app_to_dock(VALUE self, VALUE path);
8
9
  VALUE cocoadock_add_app_to_dock(VALUE self, VALUE path);
9
10
  VALUE cocoadock_remove_app_from_dock(VALUE self, VALUE path);
10
11
 
11
- void addAppToDock(const char *app_path) {
12
- NSString *appPath = [[NSString alloc] initWithCString:app_path encoding:NSUTF8StringEncoding];
12
+ BOOL IsAppInDock(NSString *bundleID) {
13
+ if (!bundleID) return NO;
14
+
15
+ // Load Dock plist directly
16
+ NSString *plistPath = [@"~/Library/Preferences/com.apple.dock.plist" stringByExpandingTildeInPath];
17
+ NSDictionary *dockPlist = [NSDictionary dictionaryWithContentsOfFile:plistPath];
18
+ NSArray *persistentApps = dockPlist[@"persistent-apps"];
19
+ if (!persistentApps) return NO;
20
+
21
+ for (NSDictionary *item in persistentApps) {
22
+ NSDictionary *tileData = item[@"tile-data"];
23
+ if (!tileData) continue;
24
+
25
+ // 1️⃣ Check bundle identifier
26
+ NSString *appBundleID = tileData[@"bundle-identifier"];
27
+ if ([appBundleID isEqualToString:bundleID]) {
28
+ return YES;
29
+ }
30
+
31
+ // 2️⃣ Fallback: check file path
32
+ NSDictionary *fileData = tileData[@"file-data"];
33
+ NSString *path = fileData[@"_CFURLString"];
34
+ if (!path) continue;
35
+
36
+ // Handle possible URL or relative path
37
+ if (![path hasPrefix:@"/"]) {
38
+ NSURL *url = [NSURL URLWithString:path];
39
+ path = url.path;
40
+ }
41
+
42
+ if (!path) continue;
13
43
 
14
- // Convert to file URL with percent escapes
15
- NSURL *appURL = [NSURL fileURLWithPath:appPath];
16
- NSString *urlString = appURL.absoluteString;
17
-
18
- // Now form the Dock entry
19
- NSString *dockEntry = [NSString stringWithFormat:
20
- @"'<dict>"
21
- "<key>tile-data</key><dict>"
22
- "<key>file-data</key><dict>"
23
- "<key>_CFURLString</key><string>%@</string>"
24
- "<key>_CFURLStringType</key><integer>15</integer>"
25
- "</dict>"
26
- "</dict>"
27
- "</dict>'", urlString];
28
-
29
- NSString *command = [NSString stringWithFormat:
30
- @"defaults write com.apple.dock persistent-apps -array-add %@ && killall Dock", dockEntry];
31
-
32
- // Execute the command
33
- NSTask *task = [[NSTask alloc] init];
34
- task.launchPath = @"/bin/sh";
35
- task.arguments = @[@"-c", command];
36
- [task launch];
44
+ // Resolve symlinks
45
+ path = [[NSURL fileURLWithPath:path] URLByResolvingSymlinksInPath].path;
46
+
47
+ NSBundle *bundle = [NSBundle bundleWithPath:path];
48
+ if (bundle && [bundle.bundleIdentifier isEqualToString:bundleID]) {
49
+ return YES;
50
+ }
51
+ }
52
+
53
+ return NO;
37
54
  }
38
55
 
39
- void removeAppFromDock(const char* app_path) {
40
- NSString *appPath = [[NSString alloc] initWithCString:app_path encoding:NSUTF8StringEncoding];
41
- NSURL *appURL = [NSURL fileURLWithPath:appPath];
42
- NSString *urlString = appURL.absoluteString;
43
-
44
- // Step 1: Export current Dock preferences
45
- NSString *exportPlistCmd = @"defaults export com.apple.dock - > /tmp/com.apple.dock.plist";
46
- system([exportPlistCmd UTF8String]);
47
-
48
- // Step 2: Load the plist
49
- NSMutableDictionary *dockPlist = [NSMutableDictionary dictionaryWithContentsOfFile:@"/tmp/com.apple.dock.plist"];
50
- NSMutableArray *persistentApps = [dockPlist[@"persistent-apps"] mutableCopy];
51
- if (!persistentApps) return;
52
-
53
- // Step 3: Filter out the app by matching its _CFURLString
54
- NSMutableArray *filtered = [NSMutableArray array];
55
- for (NSDictionary *entry in persistentApps) {
56
- NSDictionary *tileData = entry[@"tile-data"];
56
+
57
+ NSURL *AppURLForBundleID(NSString *bundleID) {
58
+ if (!bundleID) return nil;
59
+
60
+ CFArrayRef urls = LSCopyApplicationURLsForBundleIdentifier(
61
+ (__bridge CFStringRef)bundleID,
62
+ NULL
63
+ );
64
+
65
+ if (!urls || CFArrayGetCount(urls) == 0) {
66
+ if (urls) CFRelease(urls);
67
+ return nil;
68
+ }
69
+
70
+ CFURLRef firstURL = CFArrayGetValueAtIndex(urls, 0);
71
+ NSURL *appURL = (__bridge NSURL *)firstURL;
72
+
73
+ CFRelease(urls); // release the array, not the item
74
+ return appURL;
75
+ }
76
+
77
+ NSDictionary *DockTileForAppURL(NSURL *appURL) {
78
+ if (!appURL) return nil;
79
+
80
+ NSDictionary *fileData = @{
81
+ @"_CFURLString": appURL.absoluteString,
82
+ @"_CFURLStringType": @15
83
+ };
84
+
85
+ NSDictionary *tileData = @{
86
+ @"file-data": fileData
87
+ };
88
+
89
+ return @{
90
+ @"tile-type": @"file-tile",
91
+ @"tile-data": tileData
92
+ };
93
+ }
94
+
95
+ void AddDockAppByBundleID(NSString *bundleID) {
96
+ CFArrayRef persistentApps = CFPreferencesCopyAppValue(
97
+ CFSTR("persistent-apps"),
98
+ CFSTR("com.apple.dock")
99
+ );
100
+
101
+ if (!persistentApps) {
102
+ NSLog(@"Failed to read Dock preferences");
103
+ return;
104
+ }
105
+
106
+ NSMutableArray *apps =
107
+ [(__bridge NSArray *)persistentApps mutableCopy];
108
+
109
+ if (!apps || !bundleID) return;
110
+
111
+ NSURL *appURL = AppURLForBundleID(bundleID);
112
+ if (!appURL) {
113
+ NSLog(@"Could not resolve app for bundle ID %@", bundleID);
114
+ return;
115
+ }
116
+
117
+ NSString *targetPath = appURL.path;
118
+
119
+ // Prevent duplicates
120
+ for (NSDictionary *item in apps) {
121
+ NSDictionary *fileData = item[@"tile-data"][@"file-data"];
122
+ NSString *urlString = fileData[@"_CFURLString"];
123
+ if (!urlString) continue;
124
+
125
+ NSString *path = [[NSURL URLWithString:urlString] path];
126
+ if ([path isEqualToString:targetPath]) {
127
+ return;
128
+ }
129
+ }
130
+
131
+ NSDictionary *dockItem = DockTileForAppURL(appURL);
132
+ if (dockItem) {
133
+ [apps addObject:dockItem];
134
+ }
135
+
136
+ CFPreferencesSetAppValue(
137
+ CFSTR("persistent-apps"),
138
+ (__bridge CFArrayRef)apps,
139
+ CFSTR("com.apple.dock")
140
+ );
141
+
142
+ CFPreferencesAppSynchronize(CFSTR("com.apple.dock"));
143
+
144
+ CFRelease(persistentApps);
145
+ }
146
+
147
+ void RemoveDockAppByBundleID(NSString *bundleID) {
148
+ CFArrayRef persistentApps = CFPreferencesCopyAppValue(
149
+ CFSTR("persistent-apps"),
150
+ CFSTR("com.apple.dock")
151
+ );
152
+
153
+ NSMutableArray *apps =
154
+ [(__bridge NSArray *)persistentApps mutableCopy];
155
+
156
+ if (!apps || !bundleID) {
157
+ return;
158
+ }
159
+
160
+ for (NSInteger i = apps.count - 1; i >= 0; i--) {
161
+ NSDictionary *item = apps[i];
162
+ NSDictionary *tileData = item[@"tile-data"];
163
+
164
+ NSString *dockBundleID = tileData[@"bundle-identifier"];
165
+ if ([dockBundleID isEqualToString:bundleID]) {
166
+ [apps removeObjectAtIndex:i];
167
+ continue;
168
+ }
169
+
57
170
  NSDictionary *fileData = tileData[@"file-data"];
58
- NSString *entryURL = fileData[@"_CFURLString"];
59
- if (![entryURL isEqualToString:urlString]) {
60
- [filtered addObject:entry];
171
+ NSString *urlString = fileData[@"_CFURLString"];
172
+ if (urlString) {
173
+ NSString *path = [[NSURL URLWithString:urlString] path];
174
+ if ([path containsString:bundleID]) {
175
+ [apps removeObjectAtIndex:i];
176
+ }
177
+ }
178
+ }
179
+
180
+ CFPreferencesSetAppValue(
181
+ CFSTR("persistent-apps"),
182
+ (__bridge CFArrayRef)apps,
183
+ CFSTR("com.apple.dock")
184
+ );
185
+
186
+ CFPreferencesAppSynchronize(CFSTR("com.apple.dock"));
187
+
188
+ if (persistentApps) {
189
+ CFRelease(persistentApps);
190
+ }
191
+ }
192
+
193
+ BOOL isAppInDock(const char *app_path) {
194
+ NSString *appPath = [[NSString alloc] initWithCString:app_path encoding:NSUTF8StringEncoding];
195
+ NSBundle *bundle = [NSBundle bundleWithPath:appPath];
196
+
197
+ if (!bundle) {
198
+ NSLog(@"Not a valid app bundle");
199
+ return NO;
200
+ }
201
+
202
+ NSString *bundleID = bundle.bundleIdentifier;
203
+
204
+ return IsAppInDock(bundleID);
205
+ }
206
+
207
+ void addAppToDock(const char *app_path) {
208
+ @autoreleasepool {
209
+ NSString *appPath = [[NSString alloc] initWithCString:app_path encoding:NSUTF8StringEncoding];
210
+
211
+ NSBundle *bundle = [NSBundle bundleWithPath:appPath];
212
+
213
+ if (!bundle) {
214
+ NSLog(@"Not a valid app bundle");
215
+ return;
61
216
  }
217
+
218
+ NSString *bundleID = bundle.bundleIdentifier;
219
+ AddDockAppByBundleID(bundleID);
62
220
  }
221
+ }
63
222
 
64
- // Step 4: Save updated plist and re-import
65
- dockPlist[@"persistent-apps"] = filtered;
66
- [dockPlist writeToFile:@"/tmp/com.apple.dock.plist" atomically:YES];
223
+ void removeAppFromDock(const char* app_path) {
224
+ @autoreleasepool {
225
+ NSString *appPath = [[NSString alloc] initWithCString:app_path encoding:NSUTF8StringEncoding];
67
226
 
68
- NSString *importCmd = @"defaults import com.apple.dock /tmp/com.apple.dock.plist";
69
- system([importCmd UTF8String]);
227
+ NSBundle *bundle = [NSBundle bundleWithPath:appPath];
70
228
 
71
- // Step 5: Restart Dock
72
- system("killall Dock");
229
+ if (!bundle) {
230
+ NSLog(@"Not a valid app bundle");
231
+ return;
232
+ }
233
+
234
+ NSString *bundleID = bundle.bundleIdentifier;
235
+ RemoveDockAppByBundleID(bundleID);
236
+ }
73
237
  }
74
238
 
75
239
  RUBY_FUNC_EXPORTED void
@@ -79,6 +243,7 @@ Init_cocoadock(void)
79
243
  rb_mCocoadockClass = rb_define_class_under(rb_mCocoadock, "CocoaDock", rb_cObject);
80
244
  rb_define_method(rb_mCocoadockClass, "initialize", cocoadock_initialize, 0);
81
245
  rb_define_method(rb_mCocoadockClass, "add_app", cocoadock_add_app_to_dock, 1);
246
+ rb_define_method(rb_mCocoadockClass, "app_in_dock?", cocoadock_is_app_to_dock, 1);
82
247
  rb_define_method(rb_mCocoadockClass, "remove_app", cocoadock_remove_app_from_dock, 1);
83
248
  }
84
249
 
@@ -87,9 +252,17 @@ VALUE cocoadock_initialize(VALUE self) {
87
252
  return self;
88
253
  }
89
254
 
255
+ VALUE cocoadock_is_app_to_dock(VALUE self, VALUE path) {
256
+ const char *c_app_path = StringValueCStr(path);
257
+ BOOL result = isAppInDock(c_app_path);
258
+ if (result == YES) {
259
+ return Qtrue;
260
+ }
261
+ return Qfalse;
262
+ }
263
+
90
264
  VALUE cocoadock_add_app_to_dock(VALUE self, VALUE path) {
91
265
  const char *c_app_path = StringValueCStr(path);
92
- //NSString *appPath = [[NSString alloc] initWithCString:c_app_path encoding:NSUTF8StringEncoding];
93
266
  addAppToDock(c_app_path);
94
267
  }
95
268
 
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Cocoadock
4
- VERSION = "0.1.0"
4
+ VERSION = "0.2.0"
5
5
  end
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: cocoadock
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Tommy Jeff
8
8
  bindir: exe
9
9
  cert_chain: []
10
- date: 2025-07-02 00:00:00.000000000 Z
10
+ date: 2026-02-11 00:00:00.000000000 Z
11
11
  dependencies: []
12
12
  description: Cocoa dock functions for Ruby on macOS
13
13
  email: