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 +4 -4
- data/ext/cocoadock/cocoadock.m +227 -54
- data/lib/cocoadock/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: dd9b3b0da8d7451cf3d5bb0c8b3f7094aa8d3e799e98b496bca70bd33c288d85
|
|
4
|
+
data.tar.gz: 0f485accdde26fe341e0f1a4fbc789e6148b54720deb57f07a5475cc1e6aed6a
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 2338f7ca8fa704d59d04d70fd85b49dbeafed777fea482eb2e529238fe9a013262584779fd631b28b36ae744dd2b87556a633aed16bedf8173d633487274ca77
|
|
7
|
+
data.tar.gz: de6373a9858118b3812f7287061775a298c54166f6322416c7c484b2dc3f3926deb4e346343c00c571a7d421bd06bda283744eec77d8bf4ac91f3e6dc34f4e40
|
data/ext/cocoadock/cocoadock.m
CHANGED
|
@@ -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
|
-
|
|
12
|
-
|
|
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
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
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
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
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 *
|
|
59
|
-
if (
|
|
60
|
-
[
|
|
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
|
-
|
|
65
|
-
|
|
66
|
-
|
|
223
|
+
void removeAppFromDock(const char* app_path) {
|
|
224
|
+
@autoreleasepool {
|
|
225
|
+
NSString *appPath = [[NSString alloc] initWithCString:app_path encoding:NSUTF8StringEncoding];
|
|
67
226
|
|
|
68
|
-
|
|
69
|
-
system([importCmd UTF8String]);
|
|
227
|
+
NSBundle *bundle = [NSBundle bundleWithPath:appPath];
|
|
70
228
|
|
|
71
|
-
|
|
72
|
-
|
|
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
|
|
data/lib/cocoadock/version.rb
CHANGED
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.
|
|
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:
|
|
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:
|