cocoadock 0.1.0 → 0.3.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: 854f6ae805d2626929da28f513cd471e2c252be893a0c062823f52d157b0bb44
4
+ data.tar.gz: 632c33c8faaa92152b5a68a8d9affe598fe8afcbf0b3bc5691f19def81b94ae3
5
5
  SHA512:
6
- metadata.gz: e058a48e31c7a80115fc71aedf39213e2644d5fad98604e5106ae59ff0e36ba237db1d6392077df87ae380e4c1934241296685fede7f0a9faa93087157ba4f0a
7
- data.tar.gz: b0b07f1c8f8812e0f0f7052be2dac354e8e538e74b6f3805959e4a4c16061097778d722563ef69f6bcdc9c775f9be58cd7d9a9ad379216be3ce70aa9e432e7a0
6
+ metadata.gz: b3688a9af53f4d84559ac1374485dce4a38bf020a86427a0d7f2d5ce19b9eafcca844f7d630a568e42204b3401a4c44e592f95b2b57d19e1581af1474297ebe6
7
+ data.tar.gz: 6547eff88bb43c512e2879439c9891d5dcfed40ae8afe901c0582d6145fa2d6f1974f66360b6d29b41a166892690985e1b8b826c07e041c8c84ad8d040b17c75
@@ -5,71 +5,302 @@ 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);
11
+ VALUE cocoadock_remove_others_in_dock(VALUE self);
10
12
 
11
- void addAppToDock(const char *app_path) {
12
- NSString *appPath = [[NSString alloc] initWithCString:app_path encoding:NSUTF8StringEncoding];
13
+ NSMutableArray *LoadDockOthers(void) {
14
+ CFArrayRef persistentOthers = CFPreferencesCopyAppValue(
15
+ CFSTR("persistent-others"),
16
+ CFSTR("com.apple.dock")
17
+ );
13
18
 
14
- // Convert to file URL with percent escapes
15
- NSURL *appURL = [NSURL fileURLWithPath:appPath];
16
- NSString *urlString = appURL.absoluteString;
19
+ if (!persistentOthers) {
20
+ return [NSMutableArray array];
21
+ }
17
22
 
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];
23
+ NSMutableArray *others =
24
+ [(__bridge NSArray *)persistentOthers mutableCopy];
28
25
 
29
- NSString *command = [NSString stringWithFormat:
30
- @"defaults write com.apple.dock persistent-apps -array-add %@ && killall Dock", dockEntry];
26
+ CFRelease(persistentOthers);
27
+ return others;
28
+ }
31
29
 
32
- // Execute the command
33
- NSTask *task = [[NSTask alloc] init];
34
- task.launchPath = @"/bin/sh";
35
- task.arguments = @[@"-c", command];
36
- [task launch];
30
+ void SaveDockOthers(NSArray *others) {
31
+ CFPreferencesSetAppValue(
32
+ CFSTR("persistent-others"),
33
+ (__bridge CFArrayRef)others,
34
+ CFSTR("com.apple.dock")
35
+ );
36
+
37
+ CFPreferencesAppSynchronize(CFSTR("com.apple.dock"));
38
+
39
+ system("killall Dock");
37
40
  }
38
41
 
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"];
42
+ BOOL RemoveOthersInDock(void) {
43
+
44
+ int attempts = 0;
45
+
46
+ while (attempts < 3) {
47
+
48
+ NSMutableArray *others = LoadDockOthers();
49
+
50
+ if (others.count == 0) {
51
+ return YES; // Already empty
52
+ }
53
+
54
+ [others removeAllObjects];
55
+
56
+ SaveDockOthers(others);
57
+
58
+ // Give Dock time to restart and commit
59
+ [NSThread sleepForTimeInterval:0.25];
60
+
61
+ // Verify
62
+ NSMutableArray *verify = LoadDockOthers();
63
+ if (verify.count == 0) {
64
+ return YES;
65
+ }
66
+
67
+ attempts++;
68
+ [NSThread sleepForTimeInterval:0.1];
69
+ }
70
+
71
+ return NO;
72
+ }
73
+
74
+
75
+ BOOL IsAppInDock(NSString *bundleID) {
76
+ if (!bundleID) return NO;
77
+
78
+ // Load Dock plist directly
79
+ NSString *plistPath = [@"~/Library/Preferences/com.apple.dock.plist" stringByExpandingTildeInPath];
80
+ NSDictionary *dockPlist = [NSDictionary dictionaryWithContentsOfFile:plistPath];
81
+ NSArray *persistentApps = dockPlist[@"persistent-apps"];
82
+ if (!persistentApps) return NO;
83
+
84
+ for (NSDictionary *item in persistentApps) {
85
+ NSDictionary *tileData = item[@"tile-data"];
86
+ if (!tileData) continue;
87
+
88
+ // 1️⃣ Check bundle identifier
89
+ NSString *appBundleID = tileData[@"bundle-identifier"];
90
+ if ([appBundleID isEqualToString:bundleID]) {
91
+ return YES;
92
+ }
93
+
94
+ // 2️⃣ Fallback: check file path
57
95
  NSDictionary *fileData = tileData[@"file-data"];
58
- NSString *entryURL = fileData[@"_CFURLString"];
59
- if (![entryURL isEqualToString:urlString]) {
60
- [filtered addObject:entry];
96
+ NSString *path = fileData[@"_CFURLString"];
97
+ if (!path) continue;
98
+
99
+ // Handle possible URL or relative path
100
+ if (![path hasPrefix:@"/"]) {
101
+ NSURL *url = [NSURL URLWithString:path];
102
+ path = url.path;
103
+ }
104
+
105
+ if (!path) continue;
106
+
107
+ // Resolve symlinks
108
+ path = [[NSURL fileURLWithPath:path] URLByResolvingSymlinksInPath].path;
109
+
110
+ NSBundle *bundle = [NSBundle bundleWithPath:path];
111
+ if (bundle && [bundle.bundleIdentifier isEqualToString:bundleID]) {
112
+ return YES;
61
113
  }
62
114
  }
63
115
 
64
- // Step 4: Save updated plist and re-import
65
- dockPlist[@"persistent-apps"] = filtered;
66
- [dockPlist writeToFile:@"/tmp/com.apple.dock.plist" atomically:YES];
116
+ return NO;
117
+ }
67
118
 
68
- NSString *importCmd = @"defaults import com.apple.dock /tmp/com.apple.dock.plist";
69
- system([importCmd UTF8String]);
70
119
 
71
- // Step 5: Restart Dock
72
- system("killall Dock");
120
+ NSURL *AppURLForBundleID(NSString *bundleID) {
121
+ if (!bundleID) return nil;
122
+
123
+ CFArrayRef urls = LSCopyApplicationURLsForBundleIdentifier(
124
+ (__bridge CFStringRef)bundleID,
125
+ NULL
126
+ );
127
+
128
+ if (!urls || CFArrayGetCount(urls) == 0) {
129
+ if (urls) CFRelease(urls);
130
+ return nil;
131
+ }
132
+
133
+ CFURLRef firstURL = CFArrayGetValueAtIndex(urls, 0);
134
+ NSURL *appURL = (__bridge NSURL *)firstURL;
135
+
136
+ CFRelease(urls); // release the array, not the item
137
+ return appURL;
138
+ }
139
+
140
+ NSDictionary *DockTileForAppURL(NSURL *appURL) {
141
+ if (!appURL) return nil;
142
+
143
+ NSDictionary *fileData = @{
144
+ @"_CFURLString": appURL.absoluteString,
145
+ @"_CFURLStringType": @15
146
+ };
147
+
148
+ NSDictionary *tileData = @{
149
+ @"file-data": fileData
150
+ };
151
+
152
+ return @{
153
+ @"tile-type": @"file-tile",
154
+ @"tile-data": tileData
155
+ };
156
+ }
157
+
158
+ void AddDockAppByBundleID(NSString *bundleID) {
159
+ CFArrayRef persistentApps = CFPreferencesCopyAppValue(
160
+ CFSTR("persistent-apps"),
161
+ CFSTR("com.apple.dock")
162
+ );
163
+
164
+ if (!persistentApps) {
165
+ NSLog(@"Failed to read Dock preferences");
166
+ return;
167
+ }
168
+
169
+ NSMutableArray *apps =
170
+ [(__bridge NSArray *)persistentApps mutableCopy];
171
+
172
+ if (!apps || !bundleID) return;
173
+
174
+ NSURL *appURL = AppURLForBundleID(bundleID);
175
+ if (!appURL) {
176
+ NSLog(@"Could not resolve app for bundle ID %@", bundleID);
177
+ return;
178
+ }
179
+
180
+ NSString *targetPath = appURL.path;
181
+
182
+ // Prevent duplicates
183
+ for (NSDictionary *item in apps) {
184
+ NSDictionary *fileData = item[@"tile-data"][@"file-data"];
185
+ NSString *urlString = fileData[@"_CFURLString"];
186
+ if (!urlString) continue;
187
+
188
+ NSString *path = [[NSURL URLWithString:urlString] path];
189
+ if ([path isEqualToString:targetPath]) {
190
+ return;
191
+ }
192
+ }
193
+
194
+ NSDictionary *dockItem = DockTileForAppURL(appURL);
195
+ if (dockItem) {
196
+ [apps addObject:dockItem];
197
+ }
198
+
199
+ CFPreferencesSetAppValue(
200
+ CFSTR("persistent-apps"),
201
+ (__bridge CFArrayRef)apps,
202
+ CFSTR("com.apple.dock")
203
+ );
204
+
205
+ CFPreferencesAppSynchronize(CFSTR("com.apple.dock"));
206
+
207
+ CFRelease(persistentApps);
208
+ }
209
+
210
+ void RemoveDockAppByBundleID(NSString *bundleID) {
211
+ CFArrayRef persistentApps = CFPreferencesCopyAppValue(
212
+ CFSTR("persistent-apps"),
213
+ CFSTR("com.apple.dock")
214
+ );
215
+
216
+ NSMutableArray *apps =
217
+ [(__bridge NSArray *)persistentApps mutableCopy];
218
+
219
+ if (!apps || !bundleID) {
220
+ return;
221
+ }
222
+
223
+ for (NSInteger i = apps.count - 1; i >= 0; i--) {
224
+ NSDictionary *item = apps[i];
225
+ NSDictionary *tileData = item[@"tile-data"];
226
+
227
+ NSString *dockBundleID = tileData[@"bundle-identifier"];
228
+ if ([dockBundleID isEqualToString:bundleID]) {
229
+ [apps removeObjectAtIndex:i];
230
+ continue;
231
+ }
232
+
233
+ NSDictionary *fileData = tileData[@"file-data"];
234
+ NSString *urlString = fileData[@"_CFURLString"];
235
+ if (urlString) {
236
+ NSString *path = [[NSURL URLWithString:urlString] path];
237
+ if ([path containsString:bundleID]) {
238
+ [apps removeObjectAtIndex:i];
239
+ }
240
+ }
241
+ }
242
+
243
+ CFPreferencesSetAppValue(
244
+ CFSTR("persistent-apps"),
245
+ (__bridge CFArrayRef)apps,
246
+ CFSTR("com.apple.dock")
247
+ );
248
+
249
+ CFPreferencesAppSynchronize(CFSTR("com.apple.dock"));
250
+
251
+ if (persistentApps) {
252
+ CFRelease(persistentApps);
253
+ }
254
+ }
255
+
256
+ BOOL isAppInDock(const char *app_path) {
257
+ NSString *appPath = [[NSString alloc] initWithCString:app_path encoding:NSUTF8StringEncoding];
258
+ NSBundle *bundle = [NSBundle bundleWithPath:appPath];
259
+
260
+ if (!bundle) {
261
+ NSLog(@"Not a valid app bundle");
262
+ return NO;
263
+ }
264
+
265
+ NSString *bundleID = bundle.bundleIdentifier;
266
+
267
+ return IsAppInDock(bundleID);
268
+ }
269
+
270
+ void addAppToDock(const char *app_path) {
271
+ @autoreleasepool {
272
+ NSString *appPath = [[NSString alloc] initWithCString:app_path encoding:NSUTF8StringEncoding];
273
+
274
+ NSBundle *bundle = [NSBundle bundleWithPath:appPath];
275
+
276
+ if (!bundle) {
277
+ NSLog(@"Not a valid app bundle");
278
+ return;
279
+ }
280
+
281
+ NSString *bundleID = bundle.bundleIdentifier;
282
+ AddDockAppByBundleID(bundleID);
283
+ }
284
+ }
285
+
286
+ void removeAppFromDock(const char* app_path) {
287
+ @autoreleasepool {
288
+ NSString *appPath = [[NSString alloc] initWithCString:app_path encoding:NSUTF8StringEncoding];
289
+
290
+ NSBundle *bundle = [NSBundle bundleWithPath:appPath];
291
+
292
+ if (!bundle) {
293
+ NSLog(@"Not a valid app bundle");
294
+ return;
295
+ }
296
+
297
+ NSString *bundleID = bundle.bundleIdentifier;
298
+ RemoveDockAppByBundleID(bundleID);
299
+ }
300
+ }
301
+
302
+ void removeOthersInDock () {
303
+ RemoveOthersInDock();
73
304
  }
74
305
 
75
306
  RUBY_FUNC_EXPORTED void
@@ -79,7 +310,9 @@ Init_cocoadock(void)
79
310
  rb_mCocoadockClass = rb_define_class_under(rb_mCocoadock, "CocoaDock", rb_cObject);
80
311
  rb_define_method(rb_mCocoadockClass, "initialize", cocoadock_initialize, 0);
81
312
  rb_define_method(rb_mCocoadockClass, "add_app", cocoadock_add_app_to_dock, 1);
313
+ rb_define_method(rb_mCocoadockClass, "app_in_dock?", cocoadock_is_app_to_dock, 1);
82
314
  rb_define_method(rb_mCocoadockClass, "remove_app", cocoadock_remove_app_from_dock, 1);
315
+ rb_define_method(rb_mCocoadockClass, "remove_others", cocoadock_remove_others_in_dock, 0);
83
316
  }
84
317
 
85
318
  VALUE cocoadock_initialize(VALUE self) {
@@ -87,9 +320,17 @@ VALUE cocoadock_initialize(VALUE self) {
87
320
  return self;
88
321
  }
89
322
 
323
+ VALUE cocoadock_is_app_to_dock(VALUE self, VALUE path) {
324
+ const char *c_app_path = StringValueCStr(path);
325
+ BOOL result = isAppInDock(c_app_path);
326
+ if (result == YES) {
327
+ return Qtrue;
328
+ }
329
+ return Qfalse;
330
+ }
331
+
90
332
  VALUE cocoadock_add_app_to_dock(VALUE self, VALUE path) {
91
333
  const char *c_app_path = StringValueCStr(path);
92
- //NSString *appPath = [[NSString alloc] initWithCString:c_app_path encoding:NSUTF8StringEncoding];
93
334
  addAppToDock(c_app_path);
94
335
  }
95
336
 
@@ -98,3 +339,7 @@ VALUE cocoadock_remove_app_from_dock(VALUE self, VALUE path) {
98
339
  //NSString *appPath = [[NSString alloc] initWithCString:c_app_path encoding:NSUTF8StringEncoding];
99
340
  removeAppFromDock(c_app_path);
100
341
  }
342
+
343
+ VALUE cocoadock_remove_others_in_dock(VALUE self) {
344
+ removeOthersInDock();
345
+ }
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Cocoadock
4
- VERSION = "0.1.0"
4
+ VERSION = "0.3.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.3.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: