Compare commits
1015
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
634dc9f688
|
||
|
|
da5779f872 | ||
|
|
874971aa47 | ||
|
|
756943f2b5 | ||
|
|
c675f1817f | ||
|
|
c2be713446 | ||
|
|
dc40fa43bc | ||
|
|
2ae7ded357 | ||
|
|
dd15f22031 | ||
|
|
4ecfc4606f | ||
|
|
45457cb313 | ||
|
|
62e0226f01 | ||
|
|
92254c021f | ||
|
|
0aca4d9400 | ||
|
|
2f9fac17b7 | ||
|
|
4a007e88c9 | ||
|
|
885c52567d | ||
|
|
6070f0cbb9 | ||
|
|
d1e1b29623 | ||
|
|
2588101096 | ||
|
|
6313c295f3 | ||
|
|
d3e7529d91 | ||
|
|
287c4a07c4 | ||
|
|
eb95ad2d52 | ||
|
|
b9e871f176 | ||
|
|
39f10cfba3 | ||
|
|
5b590bd6e5 | ||
|
|
0b6d389842 | ||
|
|
adcd7c5f93 | ||
|
|
b2cad34a6a | ||
|
|
76ef4e49a5 | ||
|
|
acef128bbd | ||
|
|
7f2a483ad2 | ||
|
|
0b021beda7 | ||
|
|
4c169ef3d7 | ||
|
|
c7d2d9681e | ||
|
|
edf499d19e | ||
|
|
98a023aa03 | ||
|
|
f5f9034d41 | ||
|
|
e2429096d7 | ||
|
|
4d82a684f3 | ||
|
|
6795ff43fb | ||
|
|
1a914c88f5 | ||
|
|
5c57cc738b | ||
|
|
3dde8a91b6 | ||
|
|
4506c53958 | ||
|
|
f3d7685984 | ||
|
|
e243fb4bff | ||
|
|
9fadf36bf8 | ||
|
|
f3fe535acb | ||
|
|
8124cf5496 | ||
|
|
cddc89dd21 | ||
|
|
83b5ddc091 | ||
|
|
7d60d55395 | ||
|
|
e0b1778db7 | ||
|
|
9a9994bf28 | ||
|
|
a39816e303 | ||
|
|
c143ec2f0c | ||
|
|
83f8ae18fa | ||
|
|
f006b26931 | ||
|
|
e4a306dc8c | ||
|
|
c75aac585a | ||
|
|
60cca89bb8 | ||
|
|
0899f61654 | ||
|
|
3059dc2356 | ||
|
|
5bd746d342 | ||
|
|
9a47672920 | ||
|
|
a354fdecc8 | ||
|
|
d483c5aa88 | ||
|
|
30eb5f2116 | ||
|
|
14acc0ce7d | ||
|
|
04346790b8 | ||
|
|
f49250aa45 | ||
|
|
a8098c39db | ||
|
|
f0e195d0de | ||
|
|
49778089ea | ||
|
|
f4a54c8f1b | ||
|
|
007edc624e | ||
|
|
b6af54425e | ||
|
|
146e2aec46 | ||
|
|
3a48a13cb5 | ||
|
|
910288d149 | ||
|
|
1f2b5a513e | ||
|
|
9147a4a864 | ||
|
|
317a485bcb | ||
|
|
20caf75096 | ||
|
|
873aabec72 | ||
|
|
da9a46bf49 | ||
|
|
9c5b9630e8 | ||
|
|
49a41bd66d | ||
|
|
8e07d261b9 | ||
|
|
d222dbbb10 | ||
|
|
47e6cbd987 | ||
|
|
317d2ff78a | ||
|
|
d5152fba44 | ||
|
|
90cdd3544a | ||
|
|
13340d82f6 | ||
|
|
69f67ffeeb | ||
|
|
fe6e52d691 | ||
|
|
6c35e10a66 | ||
|
|
18b523e458 | ||
|
|
41d608efda | ||
|
|
f1c59a2748 | ||
|
|
0e897b0678 | ||
|
|
7a2a5482ae | ||
|
|
170bd673e0 | ||
|
|
ff1e92303a | ||
|
|
edbbf2082c | ||
|
|
1ab419dd09 | ||
|
|
6bd5f26e1c | ||
|
|
ecafbe7212 | ||
|
|
173950af08 | ||
|
|
5ed6c1d9ce | ||
|
|
630527e0b7 | ||
|
|
a1f8b18888 | ||
|
|
9f3e20a826 | ||
|
|
dbc77fe214 | ||
|
|
be2de1ab83 | ||
|
|
56c1927584 | ||
|
|
7dd11cff01 | ||
|
|
15ede93868 | ||
|
|
4502e8bd15 | ||
|
|
6105bb30ad | ||
|
|
545d81d872 | ||
|
|
3b1c7c76a9 | ||
|
|
8962ad1a54 | ||
|
|
39b818de11 | ||
|
|
b008d63928 | ||
|
|
de9aaa1fc1 | ||
|
|
739649a749 | ||
|
|
2adf741355 | ||
|
|
ec763814a9 | ||
|
|
b21168a033 | ||
|
|
cf8a41528d | ||
|
|
57c95a6e35 | ||
|
|
17615d00a9 | ||
|
|
35ddbe72ed | ||
|
|
fd4e3734e8 | ||
|
|
cacb7891e7 | ||
|
|
9969a07b22 | ||
|
|
9aea8f62c9 | ||
|
|
106f7c90f9 | ||
|
|
3b831d4e59 | ||
|
|
dbadb2b4e5 | ||
|
|
7d0eb278bf | ||
|
|
0f8d6f140a | ||
|
|
15caa33a60 | ||
|
|
d905617248 | ||
|
|
643cc80ae0 | ||
|
|
a867171a45 | ||
|
|
2c6c9fb2cc | ||
|
|
0ad389aa6f | ||
|
|
a632e42fbc | ||
|
|
a809ffad61 | ||
|
|
ea7284b387 | ||
|
|
37ba9662f1 | ||
|
|
1f340a3590 | ||
|
|
f7368f0dfe | ||
|
|
dc538dc994 | ||
|
|
9f563bcec2 | ||
|
|
1ff8c9c19a | ||
|
|
810b7116cd | ||
|
|
3fb66d412a | ||
|
|
741b074134 | ||
|
|
c3ef4bc87c | ||
|
|
f5e81fe8cd | ||
|
|
3c95c6c276 | ||
|
|
f54aca3fd9 | ||
|
|
7220643c82 | ||
|
|
ede879f3d6 | ||
|
|
8c7263e8f1 | ||
|
|
402b6f6ff1 | ||
|
|
451bbbe035 | ||
|
|
db34a7eb8f | ||
|
|
5ce48c18e6 | ||
|
|
219098d6a3 | ||
|
|
5f87336f3f | ||
|
|
f5e1014bce | ||
|
|
75347adc8d | ||
|
|
35eae11389 | ||
|
|
28c4b121ca | ||
|
|
d5d699659c | ||
|
|
4b85fb8cf7 | ||
|
|
c51376fd26 | ||
|
|
b9e0344da4 | ||
|
|
ea51d57d3d | ||
|
|
a7b7a79ece | ||
|
|
09789e297d | ||
|
|
69a9dfa41b | ||
|
|
4e6361ff7b | ||
|
|
91d4a7781e | ||
|
|
da6be203f0 | ||
|
|
d074faafd1 | ||
|
|
39d7af8483 | ||
|
|
2ee406c909 | ||
|
|
66db895dd3 | ||
|
|
07e4024a9c | ||
|
|
8cab3e1f8b | ||
|
|
3f1617cba0 | ||
|
|
82795d845c | ||
|
|
99201191f4 | ||
|
|
3de027644c | ||
|
|
3e47ebec8e | ||
|
|
3890beb9d3 | ||
|
|
93e2b35dc7 | ||
|
|
da21cb8e21 | ||
|
|
14a0b716b6 | ||
|
|
4e986f37a0 | ||
|
|
e3092b4299 | ||
|
|
ceed334b06 | ||
|
|
51db0cb8e4 | ||
|
|
3961f2a888 | ||
|
|
91d44e72df | ||
|
|
314259032d | ||
|
|
fa9e4d0ee1 | ||
|
|
6de7fee90c | ||
|
|
f7226f681a | ||
|
|
26d46e3768 | ||
|
|
216563b635 | ||
|
|
3f2f391c93 | ||
|
|
9edf31244a | ||
|
|
d89bc29425 | ||
|
|
d525d350fc | ||
|
|
d1ba6bd333 | ||
|
|
6695e9be9c | ||
|
|
396ef6f0b0 | ||
|
|
b9637e5802 | ||
|
|
fe52511885 | ||
|
|
337e2fbfec | ||
|
|
4f5e32593b | ||
|
|
db314c9948 | ||
|
|
7dec9da401 | ||
|
|
3a8c0f3bee | ||
|
|
ec5802a487 | ||
|
|
fefc491ce5 | ||
|
|
41e0e7c688 | ||
|
|
ce2dfc1f19 | ||
|
|
13ed78bc23 | ||
|
|
904bd17c53 | ||
|
|
1f3a653faa | ||
|
|
cd1d6574f7 | ||
|
|
a5135c5560 | ||
|
|
2bce0d2d40 | ||
|
|
016052ca14 | ||
|
|
305870b3ee | ||
|
|
52530b2d5c | ||
|
|
5a35aa95bc | ||
|
|
53da0344bf | ||
|
|
f30d3760a1 | ||
|
|
fbcc3f5f02 | ||
|
|
5d0a92f3f7 | ||
|
|
4b830e6ba7 | ||
|
|
698e5e8e0c | ||
|
|
a4f5a23846 | ||
|
|
268121850d | ||
|
|
aeaf782feb | ||
|
|
21aeafd8dd | ||
|
|
ab704dc4be | ||
|
|
42c0a03ddc | ||
|
|
550529f1fa | ||
|
|
23fbc01a0e | ||
|
|
56546571b6 | ||
|
|
e5c634e672 | ||
|
|
7fdfe4a9ee | ||
|
|
3e95f77cc0 | ||
|
|
9b5e43eec5 | ||
|
|
ac3c3740c2 | ||
|
|
35a67ddf16 | ||
|
|
53815e0f28 | ||
|
|
7b7a73bd46 | ||
|
|
bf3fa6734e | ||
|
|
be06aef6d7 | ||
|
|
3e27abd1e8 | ||
|
|
0a6485e024 | ||
|
|
5b0df6bd4c | ||
|
|
a3f2a54f20 | ||
|
|
d1fd1fd9d0 | ||
|
|
cd5fb46963 | ||
|
|
09ebff06ee | ||
|
|
ac24d6d148 | ||
|
|
f481d22b75 | ||
|
|
149e3a5026 | ||
|
|
ef83525e9a | ||
|
|
56a042716e | ||
|
|
0fe722bfba | ||
|
|
3b53ce77bb | ||
|
|
b2f5c6c4d5 | ||
|
|
7c64864c2f | ||
|
|
9d0b947a48 | ||
|
|
9ed8a199b0 | ||
|
|
a0c9feefca | ||
|
|
1a2822d130 | ||
|
|
44007195b7 | ||
|
|
74b5a119d3 | ||
|
|
0539a3ad49 | ||
|
|
a06afd4bd2 | ||
|
|
1090acf3a5 | ||
|
|
b8ba0f0888 | ||
|
|
989b344b1d | ||
|
|
bf58335bbe | ||
|
|
5c4199ec74 | ||
|
|
7821d7c979 | ||
|
|
e2b47790a9 | ||
|
|
ff66a0664d | ||
|
|
6d42bef7ec | ||
|
|
cca3423a59 | ||
|
|
5e842d5850 | ||
|
|
0c724f3c7b | ||
|
|
55f3f59208 | ||
|
|
edc2f00603 | ||
|
|
bcb130e7eb | ||
|
|
d8de1e80b8 | ||
|
|
610378525a | ||
|
|
2f72c5d6ae | ||
|
|
a62949f0dc | ||
|
|
7ded01c277 | ||
|
|
a45361321d | ||
|
|
b7204f9694 | ||
|
|
b8a3b779e1 | ||
|
|
7906d630be | ||
|
|
3c04a4ce15 | ||
|
|
2f875b3bcb | ||
|
|
914f91f9f7 | ||
|
|
fcc6651b6a | ||
|
|
dda612c15e | ||
|
|
894a69d41f | ||
|
|
e2523f1d99 | ||
|
|
6fa35ae35c | ||
|
|
d0a2d31b5a | ||
|
|
43838e1ccc | ||
|
|
741e7902db | ||
|
|
6426d318fb | ||
|
|
8a98b4ee40 | ||
|
|
dcaac482de | ||
|
|
ee27df7680 | ||
|
|
e0ae29d64a | ||
|
|
db78e66451 | ||
|
|
b8ecd58005 | ||
|
|
2725834c4a | ||
|
|
42cb2d5d88 | ||
|
|
3994429b88 | ||
|
|
237bb1e0d9 | ||
|
|
911aad42cc | ||
|
|
36ded28ce4 | ||
|
|
9761bb91e9 | ||
|
|
435a310590 | ||
|
|
d377b3dbca | ||
|
|
850fe285b4 | ||
|
|
2adeb8dbcb | ||
|
|
fb5dfc4399 | ||
|
|
29e1cff8f1 | ||
|
|
2c3373ae16 | ||
|
|
c294ad0d4d | ||
|
|
ee436cc7cc | ||
|
|
168995493f | ||
|
|
e477f8b3da | ||
|
|
516329a408 | ||
|
|
938b7353ff | ||
|
|
a80721b2d3 | ||
|
|
4e065b9a80 | ||
|
|
aa8d7d02d1 | ||
|
|
05b36fc58f | ||
|
|
d6196ecad8 | ||
|
|
21a91421a6 | ||
|
|
272f51ad5b | ||
|
|
c352591d72 | ||
|
|
96caad46a5 | ||
|
|
309cae316b | ||
|
|
ffd0c75e8b | ||
|
|
fdf313472c | ||
|
|
011fdba7d3 | ||
|
|
e5e8521ae3 | ||
|
|
786e752417 | ||
|
|
d2220c68bd | ||
|
|
26d40e1149 | ||
|
|
15a55e6e07 | ||
|
|
32ab8d6573 | ||
|
|
ec55ae8b00 | ||
|
|
689adc0a30 | ||
|
|
fab9d7c0ab | ||
|
|
4aec2aa6da | ||
|
|
f46372b97c | ||
|
|
33c661505e | ||
|
|
91e16f7863 | ||
|
|
55e8c331a6 | ||
|
|
ae092cd1a7 | ||
|
|
122dc2ec0f | ||
|
|
c25b918a51 | ||
|
|
173bc97d0f | ||
|
|
84166ab954 | ||
|
|
a1cfd1de25 | ||
|
|
58fcae7af8 | ||
|
|
29f0a08391 | ||
|
|
2d683e7e46 | ||
|
|
247f6353b0 | ||
|
|
08e28fd0a5 | ||
|
|
cab591eb48 | ||
|
|
33e5acf0ea | ||
|
|
15554ef767 | ||
|
|
a9cb03c26f | ||
|
|
fb6110d0eb | ||
|
|
c3ace1faff | ||
|
|
c5286c3a6e | ||
|
|
1316eb2769 | ||
|
|
27909f1824 | ||
|
|
78c852514f | ||
|
|
985a681d72 | ||
|
|
3c79840a29 | ||
|
|
95ee614d9f | ||
|
|
591ce5274f | ||
|
|
3f18951368 | ||
|
|
be077f1a67 | ||
|
|
1a4b53917d | ||
|
|
726e13edcd | ||
|
|
4ebd990e30 | ||
|
|
4567550978 | ||
|
|
f489bd7d36 | ||
|
|
e93e42f952 | ||
|
|
250c9c4bce | ||
|
|
aa46a62457 | ||
|
|
f66c92aefb | ||
|
|
a696611615 | ||
|
|
5db53b5a9e | ||
|
|
e9e732756e | ||
|
|
ca96b85352 | ||
|
|
67f5098c95 | ||
|
|
f37652942d | ||
|
|
f67d182a92 | ||
|
|
b325cc75c0 | ||
|
|
6cc76903b0 | ||
|
|
b4bc0b006a | ||
|
|
b0ce899a71 | ||
|
|
bd162a9e08 | ||
|
|
8b7d69c0ff | ||
|
|
518f8b402e | ||
|
|
397457cacb | ||
|
|
eee0db133c | ||
|
|
dec05198c7 | ||
|
|
911c3c17c5 | ||
|
|
9d852eee9c | ||
|
|
17e1f1e4c7 | ||
|
|
c0ac29fdd8 | ||
|
|
9b3f93688e | ||
|
|
f4f7d9addc | ||
|
|
4e35f9f290 | ||
|
|
820b6ea3aa | ||
|
|
647f900b85 | ||
|
|
6c0f97ffe9 | ||
|
|
bc95b905af | ||
|
|
330ac54c6f | ||
|
|
54249d03dd | ||
|
|
d543393e3f | ||
|
|
ee010de36c | ||
|
|
3c7aaafa63 | ||
|
|
b62cec0cdc | ||
|
|
a474660498 | ||
|
|
6e2a5ff081 | ||
|
|
8dd63c722f | ||
|
|
e98dff325d | ||
|
|
8d93508062 | ||
|
|
fd0411b508 | ||
|
|
8564ed255a | ||
|
|
9d78700ab7 | ||
|
|
c72fedbb30 | ||
|
|
e637c61cc9 | ||
|
|
dae19ca067 | ||
|
|
9d6fc68642 | ||
|
|
2cd8b96d38 | ||
|
|
541337259d | ||
|
|
93defcd3a7 | ||
|
|
69edbbdf42 | ||
|
|
dcea005d8d | ||
|
|
cfd59d31a1 | ||
|
|
f42ffa61d1 | ||
|
|
0a2254702f | ||
|
|
d02327af2d | ||
|
|
c10ca2a77e | ||
|
|
2f8c01418d | ||
|
|
3a0c84a8f5 | ||
|
|
597b443796 | ||
|
|
0ea925289b | ||
|
|
f3b116e47c | ||
|
|
4c3ec7d067 | ||
|
|
941d8afb9a | ||
|
|
8e41662088 | ||
|
|
71bb2d889b | ||
|
|
c712473c7a | ||
|
|
121515cbc7 | ||
|
|
8b52dc8acb | ||
|
|
db5644e058 | ||
|
|
684cc666d3 | ||
|
|
dfff608ad1 | ||
|
|
f1c5ae73c2 | ||
|
|
d85ec7bcfb | ||
|
|
2855b1bb0b | ||
|
|
2a616e2518 | ||
|
|
d5d131185e | ||
|
|
0f2c0e794c | ||
|
|
d4e697c0de | ||
|
|
720dde1c06 | ||
|
|
1e017475ff | ||
|
|
95e51d1c27 | ||
|
|
d653a54fc3 | ||
|
|
c2ccccd093 | ||
|
|
837fa03f64 | ||
|
|
f027b4795f | ||
|
|
6ede54e3e0 | ||
|
|
922d259193 | ||
|
|
56075b5766 | ||
|
|
b0f02c6a36 | ||
|
|
de1326e118 | ||
|
|
53d1d1fded | ||
|
|
e17f427a5d | ||
|
|
df5af7d60c | ||
|
|
702cb0d140 | ||
|
|
9e415bff54 | ||
|
|
a696a0e0b2 | ||
|
|
52026701e4 | ||
|
|
7471488e33 | ||
|
|
0ef8a0b2d4 | ||
|
|
76a72a5ead | ||
|
|
3b0cba493b | ||
|
|
bc9f97f852 | ||
|
|
295eb2bd60 | ||
|
|
dfcf9de78e | ||
|
|
fc6d63603a | ||
|
|
9156ad915a | ||
|
|
a138a922ef | ||
|
|
b51ff6583d | ||
|
|
7207ad6da7 | ||
|
|
e3fafbc6ff | ||
|
|
be5eb26385 | ||
|
|
e9b529a644 | ||
|
|
250a1bcc81 | ||
|
|
96edb065d7 | ||
|
|
7e501a460a | ||
|
|
dd05ba239e | ||
|
|
b373fc465b | ||
|
|
f92f7ca803 | ||
|
|
51062a2499 | ||
|
|
48a3967b82 | ||
|
|
18ef16f6ee | ||
|
|
fda3ada055 | ||
|
|
db5500f130 | ||
|
|
42bbd0fe01 | ||
|
|
b257752812 | ||
|
|
54b7c8d697 | ||
|
|
bcafd0790f | ||
|
|
e07da9cad9 | ||
|
|
d3b5befe3e | ||
|
|
4481d94fa2 | ||
|
|
982a6dd5f2 | ||
|
|
898fc8c13c | ||
|
|
9ec2762bff | ||
|
|
6d9059ee86 | ||
|
|
ef6ab620fb | ||
|
|
ecf4cb15be | ||
|
|
c9d36b5bad | ||
|
|
9e932799ee | ||
|
|
6215b65ec9 | ||
|
|
9adf64c374 | ||
|
|
d32f9533e3 | ||
|
|
2a0a8585df | ||
|
|
87a2b219a5 | ||
|
|
c5e13ccc84 | ||
|
|
40c9a18be4 | ||
|
|
8a4aa7908f | ||
|
|
82879e39e7 | ||
|
|
60534bcfe3 | ||
|
|
41ddd7c1a6 | ||
|
|
5601a556c9 | ||
|
|
764e0f3f25 | ||
|
|
3619807cb9 | ||
|
|
d65f220530 | ||
|
|
903e758528 | ||
|
|
9b85f00564 | ||
|
|
cb191d60f3 | ||
|
|
ac70fcc5f9 | ||
|
|
68d24919b1 | ||
|
|
baa411d9c7 | ||
|
|
7e199f6815 | ||
|
|
9ca0649368 | ||
|
|
987b6b2f82 | ||
|
|
bf3a4dcc7a | ||
|
|
e4cfd85dc7 | ||
|
|
974b6a9ea2 | ||
|
|
71345465cf | ||
|
|
89c02c9a96 | ||
|
|
73382db4a2 | ||
|
|
9bafd037c3 | ||
|
|
57e3a369ae | ||
|
|
f0550d75b8 | ||
|
|
b694c837b4 | ||
|
|
a083b06de0 | ||
|
|
17e9599976 | ||
|
|
487b96d7d3 | ||
|
|
83b5319433 | ||
|
|
db305db392 | ||
|
|
dfd5f9dd94 | ||
|
|
8748d8b4e5 | ||
|
|
f97e9f9333 | ||
|
|
3c58c6bf5f | ||
|
|
a4899658df | ||
|
|
1053740723 | ||
|
|
b0adfb97ad | ||
|
|
d0d2314e9a | ||
|
|
22aafce24e | ||
|
|
c02dfafa2a | ||
|
|
f8b22dc1ed | ||
|
|
4a9b543a3a | ||
|
|
1332a73189 | ||
|
|
165a6695b9 | ||
|
|
b816745830 | ||
|
|
0b198b4031 | ||
|
|
ffdab44710 | ||
|
|
3e423a14f4 | ||
|
|
f56c65df79 | ||
|
|
c8eb0914b3 | ||
|
|
05851fb728 | ||
|
|
5c12810d23 | ||
|
|
e11392d0af | ||
|
|
e3f2da13f5 | ||
|
|
a94eeb023d | ||
|
|
82162b167b | ||
|
|
7100d251ec | ||
|
|
c4f98a10dc | ||
|
|
9585c92629 | ||
|
|
2594427e75 | ||
|
|
49178b50b0 | ||
|
|
353a7c9c2b | ||
|
|
a9614c15aa | ||
|
|
690e2f6e85 | ||
|
|
16b9122f9e | ||
|
|
4cfe16a708 | ||
|
|
3c4aaf5bba | ||
|
|
bc2fe382a9 | ||
|
|
a0fe1080f3 | ||
|
|
81eb07ee98 | ||
|
|
f7de2bb6d9 | ||
|
|
d430f835a4 | ||
|
|
c0f0ce7df2 | ||
|
|
d934fb41ab | ||
|
|
d0739ce0f1 | ||
|
|
545b5db143 | ||
|
|
649a5cb86a | ||
|
|
88c1595deb | ||
|
|
a8498bb7f6 | ||
|
|
d5fa496a20 | ||
|
|
9f86e8355d | ||
|
|
8f4784f86d | ||
|
|
696a489f43 | ||
|
|
67d436cfc5 | ||
|
|
a966de9f50 | ||
|
|
9b7e8250bc | ||
|
|
7723c0149d | ||
|
|
38c363162b | ||
|
|
4a46c46253 | ||
|
|
9d1349b5aa | ||
|
|
2c857a6eb1 | ||
|
|
ad64ced2df | ||
|
|
81fb54cff5 | ||
|
|
45bbc93bee | ||
|
|
f9542f9214 | ||
|
|
60052b8ab7 | ||
|
|
7b9a6b4e9c | ||
|
|
09ca2a6740 | ||
|
|
20cd27e68d | ||
|
|
129d061434 | ||
|
|
19f59b207f | ||
|
|
528028d41b | ||
|
|
d77fa67729 | ||
|
|
f33418ec35 | ||
|
|
dd379c41c9 | ||
|
|
609a25119d | ||
|
|
9258acf765 | ||
|
|
d46d90395c | ||
|
|
b4e97ead04 | ||
|
|
ea5574bfa5 | ||
|
|
5b6e8de9a1 | ||
|
|
9980ec62fd | ||
|
|
8abb9b4358 | ||
|
|
40c1c0ec02 | ||
|
|
f2bc95ffcc | ||
|
|
4b1fbb1243 | ||
|
|
e7c2a8b880 | ||
|
|
dbbe9e8b0e | ||
|
|
b26a12074f | ||
|
|
79f99bd4f1 | ||
|
|
38cfe3a23b | ||
|
|
5e27701228 | ||
|
|
0a9fe9bb89 | ||
|
|
d0cd206f51 | ||
|
|
9d5f11aed5 | ||
|
|
eba5e422bf | ||
|
|
0e6f200efe | ||
|
|
e890a603c2 | ||
|
|
97951e7e8e | ||
|
|
3d98b64a42 | ||
|
|
d3dfd54f54 | ||
|
|
2e15732f64 | ||
|
|
45f5c5368a | ||
|
|
012e3b5f3c | ||
|
|
35521a5214 | ||
|
|
ad1e8d03e4 | ||
|
|
1eef1e083d | ||
|
|
bb5215c306 | ||
|
|
2c47b4e585 | ||
|
|
4371eb7c39 | ||
|
|
e16a3d46b3 | ||
|
|
1a3996e73b | ||
|
|
c3006c69a6 | ||
|
|
523bcbb6e3 | ||
|
|
127ad155ba | ||
|
|
14d287afe3 | ||
|
|
6231977e70 | ||
|
|
758b3e804e | ||
|
|
67a6d8194d | ||
|
|
31b5a3df77 | ||
|
|
785cf9c73a | ||
|
|
d9aa319e2c | ||
|
|
141e7233ff | ||
|
|
9ff25e7c3a | ||
|
|
c9fe54ed29 | ||
|
|
bbaef6cf8e | ||
|
|
7df1432744 | ||
|
|
6442d85c9f | ||
|
|
f4ad2ea6c8 | ||
|
|
f7fdf3f987 | ||
|
|
b8833b74eb | ||
|
|
bc29d1e86c | ||
|
|
5ea9b8211e | ||
|
|
25f1881f85 | ||
|
|
d819d8e8a1 | ||
|
|
db022a0688 | ||
|
|
c88426e0b2 | ||
|
|
4818345ce2 | ||
|
|
70ee681757 | ||
|
|
9e03d5f665 | ||
|
|
1561f2631a | ||
|
|
c4ae5496e2 | ||
|
|
53692ffc7c | ||
|
|
3b0194d886 | ||
|
|
e75a1ff4fe | ||
|
|
042c8061f6 | ||
|
|
865570fa94 | ||
|
|
5e7f44b5ca | ||
|
|
821161363e | ||
|
|
e542da8c74 | ||
|
|
31fb795fd8 | ||
|
|
5cf7f99517 | ||
|
|
3f64463580 | ||
|
|
1a9f16a25a | ||
|
|
1084d87399 | ||
|
|
3147a6989c | ||
|
|
63502d5f30 | ||
|
|
06733812e6 | ||
|
|
f9f1c19b84 | ||
|
|
01b3a428d9 | ||
|
|
fd2f14ae2e | ||
|
|
bf6c9121f4 | ||
|
|
a67d1231de | ||
|
|
b69f5f4b5f | ||
|
|
8605b23135 | ||
|
|
7d823dd61d | ||
|
|
8152c3cebf | ||
|
|
7229b8a0a0 | ||
|
|
44a5630634 | ||
|
|
19a65050e8 | ||
|
|
04948a21f0 | ||
|
|
f7f4430be9 | ||
|
|
e77f3ed44a | ||
|
|
180ba935b1 | ||
|
|
0e106e263b | ||
|
|
7327917a81 | ||
|
|
c08dcd9fb5 | ||
|
|
ee6785f6ab | ||
|
|
537ba26645 | ||
|
|
e604ee2440 | ||
|
|
6534a2d62e | ||
|
|
b5c3bedd14 | ||
|
|
10bbaa9217 | ||
|
|
455e16a88f | ||
|
|
5d27a19b38 | ||
|
|
7cab722df3 | ||
|
|
b99c988970 | ||
|
|
f53c070ca1 | ||
|
|
84752e0fef | ||
|
|
0d4e0f1dab | ||
|
|
cea0cff2a7 | ||
|
|
a60f1e13db | ||
|
|
abe2e0b73b | ||
|
|
f9bb33c49b | ||
|
|
7a30b18c20 | ||
|
|
3fb9a195f8 | ||
|
|
c386a0c439 | ||
|
|
216c5d8b8e | ||
|
|
b57a39d8e8 | ||
|
|
8ba828885b | ||
|
|
d2c38963c9 | ||
|
|
59d00c1765 | ||
|
|
8217ad8fa5 | ||
|
|
4b2f134f32 | ||
|
|
6526caae30 | ||
|
|
4f26bc20a0 | ||
|
|
8b9b0b4fcb | ||
|
|
d2d5016780 | ||
|
|
a09dbbfd27 | ||
|
|
82cfb62804 | ||
|
|
ebf445e3e1 | ||
|
|
2123af39a7 | ||
|
|
917e661e88 | ||
|
|
bf905b1562 | ||
|
|
f6fcac5856 | ||
|
|
d4f11542f9 | ||
|
|
f906c0ecab | ||
|
|
99f8adeb7d | ||
|
|
0965a78d46 | ||
|
|
fe38b53ff7 | ||
|
|
1065c0d440 | ||
|
|
fd84a9a5fc | ||
|
|
b62c422959 | ||
|
|
f2305429c4 | ||
|
|
292e6b0339 | ||
|
|
7b54873f08 | ||
|
|
281b3ed45b | ||
|
|
8b2d659000 | ||
|
|
4c31740f25 | ||
|
|
848ae5fc9d | ||
|
|
b5da4e00ca | ||
|
|
4c3688de12 | ||
|
|
805660d4d1 | ||
|
|
1821deaa73 | ||
|
|
c9fd9a2a46 | ||
|
|
31bd0a6d68 | ||
|
|
c3162bbb1f | ||
|
|
02451e8ec8 | ||
|
|
bd21332c25 | ||
|
|
71f1609467 | ||
|
|
5331bc630a | ||
|
|
ea05c0327b | ||
|
|
d207f86daa | ||
|
|
9c26d54eda | ||
|
|
b72c4db334 | ||
|
|
5830958478 | ||
|
|
c78481b5b1 | ||
|
|
0af9e0f42e | ||
|
|
48b0b25974 | ||
|
|
6bd303d2e8 | ||
|
|
dbddcc1c10 | ||
|
|
bb4a2b9b97 | ||
|
|
5c3b5031d8 | ||
|
|
b460443e77 | ||
|
|
69bfb4180c | ||
|
|
2d32823db8 | ||
|
|
38262f11b2 | ||
|
|
e32beea00a | ||
|
|
bfcbaacec7 | ||
|
|
be8631e90c | ||
|
|
4198881815 | ||
|
|
b91696bc89 | ||
|
|
f7d070b38c | ||
|
|
95cc29fd9c | ||
|
|
9ae76a90fc | ||
|
|
24b8617c54 | ||
|
|
892a168ee7 | ||
|
|
51336412c5 | ||
|
|
f38913b75c | ||
|
|
ae35474130 | ||
|
|
bb5cc236a7 | ||
|
|
6a6c80d9a4 | ||
|
|
ae8f7964fa | ||
|
|
3630b17cde | ||
|
|
95f97d33af | ||
|
|
1243de8010 | ||
|
|
965b179d56 | ||
|
|
5d0fff2276 | ||
|
|
d7b6b7c0ae | ||
|
|
de0497a2dc | ||
|
|
1653c16c65 | ||
|
|
88783b3f5f | ||
|
|
d701bc7347 | ||
|
|
82e9ca93b6 | ||
|
|
5f99e7280a | ||
|
|
f56bf3d41b | ||
|
|
1ac0494f96 | ||
|
|
08720d8eeb | ||
|
|
c44057f47d | ||
|
|
abd54b3939 | ||
|
|
1a9fc593d1 | ||
|
|
b1bf86724d | ||
|
|
bc4c0cc505 | ||
|
|
69a5ffd8b2 | ||
|
|
e22f2f2e90 | ||
|
|
b02972b7ea | ||
|
|
d3cabc1c05 | ||
|
|
634cf79916 | ||
|
|
7e660091e0 | ||
|
|
296ced8e9b | ||
|
|
1226caca03 | ||
|
|
f1c02442bc | ||
|
|
52f8f513da | ||
|
|
c22b364ebe | ||
|
|
0792838b16 | ||
|
|
f16f6ea4d7 | ||
|
|
2e9cc3b581 | ||
|
|
71543dc615 | ||
|
|
f4f327e7d4 | ||
|
|
4c6777862e | ||
|
|
7acee37607 | ||
|
|
1bb9fd04d8 | ||
|
|
022806bea2 | ||
|
|
55bbfe59e6 | ||
|
|
52d7d21019 | ||
|
|
6974de02ac | ||
|
|
d46bd8d10c | ||
|
|
9129ff6e75 | ||
|
|
ed102015ad | ||
|
|
26e2375bfb | ||
|
|
bb1de75128 | ||
|
|
254f238f82 | ||
|
|
85210e2935 | ||
|
|
cdb461ab68 | ||
|
|
0569ed0f86 | ||
|
|
64d1e8677e | ||
|
|
790f6440f3 | ||
|
|
9e46bd8e2c | ||
|
|
02590579df | ||
|
|
fccae7734d | ||
|
|
f97cd74bd3 | ||
|
|
9b5cd427e6 | ||
|
|
0b85b09f62 | ||
|
|
6887291151 | ||
|
|
26b72e3792 | ||
|
|
2d042f2c44 | ||
|
|
52c9e644b8 | ||
|
|
058a0f82d3 | ||
|
|
17fbcc5f09 | ||
|
|
be50758451 | ||
|
|
f5fc5ba813 | ||
|
|
64d4a78570 | ||
|
|
d4780f61bc | ||
|
|
79c290603d | ||
|
|
a6ea7a3950 | ||
|
|
17f98b5c6b | ||
|
|
467736ca2f | ||
|
|
74386d0c42 | ||
|
|
b51a790997 | ||
|
|
6329cd35f9 | ||
|
|
c66e106613 | ||
|
|
4368015435 | ||
|
|
762b072c5e | ||
|
|
664c5b9628 | ||
|
|
6f931305cc | ||
|
|
92afb11668 | ||
|
|
b668928c89 | ||
|
|
3ad8619920 | ||
|
|
3709b62084 | ||
|
|
1db15e041b | ||
|
|
f4eb6ff49d | ||
|
|
4a264abadd | ||
|
|
21133ce118 | ||
|
|
e2d26f2abc | ||
|
|
98c144efe8 | ||
|
|
f1be3cf3e3 | ||
|
|
1539727da1 | ||
|
|
b55c9f1f6c | ||
|
|
9d2899d5b7 | ||
|
|
2905f7d1aa | ||
|
|
23bbd6498f | ||
|
|
1ca7eb48db | ||
|
|
47d283eb61 | ||
|
|
194e7266bd | ||
|
|
2869603573 | ||
|
|
5cd7b44580 | ||
|
|
8ab0e615c8 | ||
|
|
9d2fd763e2 | ||
|
|
91821f7431 | ||
|
|
3918c227c7 | ||
|
|
f4d3db790e | ||
|
|
4623e5b7c4 | ||
|
|
b5b3c38fec | ||
|
|
749db12bbd | ||
|
|
a7026a4f24 | ||
|
|
c7911f3f5c | ||
|
|
c18aa4d91c | ||
|
|
6f33e3ae9f | ||
|
|
46566a4fb5 | ||
|
|
0dad739b15 | ||
|
|
c59368c601 | ||
|
|
44ac1e1012 | ||
|
|
5958107cd9 | ||
|
|
25d36c8c2e | ||
|
|
a368af3f01 | ||
|
|
e630acffe0 | ||
|
|
dbd252a95c | ||
|
|
1da3f3219c | ||
|
|
a880b3d830 | ||
|
|
bed335132b | ||
|
|
6734379500 | ||
|
|
c1fc40d523 | ||
|
|
6be72f36d7 | ||
|
|
682cd1ea8b | ||
|
|
9847038f26 | ||
|
|
1539030469 | ||
|
|
4e57c13a7e | ||
|
|
5bc8f82015 | ||
|
|
5a41561261 | ||
|
|
3a9862899a | ||
|
|
6e4eb295dc | ||
|
|
96904ee917 | ||
|
|
01683af8ef | ||
|
|
6f1a27a84d | ||
|
|
4dcb6bbbc0 | ||
|
|
e86990065d | ||
|
|
4ee7d22dc0 |
No files matched your search
@@ -0,0 +1,193 @@
|
||||
---
|
||||
name: assets-optimization
|
||||
description: Audit and optimize every image asset shipped with AltTab. Apply the right format per asset class (PDF for vectors, HEIC for raster) and the right post-processing (strip Figma cruft from PDFs, extract SF Symbols as minimal vector PDFs, encode raster sources to HEIC at q50 with visual review). Use whenever new assets are added, when the bundle size needs shrinking, or whenever you want a full assets audit.
|
||||
---
|
||||
|
||||
# /assets-optimization — AltTab asset audit and optimization
|
||||
|
||||
## Goal
|
||||
|
||||
Every byte that ships in `AltTab.app/Contents/Resources/` should justify itself. Vectors stay vector, rasters compress to HEIC, and neither carries metadata, color profiles, accessibility tags, or producer signatures that AppKit doesn't use.
|
||||
|
||||
This skill applies a known-good pipeline to each asset class. It is opinionated about the right format and the right encoder for each kind of content.
|
||||
|
||||
## When to use
|
||||
|
||||
- A designer drops new exports into `~/Desktop/` or `resources/`.
|
||||
- Someone asks "why is the bundle so big?".
|
||||
- After adding a new icon, illustration, app icon variant, or menubar variant.
|
||||
- Periodic audit when nothing else is broken.
|
||||
|
||||
## Step 1: Inventory
|
||||
|
||||
Run a one-shot enumeration so you know what you're working with:
|
||||
|
||||
```sh
|
||||
find resources -type f \( -iname '*.pdf' -o -iname '*.png' -o -iname '*.jpg' -o -iname '*.jpeg' -o -iname '*.heic' -o -iname '*.svg' -o -iname '*.icns' \) \
|
||||
-exec ls -la {} \; | awk '{printf "%8d %s\n", $5, $9}' | sort -k2
|
||||
```
|
||||
|
||||
Group what you see by directory. For AltTab the relevant buckets are:
|
||||
|
||||
- `resources/icons/menubar/` — small template icons shown in the macOS menubar.
|
||||
- `resources/icons/tabs/` — Preferences sidebar icons (template, sized ~13pt).
|
||||
- `resources/icons/permission-window/` — first-launch permission window icons (~32pt).
|
||||
- `resources/icons/app/` — the macOS app icon (`.icns` + `.iconset/`). **Don't touch** — `.icns` is required by the bundle and Apple's tooling produces near-optimal output already.
|
||||
- `resources/illustrations/` — the appearance-tab preview thumbnails. Raster (screenshots inside).
|
||||
|
||||
For each asset, decide what category it falls into:
|
||||
|
||||
| Source content | Right format | Why |
|
||||
|---|---|---|
|
||||
| Custom vector design (Figma/Sketch/Illustrator) | **PDF** | macOS 10.13 doesn't accept SVG; PDF is the universal vector container AppKit reads natively. |
|
||||
| SF Symbol (Apple system glyph) | **Font glyph** in the bundled `SF-Pro-Text-Regular.otf` subset | Render via `NSImage.fromSymbol(.foo, pointSize:)` (or as text via the `Symbols` enum). Smaller than per-icon PDFs, picks up Apple's latest glyph refinements automatically when the developer updates SF Symbols.app. |
|
||||
| Photographic / screenshot-heavy | **HEIC** | HEIC at q50 beats JPEG by ~30% at the same perceptual quality. |
|
||||
| Tiny pixel-precise UI sprite | **PNG @2x** | Below ~40×40px the PDF overhead exceeds the bitmap savings. PNG wins. |
|
||||
| App icon (the macOS bundle one) | **`.icns`** | Required by `CFBundleIconFile`. |
|
||||
|
||||
If an asset is in the wrong format, flag it. If it's in the right format but unoptimized, run the matching pipeline below.
|
||||
|
||||
## Step 2: Vector PDFs — Figma exports
|
||||
|
||||
Figma's "Export → PDF" output is bloated. For each menubar/illustration/icon vector PDF that came from Figma, you can strip ~50–75% of the bytes without losing a single rendered pixel.
|
||||
|
||||
What Figma adds that AppKit doesn't need:
|
||||
|
||||
1. **Embedded ICC color profile** (`/ICCBased ...`, ~3.2 KB compressed). Replace every `[/ICCBased N R]` reference with `/DeviceRGB` (or `/DeviceGray` for monochrome). Patches needed in:
|
||||
- the page `Resources/ColorSpace` dict
|
||||
- every Form XObject's `Resources/ColorSpace` dict (these are streams, not plain dicts — pikepdf's `pdf.objects` will only catch them if you accept both `Dictionary` and `Stream`)
|
||||
- every Image XObject's direct `/ColorSpace` key
|
||||
- every Shading dict inside `Pattern` entries (Figma's color icons use 8+ patterns, each with its own `/ColorSpace N R` reference)
|
||||
2. **`/Metadata`** XMP packet (~830 B) — Figma's XML manifest.
|
||||
3. **`/StructTreeRoot`, `/ParentTree`, `/StructElem`** — accessibility tags ("Document" / "Part" structural roles). AppKit's PDF renderer ignores them.
|
||||
4. **`/Info` dict** — `Producer="Figma"`, `Title="Menubar 22x22@1x white"`. In Figma's exports the Info dict sometimes lives **inline inside the Catalog** rather than at the trailer level, so deleting `pdf.docinfo` isn't enough — also `del root[Name('/Info')]`.
|
||||
5. **`/Lang`, `/MarkInfo`, `/Annots`, `/StructParents`, `/Tabs`** — empty or trivial page-level entries.
|
||||
6. **`/ProcSet [/PDF]`** — deprecated since PDF 1.4.
|
||||
|
||||
Use the script:
|
||||
|
||||
```sh
|
||||
python3 scripts/assets/optimize_figma_pdf.py resources/icons/menubar/*.pdf
|
||||
```
|
||||
|
||||
It edits in place and prints the savings per file. After running, also pipe through `mutool clean -ggg -z` and `qpdf --object-streams=generate --recompress-flate --compression-level=9` for the final 1–2% squeeze.
|
||||
|
||||
Verify each file still renders by sips'ing it back to PNG and eyeballing:
|
||||
|
||||
```sh
|
||||
for f in resources/icons/menubar/*.pdf; do
|
||||
sips -s format png "$f" --out "/tmp/$(basename $f .pdf).png" -Z 300 >/dev/null 2>&1
|
||||
done
|
||||
```
|
||||
|
||||
Open the PNGs in Preview to confirm nothing visual changed.
|
||||
|
||||
## Step 3: SF Symbols via font subset
|
||||
|
||||
Every SF Symbol shipped in AltTab — switcher status icons, sidebar tab icons, button icons, permission/feedback icons — is rendered as a text glyph from a subsetted SF Pro Text font. There are no SF-Symbol PDFs in the bundle.
|
||||
|
||||
How it works: SF Symbols are glyphs in the Private Use Area of SF Pro Text. Apple's `SF-Pro-Text-Regular.otf` contains every symbol they've ever shipped. We subset it down to just the codepoints AltTab needs (currently ~36 glyphs, ~17 KB) into `resources/SF-Pro-Text-Regular.otf`, and register it via `Info.plist:ATSApplicationFontsPath = ""`. At runtime, `NSFont(name: "SF Pro Text", size:)` resolves to the bundled subset on macOS <11 (where the system font isn't installed) and to the system font on macOS 11+, with identical glyph appearance either way.
|
||||
|
||||
To add a new SF Symbol:
|
||||
|
||||
1. Open [SF Symbols.app](https://developer.apple.com/sf-symbols/), search for the symbol, press **Cmd-C** to copy the symbol character to the clipboard. (Apple's name→codepoint mapping is not exposed via public API, and the SF Pro Text font's cmap uses `uniXXXXXX.medium`-style names rather than semantic ones, so this manual lookup is the authoritative path.)
|
||||
2. Paste the character into a new case on the `Symbols` enum in [src/switcher/main-window/TileFontIconView.swift](src/switcher/main-window/TileFontIconView.swift) — e.g., `case foo = "" // SF Symbol name`.
|
||||
3. Paste the same character at the end of the `--text=` argument in [scripts/assets/subset_font.sh](scripts/assets/subset_font.sh).
|
||||
4. Run `bash scripts/assets/subset_font.sh`. It reads `/Library/Fonts/SF-Pro-Text-Regular.otf` (installed by SF Symbols.app — a standard developer prerequisite) and writes the regenerated subset to `resources/SF-Pro-Text-Regular.otf`. Picks up Apple's latest glyph refinements automatically.
|
||||
5. Use it in code: `NSImage.fromSymbol(.foo, pointSize: 14)` returns a template `NSImage`; or `TileFontIconView(symbol: .foo, ...)` for the cached-attributed-string path in the switcher hot loop.
|
||||
|
||||
The script runs `pyftsubset` via the project's pipenv environment. Warnings about `MERG`/`meta`/`trak` tables being dropped are normal — those tables aren't relevant to glyph rendering.
|
||||
|
||||
### Historical note: SF Symbols via PDF (deprecated, scripts removed)
|
||||
|
||||
A previous pipeline shipped each SF Symbol as a per-glyph PDF in `resources/icons/`. The pipeline lives only in git history now (`scripts/assets/export_sf_symbol_pdf.swift` was deleted alongside the migrated PDFs). The technique is worth knowing in case a future need arises (e.g., a multi-color symbol that fonts can't represent):
|
||||
|
||||
- The naive route — `NSImage(systemSymbolName:).draw(in:)` against a PDF `CGContext` — produces a **black rectangle**, because Quartz emits `image-mask + fill-rectangle` operators where the rectangle paints over the mask. AppKit bug at the PDF emission level; `paletteColors` config does not fix it.
|
||||
- The working route was to extract the symbol's vector path directly via private selectors that have been stable across macOS 11–15:
|
||||
`NSImage(systemSymbolName:).representations[0] (NSSymbolImageRep) → .perform("vectorGlyph") (CUINamedVectorGlyph) → .perform("CGPath") (real CGPath)`.
|
||||
- The CGPath lives in CUI's internal coordinate space (~2× display points, Y-down) — scale to fit the canvas and flip Y. Walk the path via `CGPath.applyWithBlock` and emit raw PDF operators (`m`, `l`, `c`, `h`, `f`). Non-zero winding fill. Fill with DeviceGray (`0 g`), **not** `NSColor.black.cgColor`, which drags in a ~3.4 KB ICC color profile.
|
||||
- Final PDF wrapper: 4 objects (Catalog, Pages, Page, Content), no `/Info`, no `/Metadata`, no `/Resources/ColorSpace`. ~750–1800 bytes per icon.
|
||||
|
||||
Git: see commit `990c1e79` ("feat: pro improve assets") for the PDF pipeline as-it-was; subsequent commit migrated the SF-Symbol PDFs back to font glyphs.
|
||||
|
||||
## Step 4: Raster → HEIC at q50
|
||||
|
||||
For anything raster (illustration thumbnails, screenshots inside an icon, anything photographic), HEIC at quality 50 is the baseline. q50 is roughly 50% smaller than JPEG at perceptually-equivalent quality, and at the small display sizes used in this app the artifacts are invisible.
|
||||
|
||||
Pipeline (built-in to macOS via `sips`):
|
||||
|
||||
```sh
|
||||
sips -Z 1000 -s format heic -s formatOptions 50 input.png --out output.heic
|
||||
```
|
||||
|
||||
- `-Z 1000` resizes the longest edge to 1000px **preserving aspect ratio**. AltTab's illustration display is 500pt wide, so 1000px is the correct @2x ship size. Anything larger wastes bytes; anything smaller looks soft on Retina.
|
||||
- `formatOptions 50` is the quality. q50 was chosen after a side-by-side comparison at q20/q35/q50/q65/q80 — q50 was the lowest setting where text in screenshots stayed legible and gradient backgrounds didn't band.
|
||||
|
||||
Use the script for batch conversion:
|
||||
|
||||
```sh
|
||||
bash scripts/assets/encode_heic.sh ~/Desktop 1000 50
|
||||
```
|
||||
|
||||
That walks all PNG/JPEG files in the source directory, resizes to longest-edge 1000px at q50 HEIC, writes outputs to `/tmp/heic-out/`.
|
||||
|
||||
### Visual review (mandatory)
|
||||
|
||||
Before swapping the new HEICs into `resources/`, **always** decode a representative sample back to PNG and visually compare against the source:
|
||||
|
||||
```sh
|
||||
sips -s format png /tmp/heic-out/sample.heic --out /tmp/sample-decoded.png >/dev/null 2>&1
|
||||
open /tmp/sample-decoded.png /Users/you/Desktop/sample.png
|
||||
```
|
||||
|
||||
Pick the visually most demanding file from the batch — usually one with the most text or the strongest gradients. Confirm:
|
||||
|
||||
- No banding in flat color regions
|
||||
- Text edges still crisp at native display size
|
||||
- No haloing around anti-aliased edges
|
||||
- Color rendition matches
|
||||
|
||||
If anything looks degraded, bump quality to q60 or q65 and re-batch. The user, not the script, is the final arbiter — show them the sample with sizes before committing.
|
||||
|
||||
### Bumping quality
|
||||
|
||||
If q50 isn't acceptable, the next quality steps are q60 and q65 — beyond that, returns diminish quickly. q80 is the previous default in this repo and roughly 2× the bytes of q50 for no visible improvement on AltTab's content.
|
||||
|
||||
## Step 5: pbxproj registration
|
||||
|
||||
Whenever you change the file extension of a resource (.jpg → .heic, .png → .pdf, etc.), update [alt-tab-macos.xcodeproj/project.pbxproj](alt-tab-macos.xcodeproj/project.pbxproj). The places that need patching:
|
||||
|
||||
1. **PBXBuildFile section** — comment + the comment inside `fileRef = ... /* name.ext */`.
|
||||
2. **PBXFileReference section** — comment, `lastKnownFileType` (e.g. `image.pdf`, `image.heic`, `image.png`), and `path = "name.ext"`.
|
||||
3. **PBXGroup section** — the file's entry inside its parent group's `children`.
|
||||
4. **PBXResourcesBuildPhase section** — the entry in the main app target's `files`.
|
||||
|
||||
For pure extension swaps (no new files), `sed -i '' 's|old\.ext|new.ext|g'` plus a `lastKnownFileType` substitution covers it. For new files, generate new 24-char uppercase-hex object IDs (`python3 -c "import secrets; print(secrets.token_hex(12).upper())"`) and insert in all four places.
|
||||
|
||||
If you removed an asset entirely (file deleted from disk), delete its 4 entries from pbxproj — otherwise the build fails with "missing file" or ships dangling references.
|
||||
|
||||
## Step 6: Verify
|
||||
|
||||
```sh
|
||||
bash ai/build.sh # must show ** BUILD SUCCEEDED **
|
||||
bash ai/run.sh # launch the app and visually inspect every asset
|
||||
```
|
||||
|
||||
Walk through every UI surface that loads an asset:
|
||||
|
||||
- Menubar icon (default + the two alternates from Preferences → General → Menubar icon)
|
||||
- Preferences sidebar — 4 tab icons (SF Symbol on macOS 11+, bundled PDF below)
|
||||
- Permissions window — open by revoking a permission
|
||||
- Preferences → Appearance — illustration thumbnails change per show/hide row
|
||||
|
||||
Compare `git diff --stat` before committing. Asset replacements should net negative on bundle size.
|
||||
|
||||
## Reporting
|
||||
|
||||
After the run, report:
|
||||
|
||||
- File-by-file before/after sizes for everything that changed.
|
||||
- Total bundle delta in KB.
|
||||
- Any files left untouched and why (e.g., `app.icns` — bundle-required format).
|
||||
- The encoder settings used (especially HEIC quality if not q50, so the next person knows).
|
||||
- Anything the visual review revealed (e.g., "had to bump to q60 for `thumbnails_dark` because gradient banding at q50").
|
||||
@@ -0,0 +1,65 @@
|
||||
---
|
||||
name: audit-specs-tests
|
||||
description: Audit the AltTab test suite for spec/test/code consistency. For every co-located triad in `src/` (`Foo.swift` + `FooSpecs.md` + `FooTests.swift`), it cross-checks that each XCTest method has a matching scenario line in the spec and vice-versa, flags drift between a scenario's description and the test's doc-comment, and reports orphan tests, orphan scenarios, and unpaired files. Use after adding or editing any `*Tests.swift` / `*Specs.md`, before committing a test change, or to get an overall health check of the suite.
|
||||
---
|
||||
|
||||
# /audit-specs-tests
|
||||
|
||||
## Project context
|
||||
|
||||
AltTab co-locates tests and a living spec next to the source they cover. The convention (see the plan in `.claude/plans/` and `src/pro/license/` as a worked example) is a **triad** per concept:
|
||||
|
||||
```
|
||||
src/<feature>/
|
||||
├── Foo.swift # production / kernel (app target, or app+test for kernels)
|
||||
├── FooSpecs.md # the living spec (not compiled)
|
||||
└── FooTests.swift # XCTest (unit-tests target only)
|
||||
```
|
||||
|
||||
The spec is **documentation tied 1:1 to the test methods**. Its `## Test scenarios` section lists every test, grouped, as:
|
||||
|
||||
```markdown
|
||||
### A. <Group name>
|
||||
- **testAFooDoesBar** — one-line description of what it pins.
|
||||
```
|
||||
|
||||
This skill's job is to keep the three in sync: every `func test…` ⇔ exactly one `- **test…** —` spec line, descriptions roughly agree, and no triad is missing a leg.
|
||||
|
||||
## What counts as a triad
|
||||
|
||||
Pair by base name in the **same folder**: `<Base>Tests.swift` ⇔ `<Base>Specs.md`. A `<Base>.swift` (or a kernel like `SelectionResolver.swift`, `SearchTestable.swift`) in the folder is the code under test, but a spec may legitimately cover several classes (e.g. `LicenseManagerSpecs.md` documents `Clock`/`Keychain`/`LicenseAPI` too) — so a missing same-name `.swift` is a note, not an error.
|
||||
|
||||
## Workflow
|
||||
|
||||
1. **Enumerate.** Find all test and spec files:
|
||||
```sh
|
||||
find src -name '*Tests.swift' | sort
|
||||
find src -name '*Specs.md' | sort
|
||||
```
|
||||
Also list any test files still under `unit-tests/` and flag them as **not yet migrated** (the target end-state is zero test files outside `src/`).
|
||||
|
||||
2. **For each `*Tests.swift`, extract the test methods and their doc-comments.** Test names:
|
||||
```sh
|
||||
grep -nE '^\s*func test[A-Za-z0-9_]+\(' src/<path>/FooTests.swift
|
||||
```
|
||||
For each, capture the immediately-preceding `///` or `//` comment lines (the test's intent) and the enclosing `// MARK: -` group. Note methods wrapped in `#if DEBUG`.
|
||||
|
||||
3. **For each `*Specs.md`, parse the scenario index.** In the `## Test scenarios` section, collect every `### <Group>` heading and every `- **testName** — description` bullet. Build the set of spec-listed test names and their descriptions.
|
||||
|
||||
4. **Cross-check each triad and classify findings:**
|
||||
- **Orphan test** — `func testX` exists but no `- **testX** —` line in the spec. (Most common drift; spec is stale.)
|
||||
- **Orphan scenario** — spec lists `testX` but no `func testX` in the suite. (Test deleted/renamed; spec stale.)
|
||||
- **Group mismatch** — a test's `// MARK:` group and its spec `###` group disagree (readability drift, not fatal).
|
||||
- **Description drift** — the spec bullet and the test's doc-comment describe materially different behavior. Judge semantically, don't string-match; only flag real divergence.
|
||||
- **Unpaired file** — a `*Tests.swift` with no `*Specs.md` (or vice-versa).
|
||||
|
||||
5. **Lighter code check (kernels only).** For decision-kernel files (`*Testable.swift`, `*Resolver.swift`), list `public`/`static` entry-point functions and `enum … Decision` cases, and flag any with no scenario referencing them — a hint that a branch is undocumented/untested. Skip this for AppKit-coupled production files (intentionally not unit-tested here).
|
||||
|
||||
6. **Optionally repair.** If asked, regenerate a spec's `## Test scenarios` section from the live test methods + their doc-comments (preserving the existing `### Group` order where it matches `// MARK:` sections). Never invent behavior — only restate what the test asserts. Do not edit `*Tests.swift` from this skill.
|
||||
|
||||
## Reporting
|
||||
|
||||
- A per-triad status table: `folder/Base` · #tests · #scenarios · status (✅ in sync / ⚠️ drift / ❌ unpaired).
|
||||
- A punch list grouped by finding type (orphan tests, orphan scenarios, description drift, unpaired, undocumented kernel branches), each with the file + line.
|
||||
- The count of test files still outside `src/` (migration progress).
|
||||
- If nothing is wrong, say so plainly. Then offer to fix the drift (regenerate stale spec scenario indexes) and list exactly which files would change.
|
||||
@@ -0,0 +1,63 @@
|
||||
---
|
||||
name: coverage-explore
|
||||
description: Measure AltTab test coverage and record it as documentation. Runs the Test scheme with coverage into a throwaway result bundle, parses the per-file/region report, refreshes the per-feature badge atop each `*Specs.md` and the root `src/coverage.md` table, then suggests the highest-value missing tests. These two markdown outputs are the ONLY stored coverage — no lcov/html/xcresult is kept, and routine test runs don't gather coverage. Use to refresh coverage docs, find untested kernel branches, or decide what to test next.
|
||||
---
|
||||
|
||||
# /coverage-explore
|
||||
|
||||
## Project context
|
||||
|
||||
AltTab follows the **Humble Object** pattern: logic lives in dependency-free **decision kernels**
|
||||
(`SelectionResolver.swift`, `SearchModeResolver.swift`, the `*Testable.swift` files) compiled into the
|
||||
`unit-tests` target and tested in isolation; the AppKit layer (windows, views, event taps) is kept thin
|
||||
and is deliberately *not* unit-tested. So coverage is meaningful **per kernel / per feature-source** —
|
||||
those should trend to ~100%, every decision branch exercised — and an aggregate app-wide % is not a
|
||||
goal.
|
||||
|
||||
**Coverage is documentation here, not a build artifact.** The only stored coverage is:
|
||||
1. the per-feature badge (`> **Line coverage:** …`) atop each `*Specs.md`, and
|
||||
2. the root **`src/coverage.md`** table.
|
||||
|
||||
There is intentionally **nothing else**: no lcov, no HTML, no committed `.xcresult`, and coverage is
|
||||
**not** gathered on routine runs (`scripts/run_tests.sh`, CI, or Xcode) — only when this skill runs it.
|
||||
(Coverage isn't usefully viewable in IntelliJ, so there's no IDE-integration artifact to maintain.)
|
||||
|
||||
## Workflow
|
||||
|
||||
1. **Measure into a throwaway bundle** (then delete it — store nothing technical):
|
||||
```sh
|
||||
rm -rf /tmp/altcov.xcresult
|
||||
set -o pipefail && xcodebuild test \
|
||||
-project alt-tab-macos.xcodeproj -scheme Test -configuration Release \
|
||||
-enableCodeCoverage YES -resultBundlePath /tmp/altcov.xcresult | scripts/xcbeautify
|
||||
```
|
||||
If the build is red, stop and report — coverage on a failing suite is meaningless.
|
||||
|
||||
2. **Parse** `xcrun xccov view --report --json /tmp/altcov.xcresult`. Each `targets[].files[]` has
|
||||
`path`, `lineCoverage`, `coveredLines`, `executableLines`, `functions[]`. For line-by-line counts of
|
||||
one file while investigating a gap: `xcrun xccov view --file <abs-path> /tmp/altcov.xcresult`.
|
||||
|
||||
3. **Refresh `src/coverage.md`.** Rewrite the table with every `src/` file (excluding `*Tests.swift`)
|
||||
that the test target compiles, sorted worst-coverage-first, as `| NN% (cov/exec) | \`path\` |`.
|
||||
Keep the header note explaining the Humble-Object scope and that this + the badges are the only
|
||||
stored coverage. Update the "updated" date.
|
||||
|
||||
4. **Offer to refresh the per-spec badges.** For each `<Base>Specs.md` whose `<Base>.swift` (or kernel)
|
||||
is in the report, propose updating its `> **Line coverage:** …` line with the new % + today's date.
|
||||
Ask first; only apply the ones the user accepts. Keep honest notes where a file is intentionally
|
||||
partial (e.g. `PreferencesMigrations.swift`).
|
||||
|
||||
5. **Rank gaps & suggest tests.** For kernels below 100%, list the uncovered functions/regions, map each
|
||||
to the behavior it represents (read the source), and write ready-to-paste additions: a
|
||||
`- **testGroupScenario** — …` spec line + a short XCTest stub in that suite's builder style. Don't add
|
||||
tests automatically unless asked.
|
||||
|
||||
6. **Clean up:** `rm -rf /tmp/altcov.xcresult`. Leave no technical coverage files in the tree.
|
||||
|
||||
## Reporting
|
||||
|
||||
- Headline: a small ascending table of kernel `lineCoverage`, and the delta vs the previous `src/coverage.md`.
|
||||
- Ranked gap list: file → uncovered function/branch → the behavior it represents.
|
||||
- Suggested scenarios (spec lines + test stubs) for the top gaps.
|
||||
- State exactly what was rewritten (`src/coverage.md`, which badges). Flag files that are partial *by
|
||||
design* (AppKit glue, excluded migrations) so a low number isn't mistaken for a problem.
|
||||
@@ -0,0 +1,75 @@
|
||||
---
|
||||
name: translate-missing-l10n
|
||||
description: Translate missing l10n strings for AltTab using Claude's multilingual ability. Refreshes the source `Localizable.strings` from current Swift code, finds keys missing in each `<lang>.lproj/Localizable.strings`, translates them, then writes them back via `scripts/l10n/apply_translations.ts`.
|
||||
---
|
||||
|
||||
# /translate-missing-l10n
|
||||
|
||||
## Project context
|
||||
|
||||
AltTab is a macOS app that helps switch between windows, similar to the Windows alt-tab experience. The strings to translate are mostly Settings UI — some tooltips, dialogs, and general user guidance.
|
||||
|
||||
## Target languages (20)
|
||||
|
||||
`de, ja, fr, es, zh-CN, pt-BR, nl, ko, it, pl, ar, zh-HK, vi, tr, sv, th, zh-TW, he, id, ru`
|
||||
|
||||
(`en` is the source — handled automatically by the apply step.)
|
||||
|
||||
## Translation guidelines
|
||||
|
||||
- **Format specifiers must be preserved exactly**: `%@`, `%d`, `%1$@`, `%2$@`, `\n`, `\t`. The number and order of specifiers in the translation must match the source. Positional and unindexed forms are interchangeable (`%@ %@` ≡ `%1$@ %2$@`), but the count must match.
|
||||
- **Do not translate proper nouns**: "AltTab", "macOS", "Mission Control", "Spaces", "Dock".
|
||||
- **Do not translate modifier key names**: "Cmd", "Option", "Alt", "Shift", "Ctrl", "Control", "Fn", "Command".
|
||||
- **Use Apple's official platform terminology** for the target locale where one exists. For example, prefer the term Apple uses in macOS System Settings for that locale ("Réglages" vs "Préférences" in French) over a literal translation.
|
||||
- **Match the source brevity**. Settings strings are short; the translation should be short too. Prefer concise, idiomatic phrasing over literal grammatical completeness.
|
||||
- **Match macOS tone**: neutral, direct, no exclamation marks unless the source has them.
|
||||
- **Comments in the source file are engineer guidance, not user-visible text**. Use them to disambiguate meaning, but never include them in the translation.
|
||||
|
||||
## Workflow
|
||||
|
||||
1. **Refresh the source.** Run:
|
||||
```sh
|
||||
bash scripts/l10n/extract_l10n_strings.sh
|
||||
```
|
||||
This regenerates `resources/l10n/Localizable.strings` from the current Swift code via `genstrings`.
|
||||
|
||||
2. **Parse the source.** Read `resources/l10n/Localizable.strings`. Each entry has the shape:
|
||||
```
|
||||
/* engineer comment */
|
||||
"key" = "value";
|
||||
```
|
||||
Build the ordered list of `(comment, key, value)` triples. The `value` for the source is usually identical to `key`, but may include positional indices (`%1$@`, `%2$@`) when there are multiple specifiers.
|
||||
|
||||
3. **Compute missing keys per language.** For each of the 20 target languages, read `resources/l10n/<lang>.lproj/Localizable.strings`. Each line is `"key" = "translation";`. A key is **missing** if it's present in the source but either (a) absent from the target file, or (b) present with an empty or whitespace-only value. Treat (b) the same as (a) — produce a real translation.
|
||||
|
||||
4. **Stop if nothing is missing.** Report and exit.
|
||||
|
||||
5. **Translate.** For each language, produce a translation for each missing key, applying the guidelines above. Do not invent keys; only translate keys that appear in the source. Group your work into batches of 5–10 languages per call to keep individual outputs manageable.
|
||||
|
||||
6. **Apply each batch.** Write the batch to a fresh file under `/tmp` (not committed) with this shape:
|
||||
```json
|
||||
{
|
||||
"fr": { "About %@": "À propos de %@", "Quit": "Quitter" },
|
||||
"ja": { "About %@": "%@について" }
|
||||
}
|
||||
```
|
||||
Then run:
|
||||
```sh
|
||||
npx ts-node scripts/l10n/apply_translations.ts /tmp/batch-NN.json
|
||||
```
|
||||
The helper:
|
||||
- Validates format specifiers — translations whose specifier set doesn't match the source value are rejected with a clear error and **not merged**.
|
||||
- Always rewrites `en.lproj/Localizable.strings` from source **keys** (each entry written as `"key" = "key";` for symmetry) — no need to include `en` in your batch. This avoids leaking genstrings-rewritten values like `%1$@` into the English file when the original source key uses plain `%@`.
|
||||
- Rewrites each `<lang>.lproj/Localizable.strings` using source order: existing translations are preserved, new translations merged in, and keys no longer in the source are pruned.
|
||||
- Exits with code `2` if any entries were rejected.
|
||||
|
||||
7. **Handle rejections.** If `apply_translations.ts` reports format-specifier mismatches, fix those translations and re-run only the affected language(s) in a follow-up batch. Do not move on while rejections are outstanding.
|
||||
|
||||
## Reporting
|
||||
|
||||
After the run, report:
|
||||
|
||||
- Number of languages processed.
|
||||
- Total translations produced and merged.
|
||||
- Any entries you intentionally left untranslated (e.g., the source was already a proper noun) — list them so the user can decide.
|
||||
- Any format-specifier rejections that required manual fixes.
|
||||
@@ -0,0 +1 @@
|
||||
v11.3.0
|
||||
@@ -0,0 +1,31 @@
|
||||
# Gitea CI on the shared KofTwentyTwo/ci-workflows macOS workflow (grogu). The .github workflows keep
|
||||
# running on the GitHub mirror (release, notarization, appcast); this one is build only and unsigned.
|
||||
#
|
||||
# Guard B the de-paywall marker and the if-false wrap are still in computeState() (docs/PLAN-maintained-fork.md §4.4)
|
||||
# Guard C no unresolved merge conflict markers in the tree
|
||||
# build the Release scheme, unsigned (a compile check at macOS 12.0: grogu has only Xcode 27)
|
||||
# Guard A the Test scheme is compiled (build-for-testing) but not run: the grogu runner has no GUI session for
|
||||
# testmanagerd. Run scripts/run_tests.sh locally before merging (docs/contributing.md).
|
||||
name: build
|
||||
on:
|
||||
push:
|
||||
branches: ['**']
|
||||
jobs:
|
||||
macos:
|
||||
uses: self:KofTwentyTwo/ci-workflows/.gitea/workflows/macos.yaml@v1
|
||||
with:
|
||||
commands: |
|
||||
F=src/pro/license/LicenseManager.swift
|
||||
[ -f "$F" ] || { echo "::error::Guard B: $F not found; chokepoint moved? Refresh the de-paywall patch (§6.2)."; exit 1; }
|
||||
grep -q 'depaywall' "$F" || { echo "::error::PAYWALL RELOCKED: 'depaywall' marker missing from $F (§6.2)."; exit 1; }
|
||||
grep -Eq 'guard[[:space:]]+false[[:space:]]+else' "$F" || { echo "::error::PAYWALL RELOCKED: 'if false { … }' wrap missing from $F (§6.2)."; exit 1; }
|
||||
echo "Guard B OK"
|
||||
if git -c grep.lineNumber=false grep -nE '^(<<<<<<<|=======|>>>>>>>)' -- . ':(exclude)docs/'; then
|
||||
echo "::error::UNRESOLVED CONFLICT MARKERS present; resolve before merge (§4.4 Guard C)."; exit 1
|
||||
fi
|
||||
echo "Guard C OK"
|
||||
# grogu has only Xcode 27.2, which rejects the app's 10.13 deployment target (the shipping build is the GitHub
|
||||
# mirror's, on Xcode 26.0.1). This compile check builds at 12.0 with the same deprecation downgrade as
|
||||
# config/test-base.xcconfig; every other Swift warning stays an error.
|
||||
xcodebuild -quiet -project alt-tab-macos.xcodeproj -scheme Release -derivedDataPath DerivedData CODE_SIGN_IDENTITY=- CODE_SIGNING_REQUIRED=NO CODE_SIGNING_ALLOWED=NO MACOSX_DEPLOYMENT_TARGET=12.0 SWIFT_TREAT_WARNINGS_AS_ERRORS=NO 'OTHER_SWIFT_FLAGS=$(inherited) -warnings-as-errors -Wwarning DeprecatedDeclaration' build
|
||||
xcodebuild -quiet -project alt-tab-macos.xcodeproj -scheme Test -configuration Release -derivedDataPath DerivedData build-for-testing
|
||||
@@ -0,0 +1,9 @@
|
||||
# alt-tab-free [depaywall]: upstream's FUNDING.yml routed the GitHub "Sponsor" button on the fork's
|
||||
# repo to lwouis (github: lwouis / patreon: alt_tab_macos / ko_fi: alt_tab / a lwouis PayPal button) —
|
||||
# i.e. every donation would have gone to the author the fork just de-paywalled (PLAN §3.5). It has
|
||||
# been emptied so NO sponsor button renders and no donation routes to the upstream author.
|
||||
#
|
||||
# OWNER-OVERRIDABLE-BEFORE-PUBLISH: to accept sponsorships for THIS fork, uncomment and fill in your
|
||||
# own handles (do NOT re-add lwouis's). Otherwise leave this file empty / delete it.
|
||||
# github: <fork-owner-handle>
|
||||
# custom: ["https://<fork-domain>/donate"]
|
||||
@@ -13,7 +13,7 @@ A clear and concise description of what the bug is. What happened, and what you
|
||||
|
||||
**Screenshots / video**
|
||||
|
||||
Add screenshots to help explain your problem. You can also use Loom to easily record your screen to show the bug in action. That is *really* helpful!
|
||||
Add screenshots to help explain your problem. You can also record your screen to show the bug in action. That is *really* helpful!
|
||||
|
||||
**Steps to reproduce the bug**
|
||||
|
||||
@@ -21,3 +21,9 @@ Add screenshots to help explain your problem. You can also use Loom to easily re
|
||||
2. Click on '....'
|
||||
3. Scroll down to '....'
|
||||
4. See error
|
||||
|
||||
**Your environment**
|
||||
|
||||
* AltTab version:
|
||||
* macOS version:
|
||||
* Other relevant info: e.g. using external keyboard, multiple monitors, etc
|
||||
@@ -0,0 +1,226 @@
|
||||
# alt-tab-free: fork-owned CI/CD workflow (diverged from upstream lwouis/alt-tab-macos).
|
||||
# Upstream's ci_cd.yml fires `on: push: branches: [master]` ONLY and is the sole workflow,
|
||||
# so this fork ADDS a `pull_request:` trigger to gate the sync PR pre-merge (see PLAN §4.2),
|
||||
# gates every release/push-only step under `if: github.event_name == 'push'`, and wires the
|
||||
# anti-relock + conflict-marker greps as the FIRST push-job steps so a relocked or conflicted
|
||||
# tree aborts BEFORE update_appcast.sh (Sparkle sign), semantic-release (tag push), and the
|
||||
# gh-release. This file is in the §6.2 "keep ours" recurring-conflict set — on an upstream
|
||||
# ci_cd.yml change, reconcile manually and re-confirm the `pull_request:` trigger survives.
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
# alt-tab-free [depaywall]: PR trigger so the sync PR runs the guards pre-merge (PLAN §4.2).
|
||||
# A separate fork-owned .github/workflows/guard.yml ALSO runs the guards on pull_request, so a
|
||||
# careless upstream-conflict resolution that strips this trigger cannot silently disarm the gate.
|
||||
pull_request:
|
||||
branches:
|
||||
- master
|
||||
# alt-tab-free [depaywall]: GitHub defaults GITHUB_TOKEN to read-only since 2023; semantic-release
|
||||
# (@semantic-release/git) pushes commits+tags to master and softprops/action-gh-release creates the
|
||||
# release — both need contents:write, else they 403 and the appcast enclosure 404s for auto-update
|
||||
# (PLAN §4.3).
|
||||
permissions:
|
||||
contents: write
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
APPCENTER_SECRET: ${{ secrets.APPCENTER_SECRET }}
|
||||
APPCENTER_TOKEN: ${{ secrets.APPCENTER_TOKEN }}
|
||||
APPLE_ID: ${{ secrets.APPLE_ID }}
|
||||
APPLE_PASSWORD: ${{ secrets.APPLE_PASSWORD }}
|
||||
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
|
||||
APPLE_P12_CERTIFICATE: ${{ secrets.APPLE_P12_CERTIFICATE }}
|
||||
# alt-tab-free [depaywall]: optional .p12 export password (empty if exported password-less).
|
||||
# Not part of the release-gate AND — the cert import tolerates an empty password.
|
||||
APPLE_P12_PASSWORD: ${{ secrets.APPLE_P12_PASSWORD }}
|
||||
SPARKLE_ED_PRIVATE_KEY: ${{ secrets.SPARKLE_ED_PRIVATE_KEY }}
|
||||
GITHUB_EVENT_BEFORE: ${{ github.event.before }}
|
||||
GITHUB_EVENT_AFTER: ${{ github.event.after }}
|
||||
BUILD_DIR: ${{ github.workspace }}
|
||||
XCODE_BUILD_PATH: DerivedData/Build/Products/Release
|
||||
VERSION_FILE: VERSION.txt
|
||||
# alt-tab-free [depaywall]: must equal PRODUCT_NAME in config/local.xcconfig
|
||||
# (PLAN §3.1/§4.3) or the build/package/appcast scripts mismatch the artifact.
|
||||
APP_NAME: CommandTabFree
|
||||
jobs:
|
||||
build:
|
||||
runs-on: macos-15 # all options: https://github.com/actions/runner-images/tree/main
|
||||
environment: production
|
||||
steps:
|
||||
# - env:
|
||||
# GITHUB_CONTEXT: ${{ toJSON(github) }}
|
||||
# run: echo "$GITHUB_CONTEXT"
|
||||
- id: pre-checkout
|
||||
env:
|
||||
COMMITS: ${{ toJson(github.event.commits) }}
|
||||
run: |
|
||||
if [[ "${{ github.event_name }}" == "push" ]]; then
|
||||
COUNT=$(( $(echo "$COMMITS" | jq length) + 30 ))
|
||||
else
|
||||
COUNT=1
|
||||
fi
|
||||
echo "fetch-depth=$COUNT" >> "$GITHUB_OUTPUT"
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
fetch-depth: ${{ steps.pre-checkout.outputs.fetch-depth }}
|
||||
# alt-tab-free [depaywall]: Guard B + Guard C run FIRST on the push job (before run_tests.sh,
|
||||
# and therefore before update_appcast.sh's Sparkle sign + semantic-release's tag push) so a
|
||||
# relock or a still-conflicted tree aborts the job BEFORE any irreversible side-effect (PLAN
|
||||
# §4.4 "Where the guards run"). They are near-instant target-independent greps, so running them
|
||||
# first is free. They also run on the PR path for the fastest red signal. A second copy sits
|
||||
# just before action-gh-release as belt-and-suspenders.
|
||||
- name: anti-relock — verify de-paywall marker present (Guard B)
|
||||
run: |
|
||||
grep -q 'depaywall' src/pro/license/LicenseManager.swift \
|
||||
|| { echo "PAYWALL RELOCKED: depaywall marker missing from computeState()"; exit 1; }
|
||||
# The marker proves the patch is present; pair with the if-false wrap the patch introduces.
|
||||
# A bare `return .pro` grep is NOT load-bearing (it matches the unpatched line 187 too).
|
||||
grep -Eq 'guard[[:space:]]+false[[:space:]]+else' src/pro/license/LicenseManager.swift \
|
||||
|| { echo "PAYWALL RELOCKED: 'guard false else' (forced .pro) missing from computeState()"; exit 1; }
|
||||
- name: anti-conflict — no unresolved merge markers (Guard C)
|
||||
run: |
|
||||
if git -c grep.lineNumber=false grep -nE '^(<<<<<<<|=======|>>>>>>>)' -- . ':(exclude)docs/'; then
|
||||
echo "UNRESOLVED CONFLICT MARKERS present — resolve before merge/release"; exit 1
|
||||
fi
|
||||
# alt-tab-free [depaywall]: release-readiness gate (PLAN §3.3/§4.3 owner-secret checklist).
|
||||
# The fork ships unsigned/ad-hoc until the OWNER provisions the Apple Developer ID cert and the
|
||||
# Sparkle key. We do NOT hard-fail when those secrets are absent — that would make every push
|
||||
# RED on a fork that is correctly built but not yet signing-provisioned. Instead we compute a
|
||||
# boolean from secret presence and SKIP the sign/notarize/appcast/release tail cleanly when it
|
||||
# is false. The build + Guard A (run_tests.sh) still run on push, so push CI is GREEN today and
|
||||
# the release tail FLIPS ON automatically the moment the owner adds the secrets. Secrets are not
|
||||
# available in step-level `if:` directly, so map them to env and emit an output here.
|
||||
- name: release-gate — are signing/notarization secrets present?
|
||||
id: release-gate
|
||||
env:
|
||||
HAS_P12: ${{ secrets.APPLE_P12_CERTIFICATE != '' }}
|
||||
HAS_APPLE_ID: ${{ secrets.APPLE_ID != '' }}
|
||||
HAS_APPLE_PASSWORD: ${{ secrets.APPLE_PASSWORD != '' }}
|
||||
HAS_APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID != '' }}
|
||||
HAS_SPARKLE_KEY: ${{ secrets.SPARKLE_ED_PRIVATE_KEY != '' }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
if [ "$HAS_P12" = "true" ] && [ "$HAS_APPLE_ID" = "true" ] \
|
||||
&& [ "$HAS_APPLE_PASSWORD" = "true" ] && [ "$HAS_APPLE_TEAM_ID" = "true" ] \
|
||||
&& [ "$HAS_SPARKLE_KEY" = "true" ]; then
|
||||
echo "signing-ready=true" >> "$GITHUB_OUTPUT"
|
||||
echo "All signing/notarization/Sparkle secrets present — release tail WILL run on push."
|
||||
else
|
||||
echo "signing-ready=false" >> "$GITHUB_OUTPUT"
|
||||
echo "::warning::Signing/notarization/Sparkle secrets absent — release tail (codesign/notarize/appcast/semantic-release/gh-release) is SKIPPED. Build + Guard A still run. Provision APPLE_P12_CERTIFICATE / APPLE_ID / APPLE_PASSWORD / APPLE_TEAM_ID / SPARKLE_ED_PRIVATE_KEY in the 'production' Environment to enable releases (PLAN §3.3/§3.4)."
|
||||
fi
|
||||
- uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: 16 # PLAN §4.3: keep node-16; the package.json engines mismatch only warns
|
||||
cache: 'npm'
|
||||
- run: sudo xcode-select -s /Applications/Xcode_26.0.1.app/Contents/Developer
|
||||
- run: scripts/print_env.sh
|
||||
- run: npm ci
|
||||
# alt-tab-free [depaywall]: commitlint is gated to the push path. On push it lints the whole
|
||||
# pushed range, which drags in upstream's non-conforming messages on a `chore(sync)` merge —
|
||||
# this is handled by an `ignores` predicate in commitlint.config.js (PLAN §4.3) that drops the
|
||||
# synthetic merge commit and upstream's own messages; do NOT break commitlint on --no-ff merges.
|
||||
# $GITHUB_EVENT_BEFORE/AFTER are push-context (empty on pull_request), so a PR-safe variant runs
|
||||
# below on the PR path instead.
|
||||
- run: npx commitlint --from "$GITHUB_EVENT_BEFORE" --to "$GITHUB_EVENT_AFTER" --verbose
|
||||
if: github.event_name == 'push'
|
||||
# alt-tab-free [depaywall]: PR-safe commitlint — lints base..head (valid on pull_request),
|
||||
# paired with the commitlint.config.js `ignores` predicate (PLAN §4.2/§4.3).
|
||||
- run: npx commitlint --from "${{ github.event.pull_request.base.sha }}" --to "${{ github.sha }}" --verbose
|
||||
if: github.event_name == 'pull_request'
|
||||
# Runs on both push and PR: catches an un-regenerated l10n/generated-file diff (PLAN §4.2/§4.5).
|
||||
- run: scripts/ensure_generated_files_are_up_to_date.sh
|
||||
# alt-tab-free [depaywall]: release-path — a semantic-release dry-run against a PR head is
|
||||
# meaningless and can mis-bump, so gate to push (PLAN §4.2).
|
||||
- run: scripts/determine_next_version.sh
|
||||
if: github.event_name == 'push'
|
||||
# alt-tab-free [depaywall]: decouple the release apparatus from "every signing-ready push".
|
||||
# determine_next_version.sh writes an EMPTY VERSION.txt when no commit since the last tag is
|
||||
# release-worthy (semantic-release dry-run finds no bump). Gate the sign/notarize/release steps
|
||||
# on this so a docs/ci/chore push still runs guards + commitlint + run_tests (Release-config
|
||||
# compile check) but does NOT sign-build, notarize, or cut a release — no [skip ci] needed (§4.3).
|
||||
- name: release-decision — did semantic-release compute a version?
|
||||
id: release-decision
|
||||
if: github.event_name == 'push'
|
||||
run: |
|
||||
if [ -n "$(tr -d '[:space:]' < "$VERSION_FILE" 2>/dev/null)" ]; then
|
||||
echo "will-release=true" >> "$GITHUB_OUTPUT"
|
||||
echo "Release-worthy: semantic-release will cut v$(cat "$VERSION_FILE")."
|
||||
else
|
||||
echo "will-release=false" >> "$GITHUB_OUTPUT"
|
||||
echo "::notice::No release-worthy commit — build/test only; notarize+release tail skipped."
|
||||
fi
|
||||
# Inject the release version only when releasing; an empty CURRENT_PROJECT_VERSION would crash
|
||||
# App.version (see config/local.xcconfig). Non-release builds use the committed 0.0.0 default.
|
||||
- run: scripts/replace_environment_variables_in_app.sh
|
||||
if: github.event_name == 'push' && steps.release-decision.outputs.will-release == 'true'
|
||||
# codesign needs APPLE_P12_CERTIFICATE — gated on signing-ready so a not-yet-provisioned fork
|
||||
# does not RED on the missing-cert import (PLAN §3.3/§4.3).
|
||||
- run: scripts/codesign/setup_ci_master.sh
|
||||
if: github.event_name == 'push' && steps.release-gate.outputs.signing-ready == 'true' && steps.release-decision.outputs.will-release == 'true'
|
||||
# Guard A — the anti-relock unit test (appended to LicenseManagerTests.swift). Runs on both
|
||||
# push and PR (no secrets needed); on the push path it sits before the sign/tag steps below
|
||||
# so a relock fails BEFORE any irreversible side-effect (PLAN §4.4). This is what keeps push
|
||||
# CI GREEN today on the signing-unprovisioned fork.
|
||||
- run: scripts/run_tests.sh
|
||||
# alt-tab-free [depaywall]: build the Release product to prove the tree compiles. The fork
|
||||
# inherits upstream's Developer ID via config/release.xcconfig (CODE_SIGN_IDENTITY), which is
|
||||
# absent on CI until the owner provisions the Apple cert — so when the release gate is NOT
|
||||
# signing-ready we build AD-HOC (CODE_SIGN_IDENTITY=-, signing not required, no timestamp) to
|
||||
# prove the tree compiles without a cert. When the owner adds the secrets AND sets the fork's
|
||||
# Developer ID in config/local.xcconfig, this builds signed for the release tail (PLAN §3.3/§4.3).
|
||||
- name: build app (signed — owner cert provisioned)
|
||||
if: github.event_name == 'push' && steps.release-gate.outputs.signing-ready == 'true' && steps.release-decision.outputs.will-release == 'true'
|
||||
run: scripts/build_app.sh
|
||||
- name: build app (ad-hoc — until signing-provisioned)
|
||||
if: github.event_name == 'push' && steps.release-gate.outputs.signing-ready != 'true'
|
||||
run: scripts/build_app.sh CODE_SIGN_IDENTITY=- CODE_SIGNING_REQUIRED=NO OTHER_CODE_SIGN_FLAGS=--timestamp=none
|
||||
# --- Release tail: requires the owner's Apple Developer ID + Sparkle secrets. Skipped cleanly
|
||||
# (NOT failed) when they are absent, so push CI stays GREEN until the owner provisions them. ---
|
||||
- run: scripts/package_and_notarize_release.sh
|
||||
if: github.event_name == 'push' && steps.release-gate.outputs.signing-ready == 'true' && steps.release-decision.outputs.will-release == 'true'
|
||||
# alt-tab-free [depaywall]: upload_symbols_to_appcenter.sh REMOVED — telemetry is off (PLAN §3.4).
|
||||
- run: scripts/update_appcast.sh
|
||||
if: github.event_name == 'push' && steps.release-gate.outputs.signing-ready == 'true' && steps.release-decision.outputs.will-release == 'true'
|
||||
# alt-tab-free [depaywall]: update_readme_and_website.sh REMOVED — it queried the UPSTREAM
|
||||
# lwouis/alt-tab-macos GitHub API for stars/downloads and rewrote docs/contributors.md from the
|
||||
# UPSTREAM contributor graph (semantic mismatch for the fork) and dispatched the lwouis website.
|
||||
# Cosmetic only; dropped per PLAN §4.0/§4.3 (alongside update_website.sh, already removed).
|
||||
- run: npx semantic-release
|
||||
if: github.event_name == 'push' && steps.release-gate.outputs.signing-ready == 'true' && steps.release-decision.outputs.will-release == 'true'
|
||||
- id: set_release_info
|
||||
run: scripts/extract_latest_changelog.sh
|
||||
if: github.event_name == 'push' && steps.release-gate.outputs.signing-ready == 'true' && steps.release-decision.outputs.will-release == 'true'
|
||||
# alt-tab-free [depaywall]: belt-and-suspenders copy of Guard B + Guard C immediately before the
|
||||
# release is created. The load-bearing copy is at the top of the job (pre-tag-push); this second
|
||||
# copy guards the gh-release object itself (PLAN §4.4).
|
||||
- name: anti-relock + anti-conflict re-check (belt-and-suspenders)
|
||||
if: github.event_name == 'push' && steps.release-gate.outputs.signing-ready == 'true' && steps.release-decision.outputs.will-release == 'true'
|
||||
run: |
|
||||
grep -q 'depaywall' src/pro/license/LicenseManager.swift \
|
||||
|| { echo "PAYWALL RELOCKED: depaywall marker missing from computeState()"; exit 1; }
|
||||
grep -Eq 'guard[[:space:]]+false[[:space:]]+else' src/pro/license/LicenseManager.swift \
|
||||
|| { echo "PAYWALL RELOCKED: 'guard false else' (forced .pro) missing from computeState()"; exit 1; }
|
||||
if git -c grep.lineNumber=false grep -nE '^(<<<<<<<|=======|>>>>>>>)' -- . ':(exclude)docs/'; then
|
||||
echo "UNRESOLVED CONFLICT MARKERS present — resolve before release"; exit 1
|
||||
fi
|
||||
# alt-tab-free [depaywall]: point the release at THIS fork's repo via the ambient GITHUB_TOKEN
|
||||
# (release.config.js has no @semantic-release/github plugin, so softprops is the sole release
|
||||
# creator — PLAN §4.3). repository defaults to ${{ github.repository }} = the fork.
|
||||
- uses: softprops/action-gh-release@v3
|
||||
if: github.event_name == 'push' && steps.release-gate.outputs.signing-ready == 'true' && steps.release-decision.outputs.will-release == 'true'
|
||||
with:
|
||||
repository: ${{ github.repository }}
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
tag_name: ${{ steps.set_release_info.outputs.tag_name }}
|
||||
body: ${{ steps.set_release_info.outputs.body }}
|
||||
files: ${{ env.XCODE_BUILD_PATH }}/*.zip
|
||||
# alt-tab-free [depaywall]: the release tail intentionally does NOT advance .fork-sync-state.
|
||||
# That file is the UPSTREAM-sync cursor — the last-synced UPSTREAM tag (e.g. v11.3.0), read by
|
||||
# upstream_sync.yml to pick "the oldest upstream tag strictly newer than it." The fork versions
|
||||
# independently (semantic-release cuts v100.x), so a prior revision's step that wrote the FORK
|
||||
# release tag here set the cursor to v100.x — after which no upstream tag is ever "newer" and
|
||||
# upstream-sync would silently never fire. The cursor must instead be advanced by the
|
||||
# upstream-sync merge flow, recording the UPSTREAM tag it synced (to be wired with
|
||||
# upstream_sync.yml — PLAN §4.2). Until then .fork-sync-state stays at the last real upstream tag.
|
||||
# alt-tab-free [depaywall]: update_website.sh (lwouis website dispatch) REMOVED (PLAN §3.4/§4.2).
|
||||
@@ -0,0 +1,94 @@
|
||||
# alt-tab-free — anti-relock guard workflow (docs/PLAN-maintained-fork.md §4.4)
|
||||
#
|
||||
# A SEPARATE, fork-owned workflow that runs the anti-relock guards on every PR,
|
||||
# INDEPENDENTLY of ci_cd.yml. Rationale (§4.4): ci_cd.yml is the file most likely to take an
|
||||
# upstream conflict, and a careless "keep ours/theirs" resolution could silently strip its
|
||||
# pull_request: trigger or its release-step gating — at which point the in-ci_cd.yml guards
|
||||
# would stop gating PRs with NO error. A guard workflow that does NOT inherit ci_cd.yml's
|
||||
# trigger survives that. This is the reviewer's primary green/red signal on a sync PR.
|
||||
#
|
||||
# Three guards, each fails the job LOUDLY:
|
||||
# A — the Test scheme (the mock-injected anti-relock unit tests in LicenseManagerTests.swift)
|
||||
# B — a target-independent grep that the de-paywall flip is still present in computeState()
|
||||
# C — a tree-wide grep that no unresolved git conflict markers survive into a merge/release
|
||||
#
|
||||
# Defense-in-depth note: Guards B + C are ALSO wired into ci_cd.yml as the FIRST push-job
|
||||
# steps (CI agent owns that) so a relock/conflict-laden tree aborts the release BEFORE the tag
|
||||
# is pushed or the binary signed, even if this PR gate is ever missing/misconfigured (§4.4).
|
||||
name: guard
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches: [master]
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
# Guards B + C are near-instant greps; run them on cheap ubuntu so a relock or a still-
|
||||
# conflicted tree fails FAST, before the expensive macOS Guard-A job even starts (§4.5).
|
||||
grep-guards:
|
||||
name: anti-relock greps (B + C)
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
# GUARD B — verify the de-paywall flip is still present at the single state producer.
|
||||
# §4.4 pitfall: a bare `return .pro` grep MATCHES THE UNPATCHED FILE (computeState() already
|
||||
# contains `return .pro` in the keychain-valid branch on a fully relocked tree), so it is NOT
|
||||
# load-bearing. The load-bearing signals are (1) the `depaywall` marker comment, which exists
|
||||
# ONLY because the fork added it, AND (2) the `if false { … }` wrap that the patch introduces
|
||||
# around the original (now dead) body. Both are absent on an unpatched/relocked upstream file.
|
||||
- name: Guard B — de-paywall marker + if-false wrap present in computeState()
|
||||
run: |
|
||||
set -euo pipefail
|
||||
F=src/pro/license/LicenseManager.swift
|
||||
if [ ! -f "$F" ]; then
|
||||
echo "::error::Guard B: $F not found — chokepoint moved? Refresh the de-paywall patch (§6.2)."
|
||||
exit 1
|
||||
fi
|
||||
grep -q 'depaywall' "$F" || {
|
||||
echo "::error::PAYWALL RELOCKED: 'depaywall' marker missing from $F — the de-paywall flip was lost in a merge. Refresh the patch (§6.2)."
|
||||
exit 1
|
||||
}
|
||||
grep -Eq 'guard[[:space:]]+false[[:space:]]+else' "$F" || {
|
||||
echo "::error::PAYWALL RELOCKED: 'if false { … }' wrap missing from $F — the de-paywall body-suppress was lost. Refresh the patch (§6.2)."
|
||||
exit 1
|
||||
}
|
||||
echo "Guard B OK: de-paywall marker + if-false wrap present in $F."
|
||||
|
||||
# GUARD C — no unresolved git conflict markers anywhere in the tree. The §4.2 sync cron
|
||||
# deliberately commits a conflicted tree (markers + --no-verify) for a human to resolve;
|
||||
# nothing else guarantees the human removed EVERY marker before merge. A markers-laden
|
||||
# commit merged to master becomes a permanent ancestor of the published tag (un-compilable
|
||||
# source), contradicting §5(1). Exclude docs/ so this plan's own literal marker examples
|
||||
# don't trip the grep.
|
||||
- name: Guard C — no unresolved merge conflict markers
|
||||
run: |
|
||||
set -euo pipefail
|
||||
if git -c grep.lineNumber=false grep -nE '^(<<<<<<<|=======|>>>>>>>)' -- . ':(exclude)docs/'; then
|
||||
echo "::error::UNRESOLVED CONFLICT MARKERS present — resolve before merge/release (§4.4 Guard C)."
|
||||
exit 1
|
||||
fi
|
||||
echo "Guard C OK: no unresolved conflict markers."
|
||||
|
||||
# GUARD A — the behavioral anti-relock unit tests (mock-injected LicenseManager) via the Test
|
||||
# scheme. §4.4: this MUST run on macos-15 with the pinned Xcode — run_tests.sh is `xcodebuild
|
||||
# test`, which cannot run on ubuntu. If left on ubuntu, Guard A silently degrades to greps-only
|
||||
# and the behavioral relock test never executes (the exact silent-disarming class this plan
|
||||
# guards against). Bootstrap assertion (§4.4): on the deliberately-relocked acceptance PR,
|
||||
# confirm THIS job produced a real test result that is RED on the relock — not just the greps.
|
||||
unit-tests:
|
||||
name: anti-relock unit tests (A)
|
||||
runs-on: macos-15 # MUST match ci_cd.yml — `xcodebuild test` needs the pinned Xcode
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
# Pin the same Xcode as ci_cd.yml (§4.5 bootstrap invariant — re-pin on every upstream bump).
|
||||
- run: sudo xcode-select -s /Applications/Xcode_26.0.1.app/Contents/Developer
|
||||
# run_tests.sh runs the `Test` scheme (-configuration Release), which includes the
|
||||
# appended anti-relock tests in src/pro/license/LicenseManagerTests.swift:
|
||||
# testDepaywallProNeverLocked — load-bearing `state == .pro` assert
|
||||
# testDepaywallStillProAfterTrialExpiry — discriminating (expired-trial) assert
|
||||
# A relock makes computeState() fall back to computeTrialState() and these go RED.
|
||||
- name: Guard A — run the Test scheme (anti-relock unit tests)
|
||||
run: scripts/run_tests.sh
|
||||
@@ -0,0 +1,226 @@
|
||||
# alt-tab-free — upstream sync workflow (docs/PLAN-maintained-fork.md §4.2)
|
||||
#
|
||||
# Polls lwouis/alt-tab-macos for new release tags and opens a reviewable sync PR.
|
||||
# Selects the OLDEST upstream tag strictly newer than the last-merged tag recorded in
|
||||
# `.fork-sync-state` (NOT "newest tag minus --is-ancestor"). This is the §4.2
|
||||
# never-drop-commits invariant: `.fork-sync-state` advances ONLY when a sync PR merges to
|
||||
# master (a ci_cd.yml push-job step — owned by the CI agent), so a sync PR that is
|
||||
# closed-without-merge or abandoned can never silently skip its tag — that tag stays "next"
|
||||
# until it is actually merged. This subsumes the old `merge-base --is-ancestor` guard.
|
||||
#
|
||||
# A release is cut only when a human merges the PR to master (ci_cd.yml on: push). This
|
||||
# workflow never releases; it only opens the PR for review.
|
||||
name: upstream-sync
|
||||
|
||||
on:
|
||||
# §4.2 cron-durability note: the in-repo `schedule:` SILENTLY auto-disables after 60 days
|
||||
# of repo inactivity (no error, no notification). It is kept here as belt-and-suspenders.
|
||||
# The RECOMMENDED PRIMARY trigger is an EXTERNAL scheduler hitting `repository_dispatch`
|
||||
# carrying the SYNC_BOT_TOKEN (immune to the 60-day rule, and composes with the
|
||||
# workflow-re-trigger token). See ownerActions / §4.2.
|
||||
schedule:
|
||||
- cron: '0 7 * * *' # daily; cheap, idempotent
|
||||
workflow_dispatch: {}
|
||||
repository_dispatch:
|
||||
types: [upstream-sync] # external-scheduler entry point (§4.2 primary durability path)
|
||||
|
||||
# GITHUB_TOKEN is read-only by default; opening a PR + pushing a branch need these.
|
||||
permissions:
|
||||
contents: write
|
||||
pull-requests: write
|
||||
|
||||
jobs:
|
||||
sync:
|
||||
runs-on: ubuntu-latest # pure git, no Xcode
|
||||
env:
|
||||
# CRITICAL (§4.2 BLOCKING) — do NOT use ${{ github.token }} here. By GitHub's documented
|
||||
# recursion-prevention rule, a branch push / PR authored with the default GITHUB_TOKEN
|
||||
# does NOT trigger other workflows — so guard.yml and ci_cd.yml's pull_request: checks
|
||||
# would NEVER fire on the sync PR and the entire pre-merge gate would silently never run.
|
||||
# SYNC_BOT_TOKEN MUST be a fork-owned credential that re-triggers workflows: a GitHub App
|
||||
# installation token (preferred — least-privilege, short-lived) or a fork-owned PAT.
|
||||
GH_TOKEN: ${{ secrets.SYNC_BOT_TOKEN }}
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
fetch-depth: 0
|
||||
# Persist the fork-owned credential (NOT the default GITHUB_TOKEN) as the push
|
||||
# credential, so the branch push below re-triggers guard.yml / ci_cd.yml PR checks.
|
||||
token: ${{ secrets.SYNC_BOT_TOKEN }}
|
||||
|
||||
- name: Fail closed if SYNC_BOT_TOKEN is missing
|
||||
run: |
|
||||
# §4.2: without the workflow-re-triggering token the pre-merge gate is silently
|
||||
# disarmed. Fail loudly here rather than open a check-less PR.
|
||||
if [ -z "${SYNC_BOT_TOKEN:-}" ]; then
|
||||
echo "::error::SYNC_BOT_TOKEN is not set. Refusing to open a check-less sync PR (§4.2)."
|
||||
echo "Provision a GitHub App installation token (preferred) or fork-owned PAT as SYNC_BOT_TOKEN."
|
||||
exit 1
|
||||
fi
|
||||
env:
|
||||
SYNC_BOT_TOKEN: ${{ secrets.SYNC_BOT_TOKEN }}
|
||||
|
||||
- name: Add upstream + fetch tags
|
||||
run: |
|
||||
set -euo pipefail
|
||||
git remote add upstream https://github.com/lwouis/alt-tab-macos.git || true
|
||||
git fetch upstream --tags --force
|
||||
|
||||
- name: Verify sync labels exist (§4.2 — fail closed, never treat empty as "no open sync")
|
||||
run: |
|
||||
set -euo pipefail
|
||||
# Guard 2 below and the §0.1 trip-wire query `sync` / `conflict` / `chokepoint-refresh`.
|
||||
# `gh pr list --label <missing>` returns an EMPTY LIST, not an error, for a label that
|
||||
# was never created — so on a fresh repo Guard 2 would silently no-op. Fail closed if a
|
||||
# required label is absent; the bootstrap (§6.1) creates them with `gh label create`.
|
||||
missing=0
|
||||
for L in sync conflict chokepoint-refresh; do
|
||||
if ! gh label list --json name --jq '.[].name' | grep -qxF "$L"; then
|
||||
echo "::error::Required label '$L' is missing. Create it at bootstrap (§6.1): gh label create $L"
|
||||
missing=1
|
||||
fi
|
||||
done
|
||||
[ "$missing" -eq 0 ] || exit 1
|
||||
|
||||
- name: Select oldest upstream tag strictly newer than .fork-sync-state
|
||||
id: tag
|
||||
run: |
|
||||
set -euo pipefail
|
||||
# State-file-driven selection (§4.2): NOT "newest tag minus --is-ancestor".
|
||||
last_synced="$(tr -d '[:space:]' < .fork-sync-state)"
|
||||
if [ -z "$last_synced" ]; then
|
||||
echo "::error::.fork-sync-state is empty; seed it with the last-merged upstream tag (e.g. v11.3.0)."
|
||||
exit 1
|
||||
fi
|
||||
echo "Last synced (from .fork-sync-state): $last_synced"
|
||||
|
||||
# UPSTREAM-ONLY enumeration: `git tag -l 'v*'` would also match the FORK's OWN
|
||||
# semantic-release tags (the fork cuts vX.Y.Z every sync — §4.3), so list tags from
|
||||
# the upstream remote explicitly. Sort by version; keep only tags strictly newer than
|
||||
# the recorded one; take the OLDEST of those (resolve-in-order — §4.2).
|
||||
all_newer="$(git ls-remote --tags --refs upstream 'v*' \
|
||||
| awk -F/ '{print $NF}' \
|
||||
| sort -V \
|
||||
| awk -v last="$last_synced" '
|
||||
$0 == last { seen = 1; next }
|
||||
seen { print }
|
||||
')"
|
||||
# Fallback for the case where last_synced is not itself present upstream: take every
|
||||
# tag that sorts strictly after it.
|
||||
if [ -z "$all_newer" ]; then
|
||||
all_newer="$(git ls-remote --tags --refs upstream 'v*' \
|
||||
| awk -F/ '{print $NF}' \
|
||||
| sort -V \
|
||||
| awk -v last="$last_synced" '{ if ($0 != last) print $0 }' \
|
||||
| while read -r t; do
|
||||
# keep only tags that sort AFTER last_synced
|
||||
if [ "$(printf '%s\n%s\n' "$last" "$t" | sort -V | tail -1)" = "$t" ] && [ "$t" != "$last" ]; then
|
||||
echo "$t"
|
||||
fi
|
||||
done)"
|
||||
fi
|
||||
next="$(printf '%s\n' "$all_newer" | sed '/^$/d' | head -1)"
|
||||
|
||||
if [ -z "$next" ]; then
|
||||
echo "Up to date: no upstream tag newer than $last_synced."
|
||||
echo "skip=true" >> "$GITHUB_OUTPUT"
|
||||
exit 0
|
||||
fi
|
||||
echo "Next tag to sync (oldest strictly newer): $next"
|
||||
|
||||
# Guard — a prior sync is still OPEN (resolve-in-order, §4.2): do NOT open a new PR
|
||||
# while any sync/* PR is unmerged. A backlog pauses syncing until the human merges it;
|
||||
# this is the never-drop-commits behavior. The liveness alert keys off the stalled flag.
|
||||
# `gh pr list --label sync` fails closed because the label existence was verified above.
|
||||
open_sync="$(gh pr list --state open --label sync --json number,headRefName,createdAt \
|
||||
--jq 'sort_by(.createdAt) | .[0] // empty')"
|
||||
if [ -n "$open_sync" ]; then
|
||||
echo "A prior sync PR is still open (resolve-in-order): $open_sync"
|
||||
echo "skip=true" >> "$GITHUB_OUTPUT"
|
||||
echo "stalled=true" >> "$GITHUB_OUTPUT" # liveness alert keys off this (§4.2)
|
||||
exit 0
|
||||
fi
|
||||
|
||||
echo "latest=$next" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Merge onto a sync branch (real merge commit, conventional message)
|
||||
if: steps.tag.outputs.skip != 'true'
|
||||
id: merge
|
||||
run: |
|
||||
set -euo pipefail
|
||||
TAG='${{ steps.tag.outputs.latest }}'
|
||||
git config user.name "fork-sync-bot"
|
||||
git config user.email "fork-sync-bot@users.noreply.github.com"
|
||||
git switch -c "sync/${TAG}"
|
||||
# NO --no-commit: let git author a REAL merge commit with a CONVENTIONAL message so
|
||||
# commitlint / @semantic-release/commit-analyzer stay happy (§4.3). On conflict, commit
|
||||
# the conflicted tree (markers included, --no-verify) so the PR shows the markers for a
|
||||
# human to resolve; Guard C (guard.yml + ci_cd.yml) then blocks it from ever shipping.
|
||||
if git merge --no-ff -m "chore(sync): merge upstream ${TAG}" "${TAG}"; then
|
||||
conflicts=false
|
||||
else
|
||||
git add -A
|
||||
git commit --no-verify \
|
||||
-m "chore(sync): merge upstream ${TAG} (CONFLICTS — resolve before merge)"
|
||||
conflicts=true
|
||||
fi
|
||||
echo "conflicts=${conflicts}" >> "$GITHUB_OUTPUT"
|
||||
|
||||
# §0.1 trip-wire A — surface chokepoint churn as a per-sync signal. A nonzero count
|
||||
# means an upstream change touched a de-paywall chokepoint file in this merge range
|
||||
# (last-synced tag .. new tag); the PR is labeled `chokepoint-refresh` (below) so the
|
||||
# trip-wire is a single query, not a memory exercise.
|
||||
CHOKEPOINTS="src/pro/license/LicenseManager.swift src/pro/ProFeature.swift src/preferences/PreferenceDefinition.swift src/pro/scheduling/ProTransitionManager.swift src/pro/license/LicenseCookie.swift src/vendors/AppCenterCrashes.swift"
|
||||
LAST_SYNCED="$(tr -d '[:space:]' < .fork-sync-state)"
|
||||
churn="$(git rev-list --count "${LAST_SYNCED}..${TAG}" -- $CHOKEPOINTS 2>/dev/null || echo 0)"
|
||||
# Also flag if the merge left a conflict in any chokepoint file (the conflicted commit
|
||||
# is HEAD; compare against its first parent HEAD~1 = the pre-merge master tip).
|
||||
chokepoint_conflict=0
|
||||
if git diff --name-only HEAD~1 HEAD 2>/dev/null | grep -qE 'LicenseManager\.swift|ProFeature\.swift|PreferenceDefinition\.swift|ProTransitionManager\.swift|LicenseCookie\.swift|AppCenterCrashes\.swift'; then
|
||||
if [ "${conflicts:-false}" = "true" ]; then
|
||||
chokepoint_conflict=1
|
||||
fi
|
||||
fi
|
||||
echo "churn=${churn}" >> "$GITHUB_OUTPUT"
|
||||
echo "chokepoint_conflict=${chokepoint_conflict}" >> "$GITHUB_OUTPUT"
|
||||
|
||||
git push origin "HEAD:sync/${TAG}"
|
||||
|
||||
- name: Open PR
|
||||
if: steps.tag.outputs.skip != 'true'
|
||||
run: |
|
||||
set -euo pipefail
|
||||
TAG='${{ steps.tag.outputs.latest }}'
|
||||
CHURN='${{ steps.merge.outputs.churn }}'
|
||||
CONFLICTS='${{ steps.merge.outputs.conflicts }}'
|
||||
CHOKEPOINT_CONFLICT='${{ steps.merge.outputs.chokepoint_conflict }}'
|
||||
|
||||
# Label fail-closed (§4.2): conflict -> `sync,conflict`, else `sync`.
|
||||
if [ "$CONFLICTS" = "true" ]; then
|
||||
LABELS="sync,conflict"
|
||||
else
|
||||
LABELS="sync"
|
||||
fi
|
||||
|
||||
BODY="Automated upstream sync to \`${TAG}\`.
|
||||
|
||||
Selected as the OLDEST upstream tag strictly newer than \`.fork-sync-state\` (§4.2).
|
||||
Verify the guard.yml + ci_cd.yml PR checks are GREEN (anti-relock test + marker/conflict greps) before merge.
|
||||
If checks did not appear, SYNC_BOT_TOKEN is mis-set to the default GITHUB_TOKEN — the gate is disarmed (§4.2).
|
||||
|
||||
Chokepoint-churn count for this merge range: ${CHURN} (nonzero => de-paywall chokepoint touched, see §0.1 trip-wire A).
|
||||
|
||||
NEVER close this PR to defer it — \`.fork-sync-state\` only advances on MERGE, so leaving it OPEN keeps this tag 'next'; closing it would silently skip the tag (§4.2 / §6.2)."
|
||||
|
||||
gh pr create \
|
||||
--head "sync/${TAG}" \
|
||||
--base master \
|
||||
--title "sync: merge upstream ${TAG}" \
|
||||
--body "$BODY" \
|
||||
--label "$LABELS"
|
||||
|
||||
# §0.1 trip-wire A — auto-apply `chokepoint-refresh` from CI when a chokepoint file
|
||||
# changed or conflicted, so the trip-wire tally never depends on a human remembering.
|
||||
if [ "${CHURN:-0}" -gt 0 ] || [ "${CHOKEPOINT_CONFLICT:-0}" = "1" ]; then
|
||||
gh pr edit "sync/${TAG}" --add-label chokepoint-refresh || true
|
||||
fi
|
||||
+13
-1
@@ -1,13 +1,25 @@
|
||||
/.idea/
|
||||
xcuserdata/
|
||||
/.vscode/
|
||||
/buildServer.json
|
||||
WorkspaceSettings.xcsettings
|
||||
IDEWorkspaceChecks.plist
|
||||
/node_modules/
|
||||
/DerivedData/
|
||||
/build/
|
||||
/vendor/*/.build/
|
||||
/vendor/*/build/
|
||||
/vendor/*/.swiftpm/
|
||||
/vendor/*/Package.resolved
|
||||
.DS_Store
|
||||
docs/_site/
|
||||
codesign.conf
|
||||
codesign.crt
|
||||
codesign.key
|
||||
codesign.p12
|
||||
/.claude/*
|
||||
!/.claude/skills/
|
||||
/scripts/l10n/missing-translations.json
|
||||
/ai/output/
|
||||
# alt-tab-free [depaywall]: the Sparkle feed is published OUT OF TREE to the gh-pages branch by
|
||||
# scripts/update_appcast.sh; the in-tree appcast.xml is removed (PLAN §3.3).
|
||||
/appcast.xml
|
||||
@@ -0,0 +1,6 @@
|
||||
project: alt-tab-macos.xcodeproj
|
||||
schemes:
|
||||
- Release
|
||||
index_exclude:
|
||||
- vendor/**
|
||||
- src/api-wrappers/private-apis/**
|
||||
@@ -0,0 +1,10 @@
|
||||
# rules: https://github.com/nicklockwood/SwiftFormat/blob/main/Rules.md
|
||||
|
||||
--swiftversion 5.8
|
||||
--maxwidth 110
|
||||
--nowrapoperators .,==,!=,<,>,>=,<=
|
||||
--wrapternary before-operators
|
||||
--disable andOperator
|
||||
--disable wrapConditionalBodies
|
||||
--disable wrapLoopBodies
|
||||
--disable wrapMultilineStatementBraces
|
||||
@@ -0,0 +1,10 @@
|
||||
# **/*.swift: Matches all .swift files in the current directory and all its subdirectories.
|
||||
# src/**/*.swift: Matches all .swift files in the src directory and all its subdirectories.
|
||||
# !node_modules/**: Excludes all files in the node_modules directory and all its subdirectories.
|
||||
|
||||
Generated/**
|
||||
|
||||
**/PrivateApis.swift
|
||||
|
||||
# We will open a new branch to deal with code format issues at the next release.
|
||||
**/*
|
||||
-32
@@ -1,32 +0,0 @@
|
||||
branches:
|
||||
only:
|
||||
- master
|
||||
os: osx
|
||||
osx_image: xcode11.5
|
||||
language: node_js
|
||||
node_js:
|
||||
- 10
|
||||
addons:
|
||||
ssh_known_hosts: github.com
|
||||
homebrew:
|
||||
packages:
|
||||
- vitorgalvao/tiny-scripts/cask-repair
|
||||
update: true
|
||||
env:
|
||||
global:
|
||||
- IS_RELEASE=$(if [ "$TRAVIS_BRANCH" = "master" -a "$TRAVIS_PULL_REQUEST" = "false" ]; then echo true; fi)
|
||||
- XCODE_BUILD_PATH="DerivedData/Build/Products/Release"
|
||||
- VERSION_FILE="VERSION.txt"
|
||||
- APP_NAME="$(awk -F ' = ' '/PRODUCT_NAME/ { print $2; }' < config/base.xcconfig)"
|
||||
install: true # disable default install
|
||||
script: scripts/travis.sh
|
||||
deploy:
|
||||
provider: releases
|
||||
token:
|
||||
secure: wcAMT2rbXhhdk6lsc6EG9VZZkSW0BjOwjYSyXzHjKrgiXsQcZAzAsOkuisj8EfeYN9/uhpa8XX7f5KsdkE5reMqVGGWqdmJU4NnmK5Aew0LmBbzFHfffliQecVCzMmGYPmhPcDxAjidYxgCMrMbvQFaElc0WXdIsQNcaNieSkEgZw6TwGsTxgHXABD2e575K3SN/PPcq7Nx5mRZkURI7YLmo2KfsD2OPR4IcEXFpJRFbPcAZIpKaHUIC2VzpayJA93pBe+siwU150p04PzfSs/nTH/nUO9m24SUi6kAIzVN77cExvX+JDnQoVLz0V45L0iJjkKDTVb07rVOsfm6Pcuaaj2ZVXlpujSb7B5LDUIspUn/NnqMLOtzbSTuaGcx2u+zCcC0b17KooznilQ/sFq3YpeNdmjL4VL5fsw1hdpjcP6S9DqZ90TJZqy6xBQP5NubuJukH//BRilhWGfnq5rOFUlqrN8aApJ7gYry0K2rB8rVWWvA9cU+zX351iy0EERICEU6q3rQwq8gFUbdNATPJJgNg7n0PaNXSQTTF9VluZTRVUtMrpA0hWe7dn+Tvrvhrnhmqvccw3Qfw/i3SECIDHHtsjwLqHCx5gkz/xUdf7+t3k6x+r6Veua0Z3jvyVhdHprU47VTSUhM/n87EiKyNRuKa9IpA18eSlZ59exU=
|
||||
file_glob: true
|
||||
file: "$XCODE_BUILD_PATH/*.zip"
|
||||
skip_cleanup: true
|
||||
on:
|
||||
repo: lwouis/alt-tab-macos
|
||||
after_deploy: scripts/update_homebrew_cask.sh
|
||||
@@ -0,0 +1,14 @@
|
||||
# macOS development
|
||||
- Don't use xcode directly to develop
|
||||
- Use pure swift 5.8 code to make the app. No interface builder. No SwiftUI.
|
||||
- Aim for compact code. Within methods, don't have groups of statements separated with newlines. No inline comments for simple code. Instead, split statements into sub-methods.
|
||||
- Use guard closes as much as possible to separate the happy-path under them
|
||||
- Organize source files into folders. Folders should group files that change together, at the same pace (e.g. one feature)
|
||||
- Favor low latency and responsiveness. Reuse objects, avoid wasting memory or I/O.
|
||||
|
||||
# Workflow
|
||||
- Copy commands from ai/build.sh and run them, to confirm compilation works after you're done with implementing a change
|
||||
|
||||
# License / Keychain invariant
|
||||
- The app's Developer ID, TeamID, and bundle ID must remain stable across builds. Keychain items are tied to the code signature; changing any of these orphans every user's stored license key and forces mass re-activation. If a rotation is unavoidable, plan a migration first (e.g., a backup-restore handler, or `kSecAttrAccessGroup` with a stable group identifier).
|
||||
- Do not introduce legacy `SecKeychain*` API or `kSecAccessControl` (biometric/PIN gating) into license code — both can trigger Keychain password prompts, which is bad UX for license activation.
|
||||
+57
-29
@@ -2,55 +2,83 @@
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>ATSApplicationFontsPath</key>
|
||||
<string></string>
|
||||
<key>AppCenterApplicationForwarderEnabled</key>
|
||||
<string>0</string>
|
||||
<key>AppCenterSecret</key>
|
||||
<string>#APPCENTER_SECRET#</string>
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>en</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>$(EXECUTABLE_NAME)</string>
|
||||
<key>CFBundleIconFile</key>
|
||||
<string>app.icns</string>
|
||||
<!-- ===== Bundle identity (required by macOS / App Store) ===== -->
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>CFBundleName</key>
|
||||
<string>$(PRODUCT_NAME)</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>$(EXECUTABLE_NAME)</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>#VERSION#</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>#VERSION#</string>
|
||||
<key>FeedbackToken</key>
|
||||
<string>#FEEDBACK_TOKEN#</string>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>en</string>
|
||||
<key>CFBundleIconFile</key>
|
||||
<string>app.icns</string>
|
||||
<key>NSHumanReadableCopyright</key>
|
||||
<string>GPL-3.0 licence</string>
|
||||
<key>LSApplicationCategoryType</key>
|
||||
<string>public.app-category.utilities</string>
|
||||
|
||||
<!-- ===== Versioning (substituted from xcconfig; CI overrides via config/local.xcconfig) ===== -->
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>$(CURRENT_PROJECT_VERSION)</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>$(CURRENT_PROJECT_VERSION)</string>
|
||||
|
||||
<!-- ===== Launch behavior (read by LaunchServices before app starts) ===== -->
|
||||
<key>LSMinimumSystemVersion</key>
|
||||
<string>$(MACOSX_DEPLOYMENT_TARGET)</string>
|
||||
<key>LSUIElement</key>
|
||||
<string>true</string>
|
||||
<key>NSHumanReadableCopyright</key>
|
||||
<string>GPL-3.0 licence</string>
|
||||
<key>NSPrincipalClass</key>
|
||||
<string>AppCenterApplication</string>
|
||||
<key>NSSupportsAutomaticTermination</key>
|
||||
<string>false</string>
|
||||
<key>NSSupportsSuddenTermination</key>
|
||||
<string>false</string>
|
||||
<key>SUEnableAutomaticChecks</key>
|
||||
<string>true</string>
|
||||
<key>SUEnableJavaScript</key>
|
||||
<string>true</string>
|
||||
<key>SUFeedURL</key>
|
||||
<string>https://raw.githubusercontent.com/lwouis/alt-tab-macos/master/appcast.xml</string>
|
||||
<key>ATSApplicationFontsPath</key>
|
||||
<string></string>
|
||||
|
||||
<!-- ===== URL scheme registration (LaunchServices dispatches $(PRODUCT_BUNDLE_IDENTIFIER):// to us) ===== -->
|
||||
<key>CFBundleURLTypes</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>CFBundleTypeRole</key>
|
||||
<string>Viewer</string>
|
||||
<key>CFBundleURLName</key>
|
||||
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
|
||||
<key>CFBundleURLSchemes</key>
|
||||
<array>
|
||||
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
|
||||
</array>
|
||||
</dict>
|
||||
</array>
|
||||
|
||||
<!-- ===== Sparkle (auto-update framework — reads these directly from the bundle) ===== -->
|
||||
<key>SUPublicEDKey</key>
|
||||
<string>2e9SQOBoaKElchSa/4QDli/nvYkyuDNfynfzBF6vJK4=</string>
|
||||
<!-- Fork EdDSA public key for Sparkle update verification. The matching private key
|
||||
is held as CI secret SPARKLE_ED_PRIVATE_KEY (see docs/EXECUTION-STATUS.md §3.3).
|
||||
Auto-checks stay off (below) until DOMAIN serves a real appcast host. -->
|
||||
<string>1YfoCJDk/T8GQx1Gpf3O4ApFgDkBiN6oe/z/Kfnxs/M=</string>
|
||||
<key>SUEnableAutomaticChecks</key>
|
||||
<!-- alt-tab-free: enabled 2026-06-16 — DOMAIN serves the appcast on GitHub Pages (gh-pages). -->
|
||||
<string>true</string>
|
||||
<key>SUScheduledCheckInterval</key>
|
||||
<string>604800</string>
|
||||
|
||||
<!-- ===== AppCenter (crash reporter — read by AppCenterApplication superclass at init) ===== -->
|
||||
<key>AppCenterApplicationForwarderEnabled</key>
|
||||
<string>0</string>
|
||||
|
||||
<!-- ===== Build-time secrets (substituted from xcconfig; override per-dev via config/local.xcconfig) ===== -->
|
||||
<key>AppCenterSecret</key>
|
||||
<string>$(APPCENTER_SECRET)</string>
|
||||
<key>Domain</key>
|
||||
<string>$(DOMAIN)</string>
|
||||
<key>ApiDomain</key>
|
||||
<string>$(API_DOMAIN)</string>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -0,0 +1,34 @@
|
||||
# NOTICE — Statement of Changes (GPL-3.0 §5(a))
|
||||
|
||||
This file carries the prominent notice, required by GNU GPL v3.0 §5(a), that this work
|
||||
has been modified, and gives the relevant date. It is a **living document**: whenever a
|
||||
sync from upstream changes this fork's modification surface, update the change list and
|
||||
the date below (see `docs/PLAN-maintained-fork.md` §6.2).
|
||||
|
||||
**Forked from:** [lwouis/alt-tab-macos](https://github.com/lwouis/alt-tab-macos) v11.3.0,
|
||||
© Louis Pontoise, licensed GPL-3.0.
|
||||
|
||||
**Modified by:** the CommandTabFree fork. This fork neutralizes the AltTab Pro paywall so that
|
||||
all features are free.
|
||||
|
||||
**Date of modification:** 2026-06-10.
|
||||
|
||||
**License:** CommandTabFree is released under the **GNU General Public License v3.0**, the same
|
||||
license as upstream. The full license text is in [`LICENCE.md`](LICENCE.md), which is kept
|
||||
byte-for-byte identical to upstream and is never edited.
|
||||
|
||||
## Changes made in this fork
|
||||
|
||||
- **`LicenseManager.computeState`** (`src/pro/license/LicenseManager.swift`) — forced to
|
||||
return `.pro`, opening every Pro feature gate. This is the single live license-state
|
||||
producer, so this one change unlocks all paywalled features.
|
||||
- **`LicenseCookie.syncLicenseCookie`** (`src/pro/license/LicenseCookie.swift`) — made a
|
||||
no-op, so no license cookie is written to the upstream domain.
|
||||
- **`AppCenterCrash.init`** (`src/vendors/AppCenterCrashes.swift`) — telemetry disabled;
|
||||
AppCenter is never started.
|
||||
- **`App.handleCustomUrl`** (`src/App.swift`) — the `activate` deep-link handler is
|
||||
disabled, so a crafted `…://activate?license_key=…` URL can no longer POST the machine
|
||||
hardware fingerprint to a license backend.
|
||||
|
||||
The upstream copyright, contributor credits (`docs/contributors.md`), and third-party
|
||||
acknowledgments (`docs/acknowledgments.md`) are retained unchanged.
|
||||
@@ -6,6 +6,12 @@ verify_ssl = true
|
||||
[dev-packages]
|
||||
|
||||
[packages]
|
||||
fonttools = "*"
|
||||
fonttools = "==4.8.1"
|
||||
googletrans = "==3.0.0"
|
||||
|
||||
[scripts]
|
||||
[requires]
|
||||
python_version = "3.6"
|
||||
|
||||
[scripts]
|
||||
font2png = "scripts/convert_font_to_png.sh"
|
||||
translate = "python scripts/l10n/google_translate_all_terms_locally.py resources/l10n --retry_translate"
|
||||
Generated
+102
-2
@@ -1,10 +1,12 @@
|
||||
{
|
||||
"_meta": {
|
||||
"hash": {
|
||||
"sha256": "8f11c81dff5e32871b7cb2314acb76fd1fb4a06fdb403a65bc6749fe3770eda6"
|
||||
"sha256": "3646221352b85667ac21f1b528dc8b29f56ff1e24cb97e7eaba79b5040c78e21"
|
||||
},
|
||||
"pipfile-spec": 6,
|
||||
"requires": {},
|
||||
"requires": {
|
||||
"python_version": "3.6"
|
||||
},
|
||||
"sources": [
|
||||
{
|
||||
"name": "pypi",
|
||||
@@ -14,13 +16,111 @@
|
||||
]
|
||||
},
|
||||
"default": {
|
||||
"certifi": {
|
||||
"hashes": [
|
||||
"sha256:e564105f78ded564e3ae7c923924435e1daa7463faeab5bb932bc53ffae63407",
|
||||
"sha256:f6c12493cfb1b06ba2ff328595af9350c65d6644968e5d3a2ffd78699af217a5"
|
||||
],
|
||||
"markers": "python_version >= '3.7'",
|
||||
"version": "==2025.8.3"
|
||||
},
|
||||
"chardet": {
|
||||
"hashes": [
|
||||
"sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae",
|
||||
"sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691"
|
||||
],
|
||||
"version": "==3.0.4"
|
||||
},
|
||||
"fonttools": {
|
||||
"hashes": [
|
||||
"sha256:596c0e8399c650dbf1048b7ca6fc7b12bd79a7db0981f3b87743de8c2b7512de",
|
||||
"sha256:6d241d319ea9eae4c4ee27cbd1db8656dbd496dd9aa884401debfab3a4c7d82b"
|
||||
],
|
||||
"index": "pypi",
|
||||
"markers": "python_version >= '3.6'",
|
||||
"version": "==4.8.1"
|
||||
},
|
||||
"googletrans": {
|
||||
"hashes": [
|
||||
"sha256:44caeea42d91ff6ead5c2d49db2b88de66c45c2fe874c8ec03eb9b4ceb3a533d"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==3.0.0"
|
||||
},
|
||||
"h11": {
|
||||
"hashes": [
|
||||
"sha256:33d4bca7be0fa039f4e84d50ab00531047e53d6ee8ffbc83501ea602c169cae1",
|
||||
"sha256:4bc6d6a1238b7615b266ada57e0618568066f57dd6fa967d1290ec9309b2f2f1"
|
||||
],
|
||||
"version": "==0.9.0"
|
||||
},
|
||||
"h2": {
|
||||
"hashes": [
|
||||
"sha256:61e0f6601fa709f35cdb730863b4e5ec7ad449792add80d1410d4174ed139af5",
|
||||
"sha256:875f41ebd6f2c44781259005b157faed1a5031df3ae5aa7bcb4628a6c0782f14"
|
||||
],
|
||||
"version": "==3.2.0"
|
||||
},
|
||||
"hpack": {
|
||||
"hashes": [
|
||||
"sha256:0edd79eda27a53ba5be2dfabf3b15780928a0dff6eb0c60a3d6767720e970c89",
|
||||
"sha256:8eec9c1f4bfae3408a3f30500261f7e6a65912dc138526ea054f9ad98892e9d2"
|
||||
],
|
||||
"version": "==3.0.0"
|
||||
},
|
||||
"hstspreload": {
|
||||
"hashes": [
|
||||
"sha256:02bd5b0885b62f0d5239ca6c6876ffcad8d9c08dc97cee54292598141a16e9dc",
|
||||
"sha256:346552a807b3a1762376de8ecce097544e7fcd64fb64231b4652da52f86fa6f1"
|
||||
],
|
||||
"markers": "python_version >= '3.6'",
|
||||
"version": "==2025.1.1"
|
||||
},
|
||||
"httpcore": {
|
||||
"hashes": [
|
||||
"sha256:9850fe97a166a794d7e920590d5ec49a05488884c9fc8b5dba8561effab0c2a0",
|
||||
"sha256:ecc5949310d9dae4de64648a4ce529f86df1f232ce23dcfefe737c24d21dfbe9"
|
||||
],
|
||||
"markers": "python_version >= '3.6'",
|
||||
"version": "==0.9.1"
|
||||
},
|
||||
"httpx": {
|
||||
"hashes": [
|
||||
"sha256:32d930858eab677bc29a742aaa4f096de259f1c78c68a90ad11f5c3c04f08335",
|
||||
"sha256:3642bd13e90b80ba8a243a730275eb10a4c26ec96f5fc16b87e458d4ab21efae"
|
||||
],
|
||||
"markers": "python_version >= '3.6'",
|
||||
"version": "==0.13.3"
|
||||
},
|
||||
"hyperframe": {
|
||||
"hashes": [
|
||||
"sha256:5187962cb16dcc078f23cb5a4b110098d546c3f41ff2d4038a9896893bbd0b40",
|
||||
"sha256:a9f5c17f2cc3c719b917c4f33ed1c61bd1f8dfac4b1bd23b7c80b3400971b41f"
|
||||
],
|
||||
"version": "==5.2.0"
|
||||
},
|
||||
"idna": {
|
||||
"hashes": [
|
||||
"sha256:b307872f855b18632ce0c21c5e45be78c0ea7ae4c15c828c20788b26921eb3f6",
|
||||
"sha256:b97d804b1e9b523befed77c48dacec60e6dcb0b5391d57af6a65a312a90648c0"
|
||||
],
|
||||
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
|
||||
"version": "==2.10"
|
||||
},
|
||||
"rfc3986": {
|
||||
"hashes": [
|
||||
"sha256:270aaf10d87d0d4e095063c65bf3ddbc6ee3d0b226328ce21e036f946e421835",
|
||||
"sha256:a86d6e1f5b1dc238b218b012df0aa79409667bb209e58da56d0b94704e712a97"
|
||||
],
|
||||
"version": "==1.5.0"
|
||||
},
|
||||
"sniffio": {
|
||||
"hashes": [
|
||||
"sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2",
|
||||
"sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc"
|
||||
],
|
||||
"markers": "python_version >= '3.7'",
|
||||
"version": "==1.3.1"
|
||||
}
|
||||
},
|
||||
"develop": {}
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
platform :osx, '10.12'
|
||||
|
||||
target 'alt-tab-macos' do
|
||||
use_frameworks!
|
||||
pod 'LetsMove', :git => 'https://github.com/lwouis/LetsMove.git', :commit => '7abf4daed1a25218f2b52f2dfd190aee5a50071c'
|
||||
pod 'Sparkle', :podspec => 'https://raw.githubusercontent.com/lwouis/Sparkle/master/Sparkle.podspec'
|
||||
pod 'ShortcutRecorder', :git => 'https://github.com/Kentzo/ShortcutRecorder.git', :commit => 'f718220854575bad86fa89dd2bb3decd0b14b2b0'
|
||||
pod 'SwiftyMarkdown', '1.1.0'
|
||||
pod 'AppCenter/Crashes', '3.3.1'
|
||||
end
|
||||
@@ -1,49 +0,0 @@
|
||||
PODS:
|
||||
- AppCenter/Core (3.3.1)
|
||||
- AppCenter/Crashes (3.3.1):
|
||||
- AppCenter/Core
|
||||
- LetsMove (1.24)
|
||||
- ShortcutRecorder (3.1)
|
||||
- Sparkle (1.23.1)
|
||||
- SwiftyMarkdown (1.1.0)
|
||||
|
||||
DEPENDENCIES:
|
||||
- AppCenter/Crashes (= 3.3.1)
|
||||
- LetsMove (from `https://github.com/lwouis/LetsMove.git`, commit `7abf4daed1a25218f2b52f2dfd190aee5a50071c`)
|
||||
- ShortcutRecorder (from `https://github.com/Kentzo/ShortcutRecorder.git`, commit `f718220854575bad86fa89dd2bb3decd0b14b2b0`)
|
||||
- Sparkle (from `https://raw.githubusercontent.com/lwouis/Sparkle/master/Sparkle.podspec`)
|
||||
- SwiftyMarkdown (= 1.1.0)
|
||||
|
||||
SPEC REPOS:
|
||||
trunk:
|
||||
- AppCenter
|
||||
- SwiftyMarkdown
|
||||
|
||||
EXTERNAL SOURCES:
|
||||
LetsMove:
|
||||
:commit: 7abf4daed1a25218f2b52f2dfd190aee5a50071c
|
||||
:git: https://github.com/lwouis/LetsMove.git
|
||||
ShortcutRecorder:
|
||||
:commit: f718220854575bad86fa89dd2bb3decd0b14b2b0
|
||||
:git: https://github.com/Kentzo/ShortcutRecorder.git
|
||||
Sparkle:
|
||||
:podspec: https://raw.githubusercontent.com/lwouis/Sparkle/master/Sparkle.podspec
|
||||
|
||||
CHECKOUT OPTIONS:
|
||||
LetsMove:
|
||||
:commit: 7abf4daed1a25218f2b52f2dfd190aee5a50071c
|
||||
:git: https://github.com/lwouis/LetsMove.git
|
||||
ShortcutRecorder:
|
||||
:commit: f718220854575bad86fa89dd2bb3decd0b14b2b0
|
||||
:git: https://github.com/Kentzo/ShortcutRecorder.git
|
||||
|
||||
SPEC CHECKSUMS:
|
||||
AppCenter: 76ecf7e959a68e36b0fd314e8dc5897660214423
|
||||
LetsMove: fefe56bc7bc7fb7d37049e28a14f297961229fc5
|
||||
ShortcutRecorder: fdf620aca34101b0cba3b10fca815e0459254189
|
||||
Sparkle: ea073ba9c69cee3ebaab8685ba1e732e14d249f7
|
||||
SwiftyMarkdown: 033abc9dad9a37903f81aec5188cade436f40520
|
||||
|
||||
PODFILE CHECKSUM: 78df0a34910c4c031a65ac3b58dc004c21b552d0
|
||||
|
||||
COCOAPODS: 1.9.1
|
||||
-25
@@ -1,25 +0,0 @@
|
||||
Visual Studio App Center SDK for Apple platforms
|
||||
|
||||
Copyright (c) Microsoft Corporation
|
||||
|
||||
All rights reserved.
|
||||
|
||||
MIT License
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
@@ -1 +0,0 @@
|
||||
Versions/Current/AppCenter
|
||||
@@ -1 +0,0 @@
|
||||
Versions/Current/PrivateHeaders
|
||||
BIN
Binary file not shown.
Generated
-21
@@ -1,21 +0,0 @@
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
#import "MSAbstractLog.h"
|
||||
#import "MSAppCenter.h"
|
||||
#import "MSAppCenterErrors.h"
|
||||
#import "MSChannelGroupProtocol.h"
|
||||
#import "MSChannelProtocol.h"
|
||||
#import "MSConstants.h"
|
||||
#import "MSCustomProperties.h"
|
||||
#import "MSDevice.h"
|
||||
#import "MSEnable.h"
|
||||
#import "MSLog.h"
|
||||
#import "MSLogWithProperties.h"
|
||||
#import "MSLogger.h"
|
||||
#import "MSService.h"
|
||||
#import "MSServiceAbstract.h"
|
||||
#import "MSWrapperLogger.h"
|
||||
#import "MSWrapperSdk.h"
|
||||
Generated
-41
@@ -1,41 +0,0 @@
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
#ifndef MS_APP_CENTER_ERRORS_H
|
||||
#define MS_APP_CENTER_ERRORS_H
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
#define MS_APP_CENTER_BASE_DOMAIN @"com.Microsoft.AppCenter."
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
#pragma mark - Domain
|
||||
|
||||
static NSString *const kMSACErrorDomain = MS_APP_CENTER_BASE_DOMAIN @"ErrorDomain";
|
||||
|
||||
#pragma mark - General
|
||||
|
||||
// Error codes.
|
||||
NS_ENUM(NSInteger){MSACLogInvalidContainerErrorCode = 1, MSACCanceledErrorCode = 2, MSACDisabledErrorCode = 3};
|
||||
|
||||
// Error descriptions.
|
||||
static NSString const *kMSACLogInvalidContainerErrorDesc = @"Invalid log container.";
|
||||
static NSString const *kMSACCanceledErrorDesc = @"The operation was canceled.";
|
||||
static NSString const *kMSACDisabledErrorDesc = @"The service is disabled.";
|
||||
|
||||
#pragma mark - Connection
|
||||
|
||||
// Error codes.
|
||||
NS_ENUM(NSInteger){MSACConnectionPausedErrorCode = 100, MSACConnectionHttpErrorCode = 101};
|
||||
|
||||
// Error descriptions.
|
||||
static NSString const *kMSACConnectionHttpErrorDesc = @"An HTTP error occured.";
|
||||
static NSString const *kMSACConnectionPausedErrorDesc = @"Canceled, connection paused with log deletion.";
|
||||
|
||||
// Error user info keys.
|
||||
static NSString const *kMSACConnectionHttpCodeErrorKey = @"MSACConnectionHttpCode";
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
|
||||
#endif
|
||||
-83
@@ -1,83 +0,0 @@
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
#ifndef MS_CHANNEL_GROUP_PROTOCOL_H
|
||||
#define MS_CHANNEL_GROUP_PROTOCOL_H
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
#import "MSChannelProtocol.h"
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@class MSChannelUnitConfiguration;
|
||||
|
||||
@protocol MSIngestionProtocol;
|
||||
@protocol MSChannelUnitProtocol;
|
||||
|
||||
/**
|
||||
* `MSChannelGroupProtocol` represents a kind of channel that contains constituent MSChannelUnit objects. When an operation from the
|
||||
* `MSChannelProtocol` is performed on the group, that operation should be propagated to its constituent MSChannelUnit objects.
|
||||
*/
|
||||
@protocol MSChannelGroupProtocol <MSChannelProtocol>
|
||||
|
||||
/**
|
||||
* Initialize a channel unit with the given configuration.
|
||||
*
|
||||
* @param configuration channel configuration.
|
||||
*
|
||||
* @return The added `MSChannelUnitProtocol`. Use this object to enqueue logs.
|
||||
*/
|
||||
- (id<MSChannelUnitProtocol>)addChannelUnitWithConfiguration:(MSChannelUnitConfiguration *)configuration;
|
||||
|
||||
/**
|
||||
* Initialize a channel unit with the given configuration.
|
||||
*
|
||||
* @param configuration channel configuration.
|
||||
* @param ingestion The alternative ingestion object
|
||||
*
|
||||
* @return The added `MSChannelUnitProtocol`. Use this object to enqueue logs.
|
||||
*/
|
||||
- (id<MSChannelUnitProtocol>)addChannelUnitWithConfiguration:(MSChannelUnitConfiguration *)configuration
|
||||
withIngestion:(nullable id<MSIngestionProtocol>)ingestion;
|
||||
|
||||
/**
|
||||
* Change the base URL (schema + authority + port only) used to communicate with the backend.
|
||||
*
|
||||
* @param logUrl base URL to use for backend communication.
|
||||
*/
|
||||
- (void)setLogUrl:(NSString *)logUrl;
|
||||
|
||||
/**
|
||||
* Set the app secret.
|
||||
*
|
||||
* @param appSecret The app secret.
|
||||
*/
|
||||
- (void)setAppSecret:(NSString *)appSecret;
|
||||
|
||||
/**
|
||||
* Set the maximum size of the internal storage. This method must be called before App Center is started.
|
||||
*
|
||||
* @discussion The default maximum database size is 10485760 bytes (10 MiB).
|
||||
*
|
||||
* @param sizeInBytes Maximum size of the internal storage in bytes. This will be rounded up to the nearest multiple of a SQLite page size
|
||||
* (default is 4096 bytes). Values below 24576 bytes (24 KiB) will be ignored.
|
||||
* @param completionHandler Callback that is invoked when the database size has been set. The `BOOL` parameter is `YES` if changing the size
|
||||
* is successful, and `NO` otherwise.
|
||||
*/
|
||||
- (void)setMaxStorageSize:(long)sizeInBytes completionHandler:(nullable void (^)(BOOL))completionHandler;
|
||||
|
||||
/**
|
||||
* Return a channel unit instance for the given groupId.
|
||||
*
|
||||
* @param groupId The group ID for a channel unit.
|
||||
*
|
||||
* @return A channel unit instance or `nil`.
|
||||
*/
|
||||
- (nullable id<MSChannelUnitProtocol>)channelUnitForGroupId:(NSString *)groupId;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
|
||||
#endif
|
||||
Generated
-170
@@ -1,170 +0,0 @@
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
/**
|
||||
* Log Levels
|
||||
*/
|
||||
typedef NS_ENUM(NSUInteger, MSLogLevel) {
|
||||
|
||||
/**
|
||||
* Logging will be very chatty
|
||||
*/
|
||||
MSLogLevelVerbose = 2,
|
||||
|
||||
/**
|
||||
* Debug information will be logged
|
||||
*/
|
||||
MSLogLevelDebug = 3,
|
||||
|
||||
/**
|
||||
* Information will be logged
|
||||
*/
|
||||
MSLogLevelInfo = 4,
|
||||
|
||||
/**
|
||||
* Errors and warnings will be logged
|
||||
*/
|
||||
MSLogLevelWarning = 5,
|
||||
|
||||
/**
|
||||
* Errors will be logged
|
||||
*/
|
||||
MSLogLevelError = 6,
|
||||
|
||||
/**
|
||||
* Only critical errors will be logged
|
||||
*/
|
||||
MSLogLevelAssert = 7,
|
||||
|
||||
/**
|
||||
* Logging is disabled
|
||||
*/
|
||||
MSLogLevelNone = 99
|
||||
};
|
||||
|
||||
typedef NSString * (^MSLogMessageProvider)(void);
|
||||
typedef void (^MSLogHandler)(MSLogMessageProvider messageProvider, MSLogLevel logLevel, NSString *tag, const char *file,
|
||||
const char *function, uint line);
|
||||
|
||||
/**
|
||||
* Channel priorities, check the kMSPriorityCount if you add a new value.
|
||||
* The order matters here! Values NEED to range from low priority to high priority.
|
||||
*/
|
||||
typedef NS_ENUM(NSInteger, MSPriority) { MSPriorityBackground, MSPriorityDefault, MSPriorityHigh };
|
||||
static short const kMSPriorityCount = MSPriorityHigh + 1;
|
||||
|
||||
/**
|
||||
* The priority by which the modules are initialized.
|
||||
* MSPriorityMax is reserved for only 1 module and this needs to be Crashes.
|
||||
* Crashes needs to be initialized first to catch crashes in our other SDK Modules (which will hopefully never happen) and to avoid losing
|
||||
* any log at crash time.
|
||||
*/
|
||||
typedef NS_ENUM(NSInteger, MSInitializationPriority) {
|
||||
MSInitializationPriorityDefault = 500,
|
||||
MSInitializationPriorityHigh = 750,
|
||||
MSInitializationPriorityMax = 999
|
||||
};
|
||||
|
||||
/**
|
||||
* Enum with the different HTTP status codes.
|
||||
*/
|
||||
typedef NS_ENUM(NSInteger, MSHTTPCodesNo) {
|
||||
|
||||
// Invalid
|
||||
MSHTTPCodesNo0XXInvalidUnknown = 0,
|
||||
|
||||
// Informational
|
||||
MSHTTPCodesNo1XXInformationalUnknown = 1,
|
||||
MSHTTPCodesNo100Continue = 100,
|
||||
MSHTTPCodesNo101SwitchingProtocols = 101,
|
||||
MSHTTPCodesNo102Processing = 102,
|
||||
|
||||
// Success
|
||||
MSHTTPCodesNo2XXSuccessUnknown = 2,
|
||||
MSHTTPCodesNo200OK = 200,
|
||||
MSHTTPCodesNo201Created = 201,
|
||||
MSHTTPCodesNo202Accepted = 202,
|
||||
MSHTTPCodesNo203NonAuthoritativeInformation = 203,
|
||||
MSHTTPCodesNo204NoContent = 204,
|
||||
MSHTTPCodesNo205ResetContent = 205,
|
||||
MSHTTPCodesNo206PartialContent = 206,
|
||||
MSHTTPCodesNo207MultiStatus = 207,
|
||||
MSHTTPCodesNo208AlreadyReported = 208,
|
||||
MSHTTPCodesNo209IMUsed = 209,
|
||||
|
||||
// Redirection
|
||||
MSHTTPCodesNo3XXSuccessUnknown = 3,
|
||||
MSHTTPCodesNo300MultipleChoices = 300,
|
||||
MSHTTPCodesNo301MovedPermanently = 301,
|
||||
MSHTTPCodesNo302Found = 302,
|
||||
MSHTTPCodesNo303SeeOther = 303,
|
||||
MSHTTPCodesNo304NotModified = 304,
|
||||
MSHTTPCodesNo305UseProxy = 305,
|
||||
MSHTTPCodesNo306SwitchProxy = 306,
|
||||
MSHTTPCodesNo307TemporaryRedirect = 307,
|
||||
MSHTTPCodesNo308PermanentRedirect = 308,
|
||||
|
||||
// Client error
|
||||
MSHTTPCodesNo4XXSuccessUnknown = 4,
|
||||
MSHTTPCodesNo400BadRequest = 400,
|
||||
MSHTTPCodesNo401Unauthorised = 401,
|
||||
MSHTTPCodesNo402PaymentRequired = 402,
|
||||
MSHTTPCodesNo403Forbidden = 403,
|
||||
MSHTTPCodesNo404NotFound = 404,
|
||||
MSHTTPCodesNo405MethodNotAllowed = 405,
|
||||
MSHTTPCodesNo406NotAcceptable = 406,
|
||||
MSHTTPCodesNo407ProxyAuthenticationRequired = 407,
|
||||
MSHTTPCodesNo408RequestTimeout = 408,
|
||||
MSHTTPCodesNo409Conflict = 409,
|
||||
MSHTTPCodesNo410Gone = 410,
|
||||
MSHTTPCodesNo411LengthRequired = 411,
|
||||
MSHTTPCodesNo412PreconditionFailed = 412,
|
||||
MSHTTPCodesNo413RequestEntityTooLarge = 413,
|
||||
MSHTTPCodesNo414RequestURITooLong = 414,
|
||||
MSHTTPCodesNo415UnsupportedMediaType = 415,
|
||||
MSHTTPCodesNo416RequestedRangeNotSatisfiable = 416,
|
||||
MSHTTPCodesNo417ExpectationFailed = 417,
|
||||
MSHTTPCodesNo418IamATeapot = 418,
|
||||
MSHTTPCodesNo419AuthenticationTimeout = 419,
|
||||
MSHTTPCodesNo420MethodFailureSpringFramework = 420,
|
||||
MSHTTPCodesNo420EnhanceYourCalmTwitter = 4200,
|
||||
MSHTTPCodesNo422UnprocessableEntity = 422,
|
||||
MSHTTPCodesNo423Locked = 423,
|
||||
MSHTTPCodesNo424FailedDependency = 424,
|
||||
MSHTTPCodesNo424MethodFailureWebDaw = 4240,
|
||||
MSHTTPCodesNo425UnorderedCollection = 425,
|
||||
MSHTTPCodesNo426UpgradeRequired = 426,
|
||||
MSHTTPCodesNo428PreconditionRequired = 428,
|
||||
MSHTTPCodesNo429TooManyRequests = 429,
|
||||
MSHTTPCodesNo431RequestHeaderFieldsTooLarge = 431,
|
||||
MSHTTPCodesNo444NoResponseNginx = 444,
|
||||
MSHTTPCodesNo449RetryWithMicrosoft = 449,
|
||||
MSHTTPCodesNo450BlockedByWindowsParentalControls = 450,
|
||||
MSHTTPCodesNo451RedirectMicrosoft = 451,
|
||||
MSHTTPCodesNo451UnavailableForLegalReasons = 4510,
|
||||
MSHTTPCodesNo494RequestHeaderTooLargeNginx = 494,
|
||||
MSHTTPCodesNo495CertErrorNginx = 495,
|
||||
MSHTTPCodesNo496NoCertNginx = 496,
|
||||
MSHTTPCodesNo497HTTPToHTTPSNginx = 497,
|
||||
MSHTTPCodesNo499ClientClosedRequestNginx = 499,
|
||||
|
||||
// Server error
|
||||
MSHTTPCodesNo5XXSuccessUnknown = 5,
|
||||
MSHTTPCodesNo500InternalServerError = 500,
|
||||
MSHTTPCodesNo501NotImplemented = 501,
|
||||
MSHTTPCodesNo502BadGateway = 502,
|
||||
MSHTTPCodesNo503ServiceUnavailable = 503,
|
||||
MSHTTPCodesNo504GatewayTimeout = 504,
|
||||
MSHTTPCodesNo505HTTPVersionNotSupported = 505,
|
||||
MSHTTPCodesNo506VariantAlsoNegotiates = 506,
|
||||
MSHTTPCodesNo507InsufficientStorage = 507,
|
||||
MSHTTPCodesNo508LoopDetected = 508,
|
||||
MSHTTPCodesNo509BandwidthLimitExceeded = 509,
|
||||
MSHTTPCodesNo510NotExtended = 510,
|
||||
MSHTTPCodesNo511NetworkAuthenticationRequired = 511,
|
||||
MSHTTPCodesNo522ConnectionTimedOut = 522,
|
||||
MSHTTPCodesNo598NetworkReadTimeoutErrorUnknown = 598,
|
||||
MSHTTPCodesNo599NetworkConnectTimeoutErrorUnknown = 599
|
||||
};
|
||||
Generated
-44
@@ -1,44 +0,0 @@
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
#import "MSConstants.h"
|
||||
|
||||
#define MSLog(_level, _tag, _message) \
|
||||
[MSLogger logMessage:_message level:_level tag:_tag file:__FILE__ function:__PRETTY_FUNCTION__ line:__LINE__]
|
||||
#define MSLogAssert(tag, format, ...) \
|
||||
MSLog(MSLogLevelAssert, tag, (^{ \
|
||||
return [NSString stringWithFormat:(format), ##__VA_ARGS__]; \
|
||||
}))
|
||||
#define MSLogError(tag, format, ...) \
|
||||
MSLog(MSLogLevelError, tag, (^{ \
|
||||
return [NSString stringWithFormat:(format), ##__VA_ARGS__]; \
|
||||
}))
|
||||
#define MSLogWarning(tag, format, ...) \
|
||||
MSLog(MSLogLevelWarning, tag, (^{ \
|
||||
return [NSString stringWithFormat:(format), ##__VA_ARGS__]; \
|
||||
}))
|
||||
#define MSLogInfo(tag, format, ...) \
|
||||
MSLog(MSLogLevelInfo, tag, (^{ \
|
||||
return [NSString stringWithFormat:(format), ##__VA_ARGS__]; \
|
||||
}))
|
||||
#define MSLogDebug(tag, format, ...) \
|
||||
MSLog(MSLogLevelDebug, tag, (^{ \
|
||||
return [NSString stringWithFormat:(format), ##__VA_ARGS__]; \
|
||||
}))
|
||||
#define MSLogVerbose(tag, format, ...) \
|
||||
MSLog(MSLogLevelVerbose, tag, (^{ \
|
||||
return [NSString stringWithFormat:(format), ##__VA_ARGS__]; \
|
||||
}))
|
||||
|
||||
@interface MSLogger : NSObject
|
||||
|
||||
+ (void)logMessage:(MSLogMessageProvider)messageProvider
|
||||
level:(MSLogLevel)loglevel
|
||||
tag:(NSString *)tag
|
||||
file:(const char *)file
|
||||
function:(const char *)function
|
||||
line:(uint)line;
|
||||
|
||||
@end
|
||||
Generated
-35
@@ -1,35 +0,0 @@
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
#ifndef MS_SERVICE_H
|
||||
#define MS_SERVICE_H
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
/**
|
||||
* Protocol declaring service logic.
|
||||
*/
|
||||
@protocol MSService <NSObject>
|
||||
|
||||
/**
|
||||
* Enable or disable this service.
|
||||
* The state is persisted in the device's storage across application launches.
|
||||
*
|
||||
* @param isEnabled Whether this service is enabled or not.
|
||||
*
|
||||
* @see isEnabled
|
||||
*/
|
||||
+ (void)setEnabled:(BOOL)isEnabled;
|
||||
|
||||
/**
|
||||
* Indicates whether this service is enabled.
|
||||
*
|
||||
* @return `YES` if this service is enabled, `NO` if it is not.
|
||||
*
|
||||
* @see setEnabled:
|
||||
*/
|
||||
+ (BOOL)isEnabled;
|
||||
|
||||
@end
|
||||
|
||||
#endif
|
||||
Generated
-12
@@ -1,12 +0,0 @@
|
||||
framework module AppCenter {
|
||||
umbrella header "AppCenter.h"
|
||||
|
||||
export *
|
||||
module * { export * }
|
||||
|
||||
link framework "Foundation"
|
||||
link framework "SystemConfiguration"
|
||||
link framework "AppKit"
|
||||
link "sqlite3"
|
||||
link "z"
|
||||
}
|
||||
-18
@@ -1,18 +0,0 @@
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
#ifndef MS_CONSTANTS_FLAGS_H
|
||||
#define MS_CONSTANTS_FLAGS_H
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
typedef NS_OPTIONS(NSUInteger, MSFlags) {
|
||||
MSFlagsNone = (0 << 0), // => 00000000
|
||||
MSFlagsNormal = (1 << 0), // => 00000001
|
||||
MSFlagsCritical = (1 << 1), // => 00000010
|
||||
MSFlagsPersistenceNormal DEPRECATED_MSG_ATTRIBUTE("please use MSFlagsNormal") = MSFlagsNormal,
|
||||
MSFlagsPersistenceCritical DEPRECATED_MSG_ATTRIBUTE("please use MSFlagsCritical") = MSFlagsCritical,
|
||||
MSFlagsDefault = MSFlagsNormal
|
||||
};
|
||||
|
||||
#endif
|
||||
Generated
-44
@@ -1,44 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>BuildMachineOSBuild</key>
|
||||
<string>19F101</string>
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>English</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>AppCenter</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>com.microsoft.appcenter</string>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>CFBundleName</key>
|
||||
<string>AppCenter</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>FMWK</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>1</string>
|
||||
<key>CFBundleSupportedPlatforms</key>
|
||||
<array>
|
||||
<string>MacOSX</string>
|
||||
</array>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>1.0</string>
|
||||
<key>DTCompiler</key>
|
||||
<string>com.apple.compilers.llvm.clang.1_0</string>
|
||||
<key>DTPlatformBuild</key>
|
||||
<string>11C505</string>
|
||||
<key>DTPlatformVersion</key>
|
||||
<string>GM</string>
|
||||
<key>DTSDKBuild</key>
|
||||
<string>19B90</string>
|
||||
<key>DTSDKName</key>
|
||||
<string>macosx10.15</string>
|
||||
<key>DTXcode</key>
|
||||
<string>1130</string>
|
||||
<key>DTXcodeBuild</key>
|
||||
<string>11C505</string>
|
||||
<key>LSMinimumSystemVersion</key>
|
||||
<string>10.9</string>
|
||||
</dict>
|
||||
</plist>
|
||||
-1
@@ -1 +0,0 @@
|
||||
Versions/Current/AppCenterCrashes
|
||||
@@ -1 +0,0 @@
|
||||
Versions/Current/Headers
|
||||
@@ -1 +0,0 @@
|
||||
Versions/Current/Modules
|
||||
-1
@@ -1 +0,0 @@
|
||||
Versions/Current/Resources
|
||||
Generated
BIN
Binary file not shown.
-11
@@ -1,11 +0,0 @@
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
#import "MSCrashHandlerSetupDelegate.h"
|
||||
#import "MSCrashes.h"
|
||||
#import "MSCrashesDelegate.h"
|
||||
#import "MSErrorAttachmentLog+Utility.h"
|
||||
#import "MSErrorAttachmentLog.h"
|
||||
#import "MSWrapperCrashesHelper.h"
|
||||
Generated
-35
@@ -1,35 +0,0 @@
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
#ifndef MS_SERVICE_H
|
||||
#define MS_SERVICE_H
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
/**
|
||||
* Protocol declaring service logic.
|
||||
*/
|
||||
@protocol MSService <NSObject>
|
||||
|
||||
/**
|
||||
* Enable or disable this service.
|
||||
* The state is persisted in the device's storage across application launches.
|
||||
*
|
||||
* @param isEnabled Whether this service is enabled or not.
|
||||
*
|
||||
* @see isEnabled
|
||||
*/
|
||||
+ (void)setEnabled:(BOOL)isEnabled;
|
||||
|
||||
/**
|
||||
* Indicates whether this service is enabled.
|
||||
*
|
||||
* @return `YES` if this service is enabled, `NO` if it is not.
|
||||
*
|
||||
* @see setEnabled:
|
||||
*/
|
||||
+ (BOOL)isEnabled;
|
||||
|
||||
@end
|
||||
|
||||
#endif
|
||||
-55
@@ -1,55 +0,0 @@
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
#ifndef MS_SERVICE_ABSTRACT_H
|
||||
#define MS_SERVICE_ABSTRACT_H
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
#import "MSService.h"
|
||||
|
||||
@protocol MSChannelGroupProtocol;
|
||||
|
||||
/**
|
||||
* Abstraction of services common logic.
|
||||
* This class is intended to be subclassed only not instantiated directly.
|
||||
*/
|
||||
@interface MSServiceAbstract : NSObject <MSService>
|
||||
|
||||
/**
|
||||
* The flag indicates whether the service is started from application or not.
|
||||
*/
|
||||
@property(nonatomic, assign) BOOL startedFromApplication;
|
||||
|
||||
/**
|
||||
* Start this service with a channel group. Also sets the flag that indicates that a service has been started.
|
||||
*
|
||||
* @param channelGroup channel group used to persist and send logs.
|
||||
* @param appSecret app secret for the SDK.
|
||||
* @param token default transmission target token for this service.
|
||||
* @param fromApplication indicates whether the service started from an application or not.
|
||||
*/
|
||||
- (void)startWithChannelGroup:(id<MSChannelGroupProtocol>)channelGroup
|
||||
appSecret:(NSString *)appSecret
|
||||
transmissionTargetToken:(NSString *)token
|
||||
fromApplication:(BOOL)fromApplication;
|
||||
|
||||
/**
|
||||
* Update configuration when the service requires to start again. This method should only be called if the service is started from libraries
|
||||
* and then is being started from an application.
|
||||
*
|
||||
* @param appSecret app secret for the SDK.
|
||||
* @param token default transmission target token for this service.
|
||||
*/
|
||||
- (void)updateConfigurationWithAppSecret:(NSString *)appSecret transmissionTargetToken:(NSString *)token;
|
||||
|
||||
/**
|
||||
* Checks if the service needs the application secret.
|
||||
*
|
||||
* @return `YES` if the application secret is required, `NO` otherwise.
|
||||
*/
|
||||
- (BOOL)isAppSecretRequired;
|
||||
|
||||
@end
|
||||
|
||||
#endif
|
||||
-90
@@ -1,90 +0,0 @@
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
#import "MSCrashHandlerSetupDelegate.h"
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@class MSErrorReport;
|
||||
@class MSErrorAttachmentLog;
|
||||
@class MSException;
|
||||
|
||||
/**
|
||||
* This general class allows wrappers to supplement the Crashes SDK with their own behavior.
|
||||
*/
|
||||
@interface MSWrapperCrashesHelper : NSObject
|
||||
|
||||
/**
|
||||
* Sets the crash handler setup delegate.
|
||||
*
|
||||
* @param delegate The delegate to set.
|
||||
*/
|
||||
+ (void)setCrashHandlerSetupDelegate:(id<MSCrashHandlerSetupDelegate>)delegate;
|
||||
|
||||
/**
|
||||
* Gets the crash handler setup delegate.
|
||||
*
|
||||
* @return The delegate being used by Crashes.
|
||||
*/
|
||||
+ (id<MSCrashHandlerSetupDelegate>)getCrashHandlerSetupDelegate;
|
||||
|
||||
/**
|
||||
* Enables or disables automatic crash processing.
|
||||
*
|
||||
* @param automaticProcessing Passing NO causes SDK not to send reports immediately, even if "Always Send" is true.
|
||||
*/
|
||||
+ (void)setAutomaticProcessing:(BOOL)automaticProcessing;
|
||||
|
||||
/**
|
||||
* Gets a list of unprocessed crash reports. Will block until the service starts.
|
||||
*
|
||||
* @return An array of unprocessed error reports.
|
||||
*/
|
||||
+ (NSArray<MSErrorReport *> *)unprocessedCrashReports;
|
||||
|
||||
/**
|
||||
* Resumes processing for a given subset of the unprocessed reports.
|
||||
*
|
||||
* @param filteredIds An array containing the errorId/incidentIdentifier of each report that should be sent.
|
||||
*
|
||||
* @return YES if should "Always Send" is true.
|
||||
*/
|
||||
+ (BOOL)sendCrashReportsOrAwaitUserConfirmationForFilteredIds:(NSArray<NSString *> *)filteredIds;
|
||||
|
||||
/**
|
||||
* Sends error attachments for a particular error report.
|
||||
*
|
||||
* @param errorAttachments An array of error attachments that should be sent.
|
||||
* @param incidentIdentifier The identifier of the error report that the attachments will be associated with.
|
||||
*/
|
||||
+ (void)sendErrorAttachments:(NSArray<MSErrorAttachmentLog *> *)errorAttachments withIncidentIdentifier:(NSString *)incidentIdentifier;
|
||||
|
||||
/**
|
||||
* Track handled exception directly as model form.
|
||||
* This API is used by wrapper SDKs.
|
||||
*
|
||||
* @param exception model form exception.
|
||||
* @param properties dictionary of properties.
|
||||
* @param attachments a list of attachments.
|
||||
*
|
||||
* @return handled error ID.
|
||||
*/
|
||||
+ (nullable NSString *)trackModelException:(MSException *)exception
|
||||
withProperties:(nullable NSDictionary<NSString *, NSString *> *)properties
|
||||
withAttachments:(nullable NSArray<MSErrorAttachmentLog *> *)attachments;
|
||||
|
||||
/**
|
||||
* Get a generic error report representation for an handled exception.
|
||||
* This API is used by wrapper SDKs.
|
||||
*
|
||||
* @param errorID handled error ID.
|
||||
*
|
||||
* @return an error report.
|
||||
*/
|
||||
+ (MSErrorReport *)buildHandledErrorReportWithErrorID:(NSString *)errorID;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
-10
@@ -1,10 +0,0 @@
|
||||
framework module AppCenterCrashes {
|
||||
umbrella header "AppCenterCrashes.h"
|
||||
|
||||
export *
|
||||
module * { export * }
|
||||
|
||||
link framework "Foundation"
|
||||
link "c++"
|
||||
link "z"
|
||||
}
|
||||
Generated
-44
@@ -1,44 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>BuildMachineOSBuild</key>
|
||||
<string>19F101</string>
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>English</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>AppCenterCrashes</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>com.microsoft.appcenter.crashes</string>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>CFBundleName</key>
|
||||
<string>AppCenterCrashes</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>FMWK</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>1</string>
|
||||
<key>CFBundleSupportedPlatforms</key>
|
||||
<array>
|
||||
<string>MacOSX</string>
|
||||
</array>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>1.0</string>
|
||||
<key>DTCompiler</key>
|
||||
<string>com.apple.compilers.llvm.clang.1_0</string>
|
||||
<key>DTPlatformBuild</key>
|
||||
<string>11C505</string>
|
||||
<key>DTPlatformVersion</key>
|
||||
<string>GM</string>
|
||||
<key>DTSDKBuild</key>
|
||||
<string>19B90</string>
|
||||
<key>DTSDKName</key>
|
||||
<string>macosx10.15</string>
|
||||
<key>DTXcode</key>
|
||||
<string>1130</string>
|
||||
<key>DTXcodeBuild</key>
|
||||
<string>11C505</string>
|
||||
<key>LSMinimumSystemVersion</key>
|
||||
<string>10.9</string>
|
||||
</dict>
|
||||
</plist>
|
||||
-1
@@ -1 +0,0 @@
|
||||
A
|
||||
Generated
-10
@@ -1,10 +0,0 @@
|
||||
//
|
||||
// LetsMove.h
|
||||
// LetsMove
|
||||
//
|
||||
// Created by Matt Prowse on 14/05/2016.
|
||||
//
|
||||
// The contents of this file are dedicated to the public domain.
|
||||
|
||||
#import <Cocoa/Cocoa.h>
|
||||
#import <LetsMove/PFMoveApplication.h>
|
||||
Generated
-32
@@ -1,32 +0,0 @@
|
||||
//
|
||||
// PFMoveApplication.h, version 1.24
|
||||
// LetsMove
|
||||
//
|
||||
// Created by Andy Kim at Potion Factory LLC on 9/17/09
|
||||
//
|
||||
// The contents of this file are dedicated to the public domain.
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
/**
|
||||
Moves the running application to ~/Applications or /Applications if the former does not exist.
|
||||
After the move, it relaunches app from the new location.
|
||||
DOES NOT work for sandboxed applications.
|
||||
|
||||
Call from \c NSApplication's delegate method \c -applicationWillFinishLaunching: method. */
|
||||
void PFMoveToApplicationsFolderIfNecessary(void);
|
||||
|
||||
/**
|
||||
Check whether an app move is currently in progress.
|
||||
Returns YES if LetsMove is currently in-progress trying to move the app to the Applications folder, or NO otherwise.
|
||||
This can be used to work around a crash with apps that terminate after last window is closed.
|
||||
See https://github.com/potionfactory/LetsMove/issues/64 for details. */
|
||||
BOOL PFMoveIsInProgress(void);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
Generated
-563
@@ -1,563 +0,0 @@
|
||||
//
|
||||
// PFMoveApplication.m, version 1.24
|
||||
// LetsMove
|
||||
//
|
||||
// Created by Andy Kim at Potion Factory LLC on 9/17/09
|
||||
//
|
||||
// The contents of this file are dedicated to the public domain.
|
||||
|
||||
#import "PFMoveApplication.h"
|
||||
|
||||
#import <AppKit/AppKit.h>
|
||||
#import <Security/Security.h>
|
||||
#import <dlfcn.h>
|
||||
#import <sys/mount.h>
|
||||
|
||||
@interface LetsMove : NSObject
|
||||
@end
|
||||
|
||||
@implementation LetsMove
|
||||
+ (NSBundle *)bundle {
|
||||
return [NSBundle bundleForClass:self];
|
||||
}
|
||||
@end
|
||||
|
||||
// Strings
|
||||
// These are macros to be able to use custom i18n tools
|
||||
#define _I10NS(nsstr) NSLocalizedStringFromTableInBundle(nsstr, @"MoveApplication", [LetsMove bundle], nil)
|
||||
#define kStrMoveApplicationCouldNotMove _I10NS(@"Could not move to Applications folder")
|
||||
#define kStrMoveApplicationQuestionTitle _I10NS(@"Move to Applications folder?")
|
||||
#define kStrMoveApplicationQuestionTitleHome _I10NS(@"Move to Applications folder in your Home folder?")
|
||||
#define kStrMoveApplicationQuestionMessage _I10NS(@"I can move myself to the Applications folder if you'd like.")
|
||||
#define kStrMoveApplicationButtonMove _I10NS(@"Move to Applications Folder")
|
||||
#define kStrMoveApplicationButtonDoNotMove _I10NS(@"Do Not Move")
|
||||
#define kStrMoveApplicationQuestionInfoWillRequirePasswd _I10NS(@"Note that this will require an administrator password.")
|
||||
#define kStrMoveApplicationQuestionInfoInDownloadsFolder _I10NS(@"This will keep your Downloads folder uncluttered.")
|
||||
|
||||
// Needs to be defined for compiling under 10.5 SDK
|
||||
#ifndef NSAppKitVersionNumber10_5
|
||||
#define NSAppKitVersionNumber10_5 949
|
||||
#endif
|
||||
|
||||
// By default, we use a small control/font for the suppression button.
|
||||
// If you prefer to use the system default (to match your other alerts),
|
||||
// set this to 0.
|
||||
#define PFUseSmallAlertSuppressCheckbox 1
|
||||
|
||||
|
||||
static NSString *AlertSuppressKey = @"moveToApplicationsFolderAlertSuppress";
|
||||
static BOOL MoveInProgress = NO;
|
||||
|
||||
// Helper functions
|
||||
static NSString *PreferredInstallLocation(BOOL *isUserDirectory);
|
||||
static BOOL IsInApplicationsFolder(NSString *path);
|
||||
static BOOL IsInDownloadsFolder(NSString *path);
|
||||
static BOOL IsApplicationAtPathRunning(NSString *path);
|
||||
static BOOL IsApplicationAtPathNested(NSString *path);
|
||||
static NSString *ContainingDiskImageDevice(NSString *path);
|
||||
static BOOL Trash(NSString *path);
|
||||
static BOOL DeleteOrTrash(NSString *path);
|
||||
static BOOL AuthorizedInstall(NSString *srcPath, NSString *dstPath, BOOL *canceled);
|
||||
static BOOL CopyBundle(NSString *srcPath, NSString *dstPath);
|
||||
static NSString *ShellQuotedString(NSString *string);
|
||||
static void Relaunch(NSString *destinationPath);
|
||||
|
||||
// Main worker function
|
||||
void PFMoveToApplicationsFolderIfNecessary(void) {
|
||||
|
||||
// Make sure to do our work on the main thread.
|
||||
// Apparently Electron apps need this for things to work properly.
|
||||
if (![NSThread isMainThread]) {
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
PFMoveToApplicationsFolderIfNecessary();
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// Skip if user suppressed the alert before
|
||||
if ([[NSUserDefaults standardUserDefaults] boolForKey:AlertSuppressKey]) return;
|
||||
|
||||
// Path of the bundle
|
||||
NSString *bundlePath = [[NSBundle mainBundle] bundlePath];
|
||||
|
||||
// Check if the bundle is embedded in another application
|
||||
BOOL isNestedApplication = IsApplicationAtPathNested(bundlePath);
|
||||
|
||||
// Skip if the application is already in some Applications folder,
|
||||
// unless it's inside another app's bundle.
|
||||
if (IsInApplicationsFolder(bundlePath) && !isNestedApplication) return;
|
||||
|
||||
// OK, looks like we'll need to do a move - set the status variable appropriately
|
||||
MoveInProgress = YES;
|
||||
|
||||
// File Manager
|
||||
NSFileManager *fm = [NSFileManager defaultManager];
|
||||
|
||||
// Are we on a disk image?
|
||||
NSString *diskImageDevice = ContainingDiskImageDevice(bundlePath);
|
||||
|
||||
// Since we are good to go, get the preferred installation directory.
|
||||
BOOL installToUserApplications = NO;
|
||||
NSString *applicationsDirectory = PreferredInstallLocation(&installToUserApplications);
|
||||
NSString *bundleName = [bundlePath lastPathComponent];
|
||||
NSString *destinationPath = [applicationsDirectory stringByAppendingPathComponent:bundleName];
|
||||
|
||||
// Check if we need admin password to write to the Applications directory
|
||||
BOOL needAuthorization = ([fm isWritableFileAtPath:applicationsDirectory] == NO);
|
||||
|
||||
// Check if the destination bundle is already there but not writable
|
||||
needAuthorization |= ([fm fileExistsAtPath:destinationPath] && ![fm isWritableFileAtPath:destinationPath]);
|
||||
|
||||
// Setup the alert
|
||||
NSAlert *alert = [[[NSAlert alloc] init] autorelease];
|
||||
{
|
||||
NSString *informativeText = nil;
|
||||
|
||||
[alert setMessageText:(installToUserApplications ? kStrMoveApplicationQuestionTitleHome : kStrMoveApplicationQuestionTitle)];
|
||||
|
||||
informativeText = kStrMoveApplicationQuestionMessage;
|
||||
|
||||
if (needAuthorization) {
|
||||
informativeText = [informativeText stringByAppendingString:@" "];
|
||||
informativeText = [informativeText stringByAppendingString:kStrMoveApplicationQuestionInfoWillRequirePasswd];
|
||||
}
|
||||
else if (IsInDownloadsFolder(bundlePath)) {
|
||||
// Don't mention this stuff if we need authentication. The informative text is long enough as it is in that case.
|
||||
informativeText = [informativeText stringByAppendingString:@" "];
|
||||
informativeText = [informativeText stringByAppendingString:kStrMoveApplicationQuestionInfoInDownloadsFolder];
|
||||
}
|
||||
|
||||
[alert setInformativeText:informativeText];
|
||||
|
||||
// Add accept button
|
||||
[alert addButtonWithTitle:kStrMoveApplicationButtonMove];
|
||||
|
||||
// Add deny button
|
||||
NSButton *cancelButton = [alert addButtonWithTitle:kStrMoveApplicationButtonDoNotMove];
|
||||
[cancelButton setKeyEquivalent:[NSString stringWithFormat:@"%C", 0x1b]]; // Escape key
|
||||
|
||||
// Setup suppression button
|
||||
[alert setShowsSuppressionButton:YES];
|
||||
|
||||
if (PFUseSmallAlertSuppressCheckbox) {
|
||||
NSCell *cell = [[alert suppressionButton] cell];
|
||||
[cell setControlSize:NSSmallControlSize];
|
||||
[cell setFont:[NSFont systemFontOfSize:[NSFont smallSystemFontSize]]];
|
||||
}
|
||||
}
|
||||
|
||||
// Activate app -- work-around for focus issues related to "scary file from internet" OS dialog.
|
||||
if (![NSApp isActive]) {
|
||||
[NSApp activateIgnoringOtherApps:YES];
|
||||
}
|
||||
|
||||
if ([alert runModal] == NSAlertFirstButtonReturn) {
|
||||
NSLog(@"INFO -- Moving myself to the Applications folder");
|
||||
|
||||
// Move
|
||||
if (needAuthorization) {
|
||||
BOOL authorizationCanceled;
|
||||
|
||||
if (!AuthorizedInstall(bundlePath, destinationPath, &authorizationCanceled)) {
|
||||
if (authorizationCanceled) {
|
||||
NSLog(@"INFO -- Not moving because user canceled authorization");
|
||||
MoveInProgress = NO;
|
||||
return;
|
||||
}
|
||||
else {
|
||||
NSLog(@"ERROR -- Could not copy myself to /Applications with authorization");
|
||||
goto fail;
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
// If a copy already exists in the Applications folder, put it in the Trash
|
||||
if ([fm fileExistsAtPath:destinationPath]) {
|
||||
// But first, make sure that it's not running
|
||||
if (IsApplicationAtPathRunning(destinationPath)) {
|
||||
// Give the running app focus and terminate myself
|
||||
NSLog(@"INFO -- Switching to an already running version");
|
||||
[[NSTask launchedTaskWithLaunchPath:@"/usr/bin/open" arguments:[NSArray arrayWithObject:destinationPath]] waitUntilExit];
|
||||
MoveInProgress = NO;
|
||||
exit(0);
|
||||
}
|
||||
else {
|
||||
if (!Trash([applicationsDirectory stringByAppendingPathComponent:bundleName]))
|
||||
goto fail;
|
||||
}
|
||||
}
|
||||
|
||||
if (!CopyBundle(bundlePath, destinationPath)) {
|
||||
NSLog(@"ERROR -- Could not copy myself to %@", destinationPath);
|
||||
goto fail;
|
||||
}
|
||||
}
|
||||
|
||||
// Trash the original app. It's okay if this fails.
|
||||
// NOTE: This final delete does not work if the source bundle is in a network mounted volume.
|
||||
// Calling rm or file manager's delete method doesn't work either. It's unlikely to happen
|
||||
// but it'd be great if someone could fix this.
|
||||
if (!isNestedApplication && diskImageDevice == nil && !DeleteOrTrash(bundlePath)) {
|
||||
NSLog(@"WARNING -- Could not delete application after moving it to Applications folder");
|
||||
}
|
||||
|
||||
// Relaunch.
|
||||
Relaunch(destinationPath);
|
||||
|
||||
// Launched from within a disk image? -- unmount (if no files are open after 5 seconds,
|
||||
// otherwise leave it mounted).
|
||||
if (diskImageDevice && !isNestedApplication) {
|
||||
NSString *script = [NSString stringWithFormat:@"(/bin/sleep 5 && /usr/bin/hdiutil detach %@) &", ShellQuotedString(diskImageDevice)];
|
||||
[NSTask launchedTaskWithLaunchPath:@"/bin/sh" arguments:[NSArray arrayWithObjects:@"-c", script, nil]];
|
||||
}
|
||||
|
||||
MoveInProgress = NO;
|
||||
exit(0);
|
||||
}
|
||||
// Save the alert suppress preference if checked
|
||||
else if ([[alert suppressionButton] state] == NSOnState) {
|
||||
[[NSUserDefaults standardUserDefaults] setBool:YES forKey:AlertSuppressKey];
|
||||
}
|
||||
|
||||
MoveInProgress = NO;
|
||||
return;
|
||||
|
||||
fail:
|
||||
{
|
||||
// Show failure message
|
||||
alert = [[[NSAlert alloc] init] autorelease];
|
||||
[alert setMessageText:kStrMoveApplicationCouldNotMove];
|
||||
[alert runModal];
|
||||
MoveInProgress = NO;
|
||||
}
|
||||
}
|
||||
|
||||
BOOL PFMoveIsInProgress() {
|
||||
return MoveInProgress;
|
||||
}
|
||||
|
||||
#pragma mark -
|
||||
#pragma mark Helper Functions
|
||||
|
||||
static NSString *PreferredInstallLocation(BOOL *isUserDirectory) {
|
||||
// Return the preferred install location.
|
||||
// Assume that if the user has a ~/Applications folder, they'd prefer their
|
||||
// applications to go there.
|
||||
|
||||
// NSFileManager *fm = [NSFileManager defaultManager];
|
||||
//
|
||||
// NSArray *userApplicationsDirs = NSSearchPathForDirectoriesInDomains(NSApplicationDirectory, NSUserDomainMask, YES);
|
||||
//
|
||||
// if ([userApplicationsDirs count] > 0) {
|
||||
// NSString *userApplicationsDir = [userApplicationsDirs objectAtIndex:0];
|
||||
// BOOL isDirectory;
|
||||
//
|
||||
// if ([fm fileExistsAtPath:userApplicationsDir isDirectory:&isDirectory] && isDirectory) {
|
||||
// // User Applications directory exists. Get the directory contents.
|
||||
// NSArray *contents = [fm contentsOfDirectoryAtPath:userApplicationsDir error:NULL];
|
||||
//
|
||||
// // Check if there is at least one ".app" inside the directory.
|
||||
// for (NSString *contentsPath in contents) {
|
||||
// if ([[contentsPath pathExtension] isEqualToString:@"app"]) {
|
||||
// if (isUserDirectory) *isUserDirectory = YES;
|
||||
// return [userApplicationsDir stringByResolvingSymlinksInPath];
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
// No user Applications directory in use. Return the machine local Applications directory
|
||||
if (isUserDirectory) *isUserDirectory = NO;
|
||||
|
||||
return [[NSSearchPathForDirectoriesInDomains(NSApplicationDirectory, NSLocalDomainMask, YES) lastObject] stringByResolvingSymlinksInPath];
|
||||
}
|
||||
|
||||
static BOOL IsInApplicationsFolder(NSString *path) {
|
||||
// Check all the normal Application directories
|
||||
NSArray *applicationDirs = NSSearchPathForDirectoriesInDomains(NSApplicationDirectory, NSAllDomainsMask, YES);
|
||||
for (NSString *appDir in applicationDirs) {
|
||||
if ([path hasPrefix:appDir]) return YES;
|
||||
}
|
||||
|
||||
// Also, handle the case that the user has some other Application directory (perhaps on a separate data partition).
|
||||
if ([[path pathComponents] containsObject:@"Applications"]) return YES;
|
||||
|
||||
return NO;
|
||||
}
|
||||
|
||||
static BOOL IsInDownloadsFolder(NSString *path) {
|
||||
NSArray *downloadDirs = NSSearchPathForDirectoriesInDomains(NSDownloadsDirectory, NSAllDomainsMask, YES);
|
||||
for (NSString *downloadsDirPath in downloadDirs) {
|
||||
if ([path hasPrefix:downloadsDirPath]) return YES;
|
||||
}
|
||||
|
||||
return NO;
|
||||
}
|
||||
|
||||
static BOOL IsApplicationAtPathRunning(NSString *bundlePath) {
|
||||
bundlePath = [bundlePath stringByStandardizingPath];
|
||||
|
||||
#if MAC_OS_X_VERSION_MAX_ALLOWED > MAC_OS_X_VERSION_10_5
|
||||
// Use the new API on 10.6 or higher to determine if the app is already running
|
||||
if (floor(NSAppKitVersionNumber) > NSAppKitVersionNumber10_5) {
|
||||
for (NSRunningApplication *runningApplication in [[NSWorkspace sharedWorkspace] runningApplications]) {
|
||||
NSString *runningAppBundlePath = [[[runningApplication bundleURL] path] stringByStandardizingPath];
|
||||
if ([runningAppBundlePath isEqualToString:bundlePath]) {
|
||||
return YES;
|
||||
}
|
||||
}
|
||||
return NO;
|
||||
}
|
||||
#endif
|
||||
// Use the shell to determine if the app is already running on systems 10.5 or lower
|
||||
NSString *script = [NSString stringWithFormat:@"/bin/ps ax -o comm | /usr/bin/grep %@/ | /usr/bin/grep -v grep >/dev/null", ShellQuotedString(bundlePath)];
|
||||
NSTask *task = [NSTask launchedTaskWithLaunchPath:@"/bin/sh" arguments:[NSArray arrayWithObjects:@"-c", script, nil]];
|
||||
[task waitUntilExit];
|
||||
|
||||
// If the task terminated with status 0, it means that the final grep produced 1 or more lines of output.
|
||||
// Which means that the app is already running
|
||||
return [task terminationStatus] == 0;
|
||||
}
|
||||
|
||||
static BOOL IsApplicationAtPathNested(NSString *path) {
|
||||
NSString *containingPath = [path stringByDeletingLastPathComponent];
|
||||
|
||||
NSArray *components = [containingPath pathComponents];
|
||||
for (NSString *component in components) {
|
||||
if ([[component pathExtension] isEqualToString:@"app"]) {
|
||||
return YES;
|
||||
}
|
||||
}
|
||||
|
||||
return NO;
|
||||
}
|
||||
|
||||
static NSString *ContainingDiskImageDevice(NSString *path) {
|
||||
NSString *containingPath = [path stringByDeletingLastPathComponent];
|
||||
|
||||
struct statfs fs;
|
||||
if (statfs([containingPath fileSystemRepresentation], &fs) || (fs.f_flags & MNT_ROOTFS))
|
||||
return nil;
|
||||
|
||||
NSString *device = [[NSFileManager defaultManager] stringWithFileSystemRepresentation:fs.f_mntfromname length:strlen(fs.f_mntfromname)];
|
||||
|
||||
NSTask *hdiutil = [[[NSTask alloc] init] autorelease];
|
||||
[hdiutil setLaunchPath:@"/usr/bin/hdiutil"];
|
||||
[hdiutil setArguments:[NSArray arrayWithObjects:@"info", @"-plist", nil]];
|
||||
[hdiutil setStandardOutput:[NSPipe pipe]];
|
||||
[hdiutil launch];
|
||||
[hdiutil waitUntilExit];
|
||||
|
||||
NSData *data = [[[hdiutil standardOutput] fileHandleForReading] readDataToEndOfFile];
|
||||
NSDictionary *info = nil;
|
||||
#if MAC_OS_X_VERSION_MAX_ALLOWED > MAC_OS_X_VERSION_10_5
|
||||
if (floor(NSAppKitVersionNumber) > NSAppKitVersionNumber10_5) {
|
||||
info = [NSPropertyListSerialization propertyListWithData:data options:NSPropertyListImmutable format:NULL error:NULL];
|
||||
}
|
||||
else {
|
||||
#endif
|
||||
#if MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_10
|
||||
info = [NSPropertyListSerialization propertyListFromData:data mutabilityOption:NSPropertyListImmutable format:NULL errorDescription:NULL];
|
||||
#endif
|
||||
#if MAC_OS_X_VERSION_MAX_ALLOWED > MAC_OS_X_VERSION_10_5
|
||||
}
|
||||
#endif
|
||||
|
||||
if (![info isKindOfClass:[NSDictionary class]]) return nil;
|
||||
|
||||
NSArray *images = (NSArray *)[info objectForKey:@"images"];
|
||||
if (![images isKindOfClass:[NSArray class]]) return nil;
|
||||
|
||||
for (NSDictionary *image in images) {
|
||||
if (![image isKindOfClass:[NSDictionary class]]) return nil;
|
||||
|
||||
id systemEntities = [image objectForKey:@"system-entities"];
|
||||
if (![systemEntities isKindOfClass:[NSArray class]]) return nil;
|
||||
|
||||
for (NSDictionary *systemEntity in systemEntities) {
|
||||
if (![systemEntity isKindOfClass:[NSDictionary class]]) return nil;
|
||||
|
||||
NSString *devEntry = [systemEntity objectForKey:@"dev-entry"];
|
||||
if (![devEntry isKindOfClass:[NSString class]]) return nil;
|
||||
|
||||
if ([devEntry isEqualToString:device])
|
||||
return device;
|
||||
}
|
||||
}
|
||||
|
||||
return nil;
|
||||
}
|
||||
|
||||
static BOOL Trash(NSString *path) {
|
||||
BOOL result = NO;
|
||||
#if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_8
|
||||
if (floor(NSAppKitVersionNumber) >= NSAppKitVersionNumber10_8) {
|
||||
result = [[NSFileManager defaultManager] trashItemAtURL:[NSURL fileURLWithPath:path] resultingItemURL:NULL error:NULL];
|
||||
}
|
||||
#endif
|
||||
#if MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_11
|
||||
if (!result) {
|
||||
result = [[NSWorkspace sharedWorkspace] performFileOperation:NSWorkspaceRecycleOperation
|
||||
source:[path stringByDeletingLastPathComponent]
|
||||
destination:@""
|
||||
files:[NSArray arrayWithObject:[path lastPathComponent]]
|
||||
tag:NULL];
|
||||
}
|
||||
#endif
|
||||
|
||||
// As a last resort try trashing with AppleScript.
|
||||
// This allows us to trash the app in macOS Sierra even when the app is running inside
|
||||
// an app translocation image.
|
||||
if (!result) {
|
||||
NSAppleScript *appleScript = [[[NSAppleScript alloc] initWithSource:
|
||||
[NSString stringWithFormat:@"\
|
||||
set theFile to POSIX file \"%@\" \n\
|
||||
tell application \"Finder\" \n\
|
||||
move theFile to trash \n\
|
||||
end tell", path]] autorelease];
|
||||
NSDictionary *errorDict = nil;
|
||||
NSAppleEventDescriptor *scriptResult = [appleScript executeAndReturnError:&errorDict];
|
||||
if (scriptResult == nil) {
|
||||
NSLog(@"Trash AppleScript error: %@", errorDict);
|
||||
}
|
||||
result = (scriptResult != nil);
|
||||
}
|
||||
|
||||
if (!result) {
|
||||
NSLog(@"ERROR -- Could not trash '%@'", path);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
static BOOL DeleteOrTrash(NSString *path) {
|
||||
NSError *error;
|
||||
|
||||
if ([[NSFileManager defaultManager] removeItemAtPath:path error:&error]) {
|
||||
return YES;
|
||||
}
|
||||
else {
|
||||
// Don't log warning if on Sierra and running inside App Translocation path
|
||||
if ([path rangeOfString:@"/AppTranslocation/"].location == NSNotFound)
|
||||
NSLog(@"WARNING -- Could not delete '%@': %@", path, [error localizedDescription]);
|
||||
|
||||
return Trash(path);
|
||||
}
|
||||
}
|
||||
|
||||
static BOOL AuthorizedInstall(NSString *srcPath, NSString *dstPath, BOOL *canceled) {
|
||||
if (canceled) *canceled = NO;
|
||||
|
||||
// Make sure that the destination path is an app bundle. We're essentially running 'sudo rm -rf'
|
||||
// so we really don't want to fuck this up.
|
||||
if (![[dstPath pathExtension] isEqualToString:@"app"]) return NO;
|
||||
|
||||
// Do some more checks
|
||||
if ([[dstPath stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]] length] == 0) return NO;
|
||||
if ([[srcPath stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]] length] == 0) return NO;
|
||||
|
||||
int pid, status;
|
||||
AuthorizationRef myAuthorizationRef;
|
||||
|
||||
// Get the authorization
|
||||
OSStatus err = AuthorizationCreate(NULL, kAuthorizationEmptyEnvironment, kAuthorizationFlagDefaults, &myAuthorizationRef);
|
||||
if (err != errAuthorizationSuccess) return NO;
|
||||
|
||||
AuthorizationItem myItems = {kAuthorizationRightExecute, 0, NULL, 0};
|
||||
AuthorizationRights myRights = {1, &myItems};
|
||||
AuthorizationFlags myFlags = (AuthorizationFlags)(kAuthorizationFlagInteractionAllowed | kAuthorizationFlagExtendRights | kAuthorizationFlagPreAuthorize);
|
||||
|
||||
err = AuthorizationCopyRights(myAuthorizationRef, &myRights, NULL, myFlags, NULL);
|
||||
if (err != errAuthorizationSuccess) {
|
||||
if (err == errAuthorizationCanceled && canceled)
|
||||
*canceled = YES;
|
||||
goto fail;
|
||||
}
|
||||
|
||||
static OSStatus (*security_AuthorizationExecuteWithPrivileges)(AuthorizationRef authorization, const char *pathToTool,
|
||||
AuthorizationFlags options, char * const *arguments,
|
||||
FILE **communicationsPipe) = NULL;
|
||||
if (!security_AuthorizationExecuteWithPrivileges) {
|
||||
// On 10.7, AuthorizationExecuteWithPrivileges is deprecated. We want to still use it since there's no
|
||||
// good alternative (without requiring code signing). We'll look up the function through dyld and fail
|
||||
// if it is no longer accessible. If Apple removes the function entirely this will fail gracefully. If
|
||||
// they keep the function and throw some sort of exception, this won't fail gracefully, but that's a
|
||||
// risk we'll have to take for now.
|
||||
security_AuthorizationExecuteWithPrivileges = (OSStatus (*)(AuthorizationRef, const char*,
|
||||
AuthorizationFlags, char* const*,
|
||||
FILE **)) dlsym(RTLD_DEFAULT, "AuthorizationExecuteWithPrivileges");
|
||||
}
|
||||
if (!security_AuthorizationExecuteWithPrivileges) goto fail;
|
||||
|
||||
// Delete the destination
|
||||
{
|
||||
char *args[] = {"-rf", (char *)[dstPath fileSystemRepresentation], NULL};
|
||||
err = security_AuthorizationExecuteWithPrivileges(myAuthorizationRef, "/bin/rm", kAuthorizationFlagDefaults, args, NULL);
|
||||
if (err != errAuthorizationSuccess) goto fail;
|
||||
|
||||
// Wait until it's done
|
||||
pid = wait(&status);
|
||||
if (pid == -1 || !WIFEXITED(status)) goto fail; // We don't care about exit status as the destination most likely does not exist
|
||||
}
|
||||
|
||||
// Copy
|
||||
{
|
||||
char *args[] = {"-pR", (char *)[srcPath fileSystemRepresentation], (char *)[dstPath fileSystemRepresentation], NULL};
|
||||
err = security_AuthorizationExecuteWithPrivileges(myAuthorizationRef, "/bin/cp", kAuthorizationFlagDefaults, args, NULL);
|
||||
if (err != errAuthorizationSuccess) goto fail;
|
||||
|
||||
// Wait until it's done
|
||||
pid = wait(&status);
|
||||
if (pid == -1 || !WIFEXITED(status) || WEXITSTATUS(status)) goto fail;
|
||||
}
|
||||
|
||||
AuthorizationFree(myAuthorizationRef, kAuthorizationFlagDefaults);
|
||||
return YES;
|
||||
|
||||
fail:
|
||||
AuthorizationFree(myAuthorizationRef, kAuthorizationFlagDefaults);
|
||||
return NO;
|
||||
}
|
||||
|
||||
static BOOL CopyBundle(NSString *srcPath, NSString *dstPath) {
|
||||
NSFileManager *fm = [NSFileManager defaultManager];
|
||||
NSError *error = nil;
|
||||
|
||||
if ([fm copyItemAtPath:srcPath toPath:dstPath error:&error]) {
|
||||
return YES;
|
||||
}
|
||||
else {
|
||||
NSLog(@"ERROR -- Could not copy '%@' to '%@' (%@)", srcPath, dstPath, error);
|
||||
return NO;
|
||||
}
|
||||
}
|
||||
|
||||
static NSString *ShellQuotedString(NSString *string) {
|
||||
return [NSString stringWithFormat:@"'%@'", [string stringByReplacingOccurrencesOfString:@"'" withString:@"'\\''"]];
|
||||
}
|
||||
|
||||
static void Relaunch(NSString *destinationPath) {
|
||||
// The shell script waits until the original app process terminates.
|
||||
// This is done so that the relaunched app opens as the front-most app.
|
||||
int pid = [[NSProcessInfo processInfo] processIdentifier];
|
||||
|
||||
// Command run just before running open /final/path
|
||||
NSString *preOpenCmd = @"";
|
||||
|
||||
NSString *quotedDestinationPath = ShellQuotedString(destinationPath);
|
||||
|
||||
// OS X >=10.5:
|
||||
// Before we launch the new app, clear xattr:com.apple.quarantine to avoid
|
||||
// duplicate "scary file from the internet" dialog.
|
||||
if (floor(NSAppKitVersionNumber) > NSAppKitVersionNumber10_5) {
|
||||
// Add the -r flag on 10.6
|
||||
preOpenCmd = [NSString stringWithFormat:@"/usr/bin/xattr -d -r com.apple.quarantine %@", quotedDestinationPath];
|
||||
}
|
||||
else {
|
||||
preOpenCmd = [NSString stringWithFormat:@"/usr/bin/xattr -d com.apple.quarantine %@", quotedDestinationPath];
|
||||
}
|
||||
|
||||
NSString *script = [NSString stringWithFormat:@"(while /bin/kill -0 %d >&/dev/null; do /bin/sleep 0.1; done; %@; /usr/bin/open %@) &", pid, preOpenCmd, quotedDestinationPath];
|
||||
|
||||
[NSTask launchedTaskWithLaunchPath:@"/bin/sh" arguments:[NSArray arrayWithObjects:@"-c", script, nil]];
|
||||
}
|
||||
Generated
-206
@@ -1,206 +0,0 @@
|
||||
LetsMove
|
||||
========
|
||||
|
||||
A sample project that demonstrates how to move a running Mac OS X application to the Applications folder.
|
||||
|
||||

|
||||
|
||||
|
||||
Requirements
|
||||
------------
|
||||
Builds and runs on Mac OS X 10.6 or higher. Does NOT support sandboxed applications.
|
||||
|
||||
|
||||
Usage
|
||||
-----
|
||||
|
||||
Option 1:
|
||||
|
||||
Build then embed LetsMove.framework into your app.
|
||||
|
||||
Option 2:
|
||||
|
||||
Copy the following files into your project:
|
||||
|
||||
- PFMoveApplication.h
|
||||
- PFMoveApplication.m
|
||||
|
||||
If your project has ARC enabled, you'll want to disable ARC on the above files. You can do so by adding -fno-objc-arc compiler flag to your PFMoveApplication.m source file. See http://stackoverflow.com/questions/6646052/how-can-i-disable-arc-for-a-single-file-in-a-project/6658549#6658549
|
||||
|
||||
If your application is localized, also copy the 'MoveApplication.string' files into your project.
|
||||
|
||||
Link your application against Security.framework.
|
||||
|
||||
In your app delegate's "-[applicationWillFinishLaunching:]" method, call the PFMoveToApplicationsFolderIfNecessary function at the very top.
|
||||
|
||||
|
||||
License
|
||||
-------
|
||||
Public domain
|
||||
|
||||
|
||||
|
||||
Version History
|
||||
---------------
|
||||
|
||||
* 1.24
|
||||
- Add PFMoveIsInProgress function
|
||||
- Update project for Xcode 9.1
|
||||
|
||||
* 1.23
|
||||
- Fix localization not being used when built as a framework
|
||||
- Fix backwards compatibility with OS X 10.9 and earlier being broken
|
||||
- Make usable for Electron based apps or other apps that do not have access to the main thread dispatch queue
|
||||
- Update Russian localization
|
||||
|
||||
* 1.22
|
||||
- Fix not deleting or trashing itself after copying to /Applications in macOS Sierra
|
||||
|
||||
* 1.21
|
||||
- Support for [Carthage](https://github.com/Carthage/Carthage) added
|
||||
- Project now support OS X 10.6 and higher
|
||||
|
||||
* 1.20
|
||||
- Support for applications bundled inside another application
|
||||
- Brazilian Portuguese localization slightly updated
|
||||
- Build warnings fixed
|
||||
|
||||
* 1.19
|
||||
- Slovak localization added
|
||||
|
||||
* 1.18
|
||||
- Catalan localization added
|
||||
|
||||
* 1.17
|
||||
- Tranditional Chinese localization added.
|
||||
|
||||
* 1.16
|
||||
- Deprecation warning that appears when minimum deployment target is set to OS X 10.10 taken care of
|
||||
|
||||
* 1.15
|
||||
- Swedish localization added
|
||||
|
||||
* 1.14
|
||||
- Hugarian, Serbian, and Turkish localizations added
|
||||
- Macedonian localization added
|
||||
|
||||
* 1.13
|
||||
- Polish localization added
|
||||
|
||||
* 1.12
|
||||
- Use country code based .lproj directories
|
||||
- Make it compile for projects that don't use precompiled headers to import AppKit.framework
|
||||
- Minor adjustment to Dutch localization
|
||||
- Warning fixes in example project
|
||||
|
||||
* 1.11
|
||||
- Objective-C++ compatibility
|
||||
|
||||
* 1.10
|
||||
- Fixed deprecation warnings that show up when building against the OS X 10.9 SDK.
|
||||
|
||||
* 1.9
|
||||
- Removed OS X 10.4 support
|
||||
- Properly detect if the running app is in a disk image
|
||||
- Fixed a bug where if the app's name contained a quote, the app could not be moved
|
||||
- After a successful move, delete the application instead of moving it to the Trash.
|
||||
- Other fixes and improvements
|
||||
|
||||
* 1.8
|
||||
- If the app is already there in the Applications folder but not writable, request authentication from user
|
||||
- Added Korean localization
|
||||
|
||||
* 1.7.2
|
||||
- Fixed an exception that could happen.
|
||||
|
||||
* 1.7.1
|
||||
- Refactoring
|
||||
|
||||
* 1.7
|
||||
- Only move to ~/Appilcations directory if an app is already in there.
|
||||
|
||||
* 1.6.3
|
||||
- Function calls deprecated in 10.7 no longer cause compile time warnings.
|
||||
- Added Simplified Chinese and European Portuguese localizations
|
||||
|
||||
* 1.6.2
|
||||
- Garbage collection compatibility added
|
||||
- Use a new method to check if an application is already running on Mac OS X 10.6 systems or higher
|
||||
|
||||
* 1.6.1
|
||||
- Use exit(0) to terminate the app before relaunching instead of [NSApp terminate:]. We don't want applicationShouldTerminate or applicationWillTerminate NSApplication delegate methods to be called, possibly introducing side effects.
|
||||
|
||||
* 1.6
|
||||
- Resolve any aliases when finding the Applications directory
|
||||
|
||||
* 1.5.2
|
||||
- Cleaned up the code a bit. Almost functionally equivalent to 1.5.1.
|
||||
|
||||
* 1.5.1
|
||||
- Fixed a bug with clearing the quarantine file attribute on Mac OS X 10.5
|
||||
|
||||
* 1.5
|
||||
- Don't prompt to move the application if it has "Applications" in its path somewhere
|
||||
|
||||
* 1.4
|
||||
- Mac OS X 10.5 compatibility fixes
|
||||
|
||||
* 1.3
|
||||
- Fixed a rare bug in the shell script that checks to see if the app is already running
|
||||
- Clear quarantine flag after copying
|
||||
- Compile time option to show normal sized alert supress checkbox button
|
||||
- German, Danish, and Norwegian localizations added
|
||||
|
||||
* 1.2
|
||||
- Copy application from disk image then unmount disk image
|
||||
- Spanish, French, Dutch, and Russian localizations
|
||||
|
||||
* 1.1
|
||||
- Prefers ~/Applications over /Applications if it exists
|
||||
- Escape key pushes the "Do Not Move" button
|
||||
|
||||
* 1.0
|
||||
- First release
|
||||
|
||||
|
||||
Code Contributors:
|
||||
-------------
|
||||
* Andy Kim
|
||||
* John Brayton
|
||||
* Chad Sellers
|
||||
* Kevin LaCoste
|
||||
* Rasmus Andersson
|
||||
* Timothy J. Wood
|
||||
* Matt Gallagher
|
||||
* Whitney Young
|
||||
* Nick Moore
|
||||
* Nicholas Riley
|
||||
* Matt Prowse
|
||||
* Maxim Ananov
|
||||
* Charlie Stigler
|
||||
|
||||
|
||||
Translators:
|
||||
------------
|
||||
* Eita Hayashi (Japanese)
|
||||
* Gleb M. Borisov, Maxim Ananov (Russian)
|
||||
* Wouter Broekhof (Dutch)
|
||||
* Rasmus Andersson / Spotify (French and Spanish)
|
||||
* Markus Kirschner (German)
|
||||
* Fredrik Nannestad (Danish)
|
||||
* Georg Alexander Bøe (Norwegian)
|
||||
* Marco Improda (Italian)
|
||||
* Venj Chu (Simplified Chinese)
|
||||
* Sérgio Miranda (European Portuguese)
|
||||
* Victor Figueiredo and BR Lingo (Brazilian Portuguese)
|
||||
* AppLingua (Korean)
|
||||
* Czech X Team (Czech)
|
||||
* Marek Telecki (Polish)
|
||||
* Petar Vlahu (Macedonian)
|
||||
* Václav Slavík (Hungarian, Serbian, and Turkish)
|
||||
* Erik Vikström (Swedish)
|
||||
* Inndy Lin (Traditional Chinese)
|
||||
* aONe (Catalan)
|
||||
* Marek Hrusovsky (Slovak)
|
||||
|
||||
[](https://github.com/Carthage/Carthage)
|
||||
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
Generated
-2
@@ -1,2 +0,0 @@
|
||||
/* Localized versions of Info.plist keys */
|
||||
|
||||
Generated
-4208
File diff suppressed because it is too large.
Load diff
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
-25
@@ -1,25 +0,0 @@
|
||||
{
|
||||
"name": "LetsMove",
|
||||
"version": "1.24",
|
||||
"summary": "Moves a running Mac application to the /Applications directory.",
|
||||
"homepage": "https://github.com/potionfactory/LetsMove/",
|
||||
"license": "Public Domain",
|
||||
"authors": {
|
||||
"Andy Kim": "andy@potionfactory.com"
|
||||
},
|
||||
"platforms": {
|
||||
"osx": "10.6"
|
||||
},
|
||||
"source": {
|
||||
"git": "https://github.com/potionfactory/LetsMove.git",
|
||||
"tag": "v1.24"
|
||||
},
|
||||
"source_files": "*.{h,m}",
|
||||
"exclude_files": [
|
||||
"main.m",
|
||||
"LetsMoveAppDelegate.{h,m}"
|
||||
],
|
||||
"public_header_files": "PFMoveApplication.h",
|
||||
"resources": "*.lproj",
|
||||
"requires_arc": false
|
||||
}
|
||||
-34
@@ -1,34 +0,0 @@
|
||||
{
|
||||
"name": "ShortcutRecorder",
|
||||
"cocoapods_version": ">= 1.8",
|
||||
"version": "3.1",
|
||||
"summary": "The best control to record shortcuts on macOS",
|
||||
"homepage": "https://github.com/Kentzo/ShortcutRecorder",
|
||||
"license": {
|
||||
"type": "CC BY 4.0",
|
||||
"file": "LICENSE.txt"
|
||||
},
|
||||
"authors": {
|
||||
"Ilya Kulakov": "kulakov.ilya@gmail.com"
|
||||
},
|
||||
"source": {
|
||||
"git": "https://github.com/Kentzo/ShortcutRecorder.git",
|
||||
"tag": "3.1"
|
||||
},
|
||||
"platforms": {
|
||||
"osx": "10.11"
|
||||
},
|
||||
"frameworks": [
|
||||
"Carbon",
|
||||
"Cocoa"
|
||||
],
|
||||
"source_files": "Library/*.{h,m}",
|
||||
"resources": [
|
||||
"Resources/*.lproj",
|
||||
"Resources/Images.xcassets"
|
||||
],
|
||||
"requires_arc": true,
|
||||
"info_plist": {
|
||||
"CFBundleIdentifier": "com.kulakov.ShortcutRecorder"
|
||||
}
|
||||
}
|
||||
Generated
-35
@@ -1,35 +0,0 @@
|
||||
{
|
||||
"name": "Sparkle",
|
||||
"version": "1.23.1",
|
||||
"summary": "A software update framework for macOS",
|
||||
"description": "Sparkle is an easy-to-use software update framework for Cocoa developers.",
|
||||
"homepage": "http://sparkle-project.org",
|
||||
"documentation_url": "http://sparkle-project.org/documentation/",
|
||||
"screenshots": "http://sparkle-project.org/images/screenshot-noshadow@2x.png",
|
||||
"license": {
|
||||
"type": "MIT",
|
||||
"file": "LICENSE"
|
||||
},
|
||||
"authors": {
|
||||
"Mayur Pawashe": "zorgiepoo@gmail.com",
|
||||
"Kornel Lesiński": "pornel@pornel.net",
|
||||
"Jake Petroules": "jake.petroules@petroules.com",
|
||||
"C.W. Betts": "computers57@hotmail.com",
|
||||
"Andy Matuschak": "andy@andymatuschak.org"
|
||||
},
|
||||
"platforms": {
|
||||
"osx": "10.7"
|
||||
},
|
||||
"source": {
|
||||
"http": "https://github.com/lwouis/Sparkle/releases/download/1.23.1/Sparkle-1.23.1.tar.xz"
|
||||
},
|
||||
"source_files": "Sparkle.framework/Versions/A/Headers/*.h",
|
||||
"preserve_paths": "bin/*",
|
||||
"public_header_files": "Sparkle.framework/Versions/A/Headers/*.h",
|
||||
"vendored_frameworks": "Sparkle.framework",
|
||||
"xcconfig": {
|
||||
"FRAMEWORK_SEARCH_PATHS": "\"${PODS_ROOT}/Sparkle\"",
|
||||
"LD_RUNPATH_SEARCH_PATHS": "@loader_path/../Frameworks"
|
||||
},
|
||||
"requires_arc": true
|
||||
}
|
||||
Generated
-49
@@ -1,49 +0,0 @@
|
||||
PODS:
|
||||
- AppCenter/Core (3.3.1)
|
||||
- AppCenter/Crashes (3.3.1):
|
||||
- AppCenter/Core
|
||||
- LetsMove (1.24)
|
||||
- ShortcutRecorder (3.1)
|
||||
- Sparkle (1.23.1)
|
||||
- SwiftyMarkdown (1.1.0)
|
||||
|
||||
DEPENDENCIES:
|
||||
- AppCenter/Crashes (= 3.3.1)
|
||||
- LetsMove (from `https://github.com/lwouis/LetsMove.git`, commit `7abf4daed1a25218f2b52f2dfd190aee5a50071c`)
|
||||
- ShortcutRecorder (from `https://github.com/Kentzo/ShortcutRecorder.git`, commit `f718220854575bad86fa89dd2bb3decd0b14b2b0`)
|
||||
- Sparkle (from `https://raw.githubusercontent.com/lwouis/Sparkle/master/Sparkle.podspec`)
|
||||
- SwiftyMarkdown (= 1.1.0)
|
||||
|
||||
SPEC REPOS:
|
||||
trunk:
|
||||
- AppCenter
|
||||
- SwiftyMarkdown
|
||||
|
||||
EXTERNAL SOURCES:
|
||||
LetsMove:
|
||||
:commit: 7abf4daed1a25218f2b52f2dfd190aee5a50071c
|
||||
:git: https://github.com/lwouis/LetsMove.git
|
||||
ShortcutRecorder:
|
||||
:commit: f718220854575bad86fa89dd2bb3decd0b14b2b0
|
||||
:git: https://github.com/Kentzo/ShortcutRecorder.git
|
||||
Sparkle:
|
||||
:podspec: https://raw.githubusercontent.com/lwouis/Sparkle/master/Sparkle.podspec
|
||||
|
||||
CHECKOUT OPTIONS:
|
||||
LetsMove:
|
||||
:commit: 7abf4daed1a25218f2b52f2dfd190aee5a50071c
|
||||
:git: https://github.com/lwouis/LetsMove.git
|
||||
ShortcutRecorder:
|
||||
:commit: f718220854575bad86fa89dd2bb3decd0b14b2b0
|
||||
:git: https://github.com/Kentzo/ShortcutRecorder.git
|
||||
|
||||
SPEC CHECKSUMS:
|
||||
AppCenter: 76ecf7e959a68e36b0fd314e8dc5897660214423
|
||||
LetsMove: fefe56bc7bc7fb7d37049e28a14f297961229fc5
|
||||
ShortcutRecorder: fdf620aca34101b0cba3b10fca815e0459254189
|
||||
Sparkle: ea073ba9c69cee3ebaab8685ba1e732e14d249f7
|
||||
SwiftyMarkdown: 033abc9dad9a37903f81aec5188cade436f40520
|
||||
|
||||
PODFILE CHECKSUM: 78df0a34910c4c031a65ac3b58dc004c21b552d0
|
||||
|
||||
COCOAPODS: 1.9.1
|
||||
Generated
-1553
File diff suppressed because it is too large.
Load diff
Generated
-337
@@ -1,337 +0,0 @@
|
||||
Creative Commons Attribution 4.0 International Public License
|
||||
|
||||
By exercising the Licensed Rights (defined below), You accept and agree
|
||||
to be bound by the terms and conditions of this Creative Commons
|
||||
Attribution 4.0 International Public License ("Public License"). To the
|
||||
extent this Public License may be interpreted as a contract, You are
|
||||
granted the Licensed Rights in consideration of Your acceptance of
|
||||
these terms and conditions, and the Licensor grants You such rights in
|
||||
consideration of benefits the Licensor receives from making the
|
||||
Licensed Material available under these terms and conditions.
|
||||
|
||||
|
||||
Section 1 -- Definitions.
|
||||
|
||||
a. Adapted Material means material subject to Copyright and Similar
|
||||
Rights that is derived from or based upon the Licensed Material
|
||||
and in which the Licensed Material is translated, altered,
|
||||
arranged, transformed, or otherwise modified in a manner requiring
|
||||
permission under the Copyright and Similar Rights held by the
|
||||
Licensor. For purposes of this Public License, where the Licensed
|
||||
Material is a musical work, performance, or sound recording,
|
||||
Adapted Material is always produced where the Licensed Material is
|
||||
synched in timed relation with a moving image.
|
||||
|
||||
b. Adapter's License means the license You apply to Your Copyright
|
||||
and Similar Rights in Your contributions to Adapted Material in
|
||||
accordance with the terms and conditions of this Public License.
|
||||
|
||||
c. Copyright and Similar Rights means copyright and/or similar rights
|
||||
closely related to copyright including, without limitation,
|
||||
performance, broadcast, sound recording, and Sui Generis Database
|
||||
Rights, without regard to how the rights are labeled or
|
||||
categorized. For purposes of this Public License, the rights
|
||||
specified in Section 2(b)(1)-(2) are not Copyright and Similar
|
||||
Rights.
|
||||
|
||||
d. Effective Technological Measures means those measures that, in the
|
||||
absence of proper authority, may not be circumvented under laws
|
||||
fulfilling obligations under Article 11 of the WIPO Copyright
|
||||
Treaty adopted on December 20, 1996, and/or similar international
|
||||
agreements.
|
||||
|
||||
e. Exceptions and Limitations means fair use, fair dealing, and/or
|
||||
any other exception or limitation to Copyright and Similar Rights
|
||||
that applies to Your use of the Licensed Material.
|
||||
|
||||
f. Licensed Material means the artistic or literary work, database,
|
||||
or other material to which the Licensor applied this Public
|
||||
License.
|
||||
|
||||
g. Licensed Rights means the rights granted to You subject to the
|
||||
terms and conditions of this Public License, which are limited to
|
||||
all Copyright and Similar Rights that apply to Your use of the
|
||||
Licensed Material and that the Licensor has authority to license.
|
||||
|
||||
h. Licensor means the individual(s) or entity(ies) granting rights
|
||||
under this Public License.
|
||||
|
||||
i. Share means to provide material to the public by any means or
|
||||
process that requires permission under the Licensed Rights, such
|
||||
as reproduction, public display, public performance, distribution,
|
||||
dissemination, communication, or importation, and to make material
|
||||
available to the public including in ways that members of the
|
||||
public may access the material from a place and at a time
|
||||
individually chosen by them.
|
||||
|
||||
j. Sui Generis Database Rights means rights other than copyright
|
||||
resulting from Directive 96/9/EC of the European Parliament and of
|
||||
the Council of 11 March 1996 on the legal protection of databases,
|
||||
as amended and/or succeeded, as well as other essentially
|
||||
equivalent rights anywhere in the world.
|
||||
|
||||
k. You means the individual or entity exercising the Licensed Rights
|
||||
under this Public License. Your has a corresponding meaning.
|
||||
|
||||
|
||||
Section 2 -- Scope.
|
||||
|
||||
a. License grant.
|
||||
|
||||
1. Subject to the terms and conditions of this Public License,
|
||||
the Licensor hereby grants You a worldwide, royalty-free,
|
||||
non-sublicensable, non-exclusive, irrevocable license to
|
||||
exercise the Licensed Rights in the Licensed Material to:
|
||||
|
||||
a. reproduce and Share the Licensed Material, in whole or
|
||||
in part; and
|
||||
|
||||
b. produce, reproduce, and Share Adapted Material.
|
||||
|
||||
2. Exceptions and Limitations. For the avoidance of doubt, where
|
||||
Exceptions and Limitations apply to Your use, this Public
|
||||
License does not apply, and You do not need to comply with
|
||||
its terms and conditions.
|
||||
|
||||
3. Term. The term of this Public License is specified in Section
|
||||
6(a).
|
||||
|
||||
4. Media and formats; technical modifications allowed. The
|
||||
Licensor authorizes You to exercise the Licensed Rights in
|
||||
all media and formats whether now known or hereafter created,
|
||||
and to make technical modifications necessary to do so. The
|
||||
Licensor waives and/or agrees not to assert any right or
|
||||
authority to forbid You from making technical modifications
|
||||
necessary to exercise the Licensed Rights, including
|
||||
technical modifications necessary to circumvent Effective
|
||||
Technological Measures. For purposes of this Public License,
|
||||
simply making modifications authorized by this Section 2(a)
|
||||
(4) never produces Adapted Material.
|
||||
|
||||
5. Downstream recipients.
|
||||
|
||||
a. Offer from the Licensor -- Licensed Material. Every
|
||||
recipient of the Licensed Material automatically
|
||||
receives an offer from the Licensor to exercise the
|
||||
Licensed Rights under the terms and conditions of this
|
||||
Public License.
|
||||
|
||||
b. No downstream restrictions. You may not offer or impose
|
||||
any additional or different terms or conditions on, or
|
||||
apply any Effective Technological Measures to, the
|
||||
Licensed Material if doing so restricts exercise of the
|
||||
Licensed Rights by any recipient of the Licensed
|
||||
Material.
|
||||
|
||||
6. No endorsement. Nothing in this Public License constitutes or
|
||||
may be construed as permission to assert or imply that You
|
||||
are, or that Your use of the Licensed Material is, connected
|
||||
with, or sponsored, endorsed, or granted official status by,
|
||||
the Licensor or others designated to receive attribution as
|
||||
provided in Section 3(a)(1)(A)(i).
|
||||
|
||||
b. Other rights.
|
||||
|
||||
1. Moral rights, such as the right of integrity, are not
|
||||
licensed under this Public License, nor are publicity,
|
||||
privacy, and/or other similar personality rights; however, to
|
||||
the extent possible, the Licensor waives and/or agrees not to
|
||||
assert any such rights held by the Licensor to the limited
|
||||
extent necessary to allow You to exercise the Licensed
|
||||
Rights, but not otherwise.
|
||||
|
||||
2. Patent and trademark rights are not licensed under this
|
||||
Public License.
|
||||
|
||||
3. To the extent possible, the Licensor waives any right to
|
||||
collect royalties from You for the exercise of the Licensed
|
||||
Rights, whether directly or through a collecting society
|
||||
under any voluntary or waivable statutory or compulsory
|
||||
licensing scheme. In all other cases the Licensor expressly
|
||||
reserves any right to collect such royalties.
|
||||
|
||||
|
||||
Section 3 -- License Conditions.
|
||||
|
||||
Your exercise of the Licensed Rights is expressly made subject to the
|
||||
following conditions.
|
||||
|
||||
a. Attribution.
|
||||
|
||||
1. If You Share the Licensed Material (including in modified
|
||||
form), You must:
|
||||
|
||||
a. retain the following if it is supplied by the Licensor
|
||||
with the Licensed Material:
|
||||
|
||||
i. identification of the creator(s) of the Licensed
|
||||
Material and any others designated to receive
|
||||
attribution, in any reasonable manner requested by
|
||||
the Licensor (including by pseudonym if
|
||||
designated);
|
||||
|
||||
ii. a copyright notice;
|
||||
|
||||
iii. a notice that refers to this Public License;
|
||||
|
||||
iv. a notice that refers to the disclaimer of
|
||||
warranties;
|
||||
|
||||
v. a URI or hyperlink to the Licensed Material to the
|
||||
extent reasonably practicable;
|
||||
|
||||
b. indicate if You modified the Licensed Material and
|
||||
retain an indication of any previous modifications; and
|
||||
|
||||
c. indicate the Licensed Material is licensed under this
|
||||
Public License, and include the text of, or the URI or
|
||||
hyperlink to, this Public License.
|
||||
|
||||
2. You may satisfy the conditions in Section 3(a)(1) in any
|
||||
reasonable manner based on the medium, means, and context in
|
||||
which You Share the Licensed Material. For example, it may be
|
||||
reasonable to satisfy the conditions by providing a URI or
|
||||
hyperlink to a resource that includes the required
|
||||
information.
|
||||
|
||||
3. If requested by the Licensor, You must remove any of the
|
||||
information required by Section 3(a)(1)(A) to the extent
|
||||
reasonably practicable.
|
||||
|
||||
4. If You Share Adapted Material You produce, the Adapter's
|
||||
License You apply must not prevent recipients of the Adapted
|
||||
Material from complying with this Public License.
|
||||
|
||||
|
||||
Section 4 -- Sui Generis Database Rights.
|
||||
|
||||
Where the Licensed Rights include Sui Generis Database Rights that
|
||||
apply to Your use of the Licensed Material:
|
||||
|
||||
a. for the avoidance of doubt, Section 2(a)(1) grants You the right
|
||||
to extract, reuse, reproduce, and Share all or a substantial
|
||||
portion of the contents of the database;
|
||||
|
||||
b. if You include all or a substantial portion of the database
|
||||
contents in a database in which You have Sui Generis Database
|
||||
Rights, then the database in which You have Sui Generis Database
|
||||
Rights (but not its individual contents) is Adapted Material; and
|
||||
|
||||
c. You must comply with the conditions in Section 3(a) if You Share
|
||||
all or a substantial portion of the contents of the database.
|
||||
|
||||
For the avoidance of doubt, this Section 4 supplements and does not
|
||||
replace Your obligations under this Public License where the Licensed
|
||||
Rights include other Copyright and Similar Rights.
|
||||
|
||||
|
||||
Section 5 -- Disclaimer of Warranties and Limitation of Liability.
|
||||
|
||||
a. UNLESS OTHERWISE SEPARATELY UNDERTAKEN BY THE LICENSOR, TO THE
|
||||
EXTENT POSSIBLE, THE LICENSOR OFFERS THE LICENSED MATERIAL AS-IS
|
||||
AND AS-AVAILABLE, AND MAKES NO REPRESENTATIONS OR WARRANTIES OF
|
||||
ANY KIND CONCERNING THE LICENSED MATERIAL, WHETHER EXPRESS,
|
||||
IMPLIED, STATUTORY, OR OTHER. THIS INCLUDES, WITHOUT LIMITATION,
|
||||
WARRANTIES OF TITLE, MERCHANTABILITY, FITNESS FOR A PARTICULAR
|
||||
PURPOSE, NON-INFRINGEMENT, ABSENCE OF LATENT OR OTHER DEFECTS,
|
||||
ACCURACY, OR THE PRESENCE OR ABSENCE OF ERRORS, WHETHER OR NOT
|
||||
KNOWN OR DISCOVERABLE. WHERE DISCLAIMERS OF WARRANTIES ARE NOT
|
||||
ALLOWED IN FULL OR IN PART, THIS DISCLAIMER MAY NOT APPLY TO YOU.
|
||||
|
||||
b. TO THE EXTENT POSSIBLE, IN NO EVENT WILL THE LICENSOR BE LIABLE
|
||||
TO YOU ON ANY LEGAL THEORY (INCLUDING, WITHOUT LIMITATION,
|
||||
NEGLIGENCE) OR OTHERWISE FOR ANY DIRECT, SPECIAL, INDIRECT,
|
||||
INCIDENTAL, CONSEQUENTIAL, PUNITIVE, EXEMPLARY, OR OTHER LOSSES,
|
||||
COSTS, EXPENSES, OR DAMAGES ARISING OUT OF THIS PUBLIC LICENSE OR
|
||||
USE OF THE LICENSED MATERIAL, EVEN IF THE LICENSOR HAS BEEN
|
||||
ADVISED OF THE POSSIBILITY OF SUCH LOSSES, COSTS, EXPENSES, OR
|
||||
DAMAGES. WHERE A LIMITATION OF LIABILITY IS NOT ALLOWED IN FULL OR
|
||||
IN PART, THIS LIMITATION MAY NOT APPLY TO YOU.
|
||||
|
||||
c. The disclaimer of warranties and limitation of liability provided
|
||||
above shall be interpreted in a manner that, to the extent
|
||||
possible, most closely approximates an absolute disclaimer and
|
||||
waiver of all liability.
|
||||
|
||||
|
||||
Section 6 -- Term and Termination.
|
||||
|
||||
a. This Public License applies for the term of the Copyright and
|
||||
Similar Rights licensed here. However, if You fail to comply with
|
||||
this Public License, then Your rights under this Public License
|
||||
terminate automatically.
|
||||
|
||||
b. Where Your right to use the Licensed Material has terminated under
|
||||
Section 6(a), it reinstates:
|
||||
|
||||
1. automatically as of the date the violation is cured, provided
|
||||
it is cured within 30 days of Your discovery of the
|
||||
violation; or
|
||||
|
||||
2. upon express reinstatement by the Licensor.
|
||||
|
||||
For the avoidance of doubt, this Section 6(b) does not affect any
|
||||
right the Licensor may have to seek remedies for Your violations
|
||||
of this Public License.
|
||||
|
||||
c. For the avoidance of doubt, the Licensor may also offer the
|
||||
Licensed Material under separate terms or conditions or stop
|
||||
distributing the Licensed Material at any time; however, doing so
|
||||
will not terminate this Public License.
|
||||
|
||||
d. Sections 1, 5, 6, 7, and 8 survive termination of this Public
|
||||
License.
|
||||
|
||||
|
||||
Section 7 -- Other Terms and Conditions.
|
||||
|
||||
a. The Licensor shall not be bound by any additional or different
|
||||
terms or conditions communicated by You unless expressly agreed.
|
||||
|
||||
b. Any arrangements, understandings, or agreements regarding the
|
||||
Licensed Material not stated herein are separate from and
|
||||
independent of the terms and conditions of this Public License.
|
||||
|
||||
|
||||
Section 8 -- Interpretation.
|
||||
|
||||
a. For the avoidance of doubt, this Public License does not, and
|
||||
shall not be interpreted to, reduce, limit, restrict, or impose
|
||||
conditions on any use of the Licensed Material that could lawfully
|
||||
be made without permission under this Public License.
|
||||
|
||||
b. To the extent possible, if any provision of this Public License is
|
||||
deemed unenforceable, it shall be automatically reformed to the
|
||||
minimum extent necessary to make it enforceable. If the provision
|
||||
cannot be reformed, it shall be severed from this Public License
|
||||
without affecting the enforceability of the remaining terms and
|
||||
conditions.
|
||||
|
||||
c. No term or condition of this Public License will be waived and no
|
||||
failure to comply consented to unless expressly agreed to by the
|
||||
Licensor.
|
||||
|
||||
d. Nothing in this Public License constitutes or may be interpreted
|
||||
as a limitation upon, or waiver of, any privileges and immunities
|
||||
that apply to the Licensor or You, including from the legal
|
||||
processes of any jurisdiction or authority.
|
||||
|
||||
|
||||
=======================================================================
|
||||
|
||||
Creative Commons is not a party to its public
|
||||
licenses. Notwithstanding, Creative Commons may elect to apply one of
|
||||
its public licenses to material it publishes and in those instances
|
||||
will be considered the “Licensor.” The text of the Creative Commons
|
||||
public licenses is dedicated to the public domain under the CC0 Public
|
||||
Domain Dedication. Except for the limited purpose of indicating that
|
||||
material is shared under a Creative Commons public license or as
|
||||
otherwise permitted by the Creative Commons policies published at
|
||||
creativecommons.org/policies, Creative Commons does not authorize the
|
||||
use of the trademark "Creative Commons" or any other trademark or logo
|
||||
of Creative Commons without its prior written consent including,
|
||||
without limitation, in connection with any unauthorized modifications
|
||||
to any of its public licenses or any other arrangements,
|
||||
understandings, or agreements concerning use of licensed material. For
|
||||
the avoidance of doubt, this paragraph does not form part of the
|
||||
public licenses.
|
||||
Generated
-91
@@ -1,91 +0,0 @@
|
||||
[](http://creativecommons.org/licenses/by/4.0/)
|
||||

|
||||

|
||||
|
||||
[](https://cocoapods.org/pods/ShortcutRecorder)
|
||||
[](https://github.com/Carthage/Carthage)
|
||||
|
||||
[](https://codecov.io/gh/Kentzo/ShortcutRecorder)
|
||||
[](https://travis-ci.org/Kentzo/ShortcutRecorder)
|
||||
|
||||
# ShortcutRecorder
|
||||
|
||||

|
||||
|
||||
The best control to record shortcuts on macOS
|
||||
|
||||
- Objective-C framework developed with Swift in mind
|
||||
- Easily stylable
|
||||
- Translated into 22 languages
|
||||
- Supports macOS Accessibility
|
||||
- Thoroughly documented and tested
|
||||
- Global and Local shortcuts for both key up and key down actions
|
||||
- Mac App Store approved
|
||||
- End-to-end Interface Builder integration
|
||||
|
||||
## What is inside
|
||||
|
||||
The framework comes with:
|
||||
- `SRRecorderControl` to render and capture user input
|
||||
- `SRRecorderControlStyle` for custom styling
|
||||
- `SRShortcut` that represents a shortcut model
|
||||
- `SRGlobalShortcutMonitor` to turn the shortcut into an action by registering a global hot key
|
||||
- `SRLocalShortcutMonitor` for manual handling in the responder chain and `NSEvent` monitors
|
||||
- `SRShortcutController` for smooth Cocoa Bindings and seamless Interface Builder integration
|
||||
- `SRShortcutValidator` to check validity of the shortcut against Cocoa key equivalents and global hot keys
|
||||
- `NSValueTransformer` and `NSFormatter` subclasses for custom alternations
|
||||
|
||||
```swift
|
||||
import ShortcutRecorder
|
||||
|
||||
let defaults = NSUserDefaultsController.shared
|
||||
let keyPath = "values.shortcut"
|
||||
let options = [NSBindingOption.valueTransformerName: .keyedUnarchiveFromDataTransformerName]
|
||||
|
||||
let beepAction = ShortcutAction(keyPath: keyPath, of: defaults) { _ in
|
||||
NSSound.beep()
|
||||
return true
|
||||
}
|
||||
GlobalShortcutMonitor.shared.addAction(beepAction, forKeyEvent: .down)
|
||||
|
||||
let recorder = RecorderControl()
|
||||
recorder.bind(.value, to: defaults, withKeyPath: keyPath, options: options)
|
||||
|
||||
recorder.objectValue = Shortcut(keyEquivalent: "⇧⌘A")
|
||||
```
|
||||
|
||||
## Integration
|
||||
|
||||
The framework supports [module maps](https://clang.llvm.org/docs/Modules.html), explicit linking is not required: simply `import ShortcutRecorder` / `#import <ShortcutRecorder/ShortcutRecorder.h>`
|
||||
|
||||
### CocoaPods
|
||||
|
||||
pod 'ShortcutRecorder', '~> 3.1'
|
||||
|
||||
### Carthage
|
||||
|
||||
github "Kentzo/ShortcutRecorder" ~> 3.1
|
||||
|
||||
Prebuilt frameworks are available.
|
||||
|
||||
### Git Submodule
|
||||
|
||||
Add the submodule:
|
||||
|
||||
git submodule add git://github.com/Kentzo/ShortcutRecorder.git
|
||||
|
||||
Then drag'n'drop into Xcode workspace of your project.
|
||||
|
||||
## Next Steps
|
||||
|
||||
- The Documentation playground covers all parts of the framework (see in Xcode)
|
||||
- The Inspector app is useful during development of custom styles
|
||||
- Read about [Styling](https://github.com/Kentzo/ShortcutRecorder/wiki/Styling) and special notes regarding [Cocoa's Key Equivalents](https://github.com/Kentzo/ShortcutRecorder/wiki/Cocoa-Key-Equivalents).
|
||||
|
||||
## Questions
|
||||
|
||||
Still have questions? [Create an issue](https://github.com/Kentzo/ShortcutRecorder/issues/new).
|
||||
|
||||
## Paid Support
|
||||
|
||||
Paid support is available for custom alterations, help with integration and general advice regarding Cocoa development.
|
||||
@@ -1,56 +0,0 @@
|
||||
/* Literal for the Space key */
|
||||
"Space" = "Espai";
|
||||
|
||||
/* VoiceOver type of the object recorded by the control */
|
||||
"Shortcut" = "Drecera";
|
||||
|
||||
/* VoiceOver value for the value-less control */
|
||||
"Empty" = "Buida";
|
||||
|
||||
/* Tooltip for the cancel button when the control has a value */
|
||||
"Use old shortcut" = "Utilitza la drecera antiga";
|
||||
|
||||
/* Tooltip for the cancel button when the control has no value */
|
||||
"Cancel recording" = "Cancel·la la gravació";
|
||||
|
||||
/* Tooltip for the clear button */
|
||||
"Delete current shortcut" = "Elimineu la drecera actual";
|
||||
|
||||
/* Tooltip for the recording mode */
|
||||
"Type shortcut" = "Determina drecera";
|
||||
|
||||
/* Label for the value-less control */
|
||||
"Record Shortcut" = "Gravar drecera";
|
||||
|
||||
/* Tooltip for the normal mode */
|
||||
"Click to record shortcut" = "Cliqueu per desar drecera";
|
||||
|
||||
/* Value for the NSLocalizedFailureReasonErrorKey key of validator's error */
|
||||
"The \"%@\" shortcut can't be used!" = "La combinació “%@“ no pot ser utilitzada!";
|
||||
|
||||
/* Value for the NSLocalizedDescriptionKey key of validator's delegate error */
|
||||
"The \"%@\" shortcut can't be used because %@." = "La combinació “%@“ no pot ser utilitzada perquè %@.";
|
||||
|
||||
/* Default value for the NSLocalizedDescriptionKey key of validator's error */
|
||||
"The \"%@\" shortcut is already in use." = "La combinació “%@“ ja està en ús.";
|
||||
|
||||
/* Value for the NSLocalizedDescriptionKey key of validator's system shortcuts error */
|
||||
"The \"%@\" shortcut can't be used because it's already used by a system-wide keyboard shortcut. If you really want to use this shortcut, most shortcuts can be changed in the Keyboard panel in System Preferences." = "La combinació “%@“ no pot ser utilitzada perquè actualment l’utilitza el sistema. (Si realment voleu utilitzar aquesta combinació, podeu modificar les dreceres del sistema en l'apartat Teclat i Ratolí del panell de preferències del sistema).";
|
||||
|
||||
/* Value for the NSLocalizedDescriptionKey key of validator's menu error */
|
||||
"The \"%@\" shortcut can't be used because it's already used by the \"%@\" menu item." = "La combinació “%@“ no pot ser utilitzada perquè actualment l’ítem de menú “%@“ ja l’utilitza.";
|
||||
|
||||
/* Accessibility name of the Command modifier key */
|
||||
"Command" = "Ordre";
|
||||
|
||||
/* VoiceOver name of the Option modifier key */
|
||||
"Option" = "Opció";
|
||||
|
||||
/* VoiceOver name of the Shift modifier key */
|
||||
"Shift" = "Majúscules";
|
||||
|
||||
/* VoiceOver name of the Control modifier key */
|
||||
"Control" = "Control";
|
||||
|
||||
/* VoiceOver name of the modifier keys delimiter */
|
||||
"-" = "-";
|
||||
@@ -1,56 +0,0 @@
|
||||
/* Literal for the Space key */
|
||||
"Space" = "Mezerník";
|
||||
|
||||
/* VoiceOver type of the object recorded by the control */
|
||||
"Shortcut" = "Kl. Zkratku";
|
||||
|
||||
/* VoiceOver value for the value-less control */
|
||||
"Empty" = "Prázdné";
|
||||
|
||||
/* Tooltip for the cancel button when the control has a value */
|
||||
"Use old shortcut" = "Použít starou kl. zkratku";
|
||||
|
||||
/* Tooltip for the cancel button when the control has no value */
|
||||
"Cancel recording" = "Zrušit nahrávání";
|
||||
|
||||
/* Tooltip for the clear button */
|
||||
"Delete current shortcut" = "Odstranit aktuální kl. zkratku";
|
||||
|
||||
/* Tooltip for the recording mode */
|
||||
"Type shortcut" = "Zadejte kl. zkratku";
|
||||
|
||||
/* Label for the value-less control */
|
||||
"Record Shortcut" = "Nahrát kl. zkratku";
|
||||
|
||||
/* Tooltip for the normal mode */
|
||||
"Click to record shortcut" = "Klepněte pro záznam kl. zkratky";
|
||||
|
||||
/* Value for the NSLocalizedFailureReasonErrorKey key of validator's error */
|
||||
"The \"%@\" shortcut can't be used!" = "Kombinaci kláves „%@“ nelze použít!";
|
||||
|
||||
/* Value for the NSLocalizedDescriptionKey key of validator's delegate error */
|
||||
"The \"%@\" shortcut can't be used because %@." = "Kombinaci kláves „%@“ nelze použít, protože %@.";
|
||||
|
||||
/* Default value for the NSLocalizedDescriptionKey key of validator's error */
|
||||
"The \"%@\" shortcut is already in use." = "Kombinaci kláves „%@“ je již používána.";
|
||||
|
||||
/* Value for the NSLocalizedDescriptionKey key of validator's system shortcuts error */
|
||||
"The \"%@\" shortcut can't be used because it's already used by a system-wide keyboard shortcut. If you really want to use this shortcut, most shortcuts can be changed in the Keyboard panel in System Preferences." = "Kombinaci kláves „%@“ nelze použít, protože je již použita v systémových kl. zkratkách. Pokud si přejete skutečně používat tuto kl. zkratku, musíte změnit nastavení již použité zkratky v Předvolbách systému, v panelu Klávesnice, Klávesové zkratky.";
|
||||
|
||||
/* Value for the NSLocalizedDescriptionKey key of validator's menu error */
|
||||
"The \"%@\" shortcut can't be used because it's already used by the \"%@\" menu item." = "Kombinaci kláves „%@“ nelze použít, protože je již použita na položku nabídky „%@“.";
|
||||
|
||||
/* Accessibility name of the Command modifier key */
|
||||
"Command" = "Command";
|
||||
|
||||
/* VoiceOver name of the Option modifier key */
|
||||
"Option" = "Alt";
|
||||
|
||||
/* VoiceOver name of the Shift modifier key */
|
||||
"Shift" = "Shift";
|
||||
|
||||
/* VoiceOver name of the Control modifier key */
|
||||
"Control" = "Control";
|
||||
|
||||
/* VoiceOver name of the modifier keys delimiter */
|
||||
"-" = "-";
|
||||
@@ -1,56 +0,0 @@
|
||||
/* Literal for the Space key */
|
||||
"Space" = "Διάστημα";
|
||||
|
||||
/* VoiceOver type of the object recorded by the control */
|
||||
"Shortcut" = "Συντόμευσης";
|
||||
|
||||
/* VoiceOver value for the value-less control */
|
||||
"Empty" = "Κενό";
|
||||
|
||||
/* Tooltip for the cancel button when the control has a value */
|
||||
"Use old shortcut" = "Χρήση παλιάς συντόμευσης";
|
||||
|
||||
/* Tooltip for the cancel button when the control has no value */
|
||||
"Cancel recording" = "Ακύρωση εγγραφής";
|
||||
|
||||
/* Tooltip for the clear button */
|
||||
"Delete current shortcut" = "Διαγραφή τρέχουσας συντομεύσεως";
|
||||
|
||||
/* Tooltip for the recording mode */
|
||||
"Type shortcut" = "Εισαγωγή συντόμευσης";
|
||||
|
||||
/* Label for the value-less control */
|
||||
"Record Shortcut" = "Εγγραφή Συντόμευσης";
|
||||
|
||||
/* Tooltip for the normal mode */
|
||||
"Click to record shortcut" = "Κλίκ για εισαγωγή συντόμευσης";
|
||||
|
||||
/* Value for the NSLocalizedFailureReasonErrorKey key of validator's error */
|
||||
"The \"%@\" shortcut can't be used!" = "Ο συνδιασμός πλήκτρων «%@» δεν μπορεί να χρησιμοποιηθεί!";
|
||||
|
||||
/* Value for the NSLocalizedDescriptionKey key of validator's delegate error */
|
||||
"The \"%@\" shortcut can't be used because %@." = "Ο συνδιασμός πλήκτρων «%@» δεν μπορεί να χρησιμοποιηθεί λόγω %@.";
|
||||
|
||||
/* Default value for the NSLocalizedDescriptionKey key of validator's error */
|
||||
"The \"%@\" shortcut is already in use." = "Ο συνδιασμός πλήκτρων «%@» χρησιμοποιείται ήδη.";
|
||||
|
||||
/* Value for the NSLocalizedDescriptionKey key of validator's system shortcuts error */
|
||||
"The \"%@\" shortcut can't be used because it's already used by a system-wide keyboard shortcut. If you really want to use this shortcut, most shortcuts can be changed in the Keyboard panel in System Preferences." = "Ο συνδιασμός πλήκτρων «%@» δεν μπορεί να χρησιμοποιηθεί γιατί χρησιμοποιείται ήδη από μια συντόμευση συστήματος. Αν θέλετε οπωσδήποτε να χρησιμοποιήσετε αυτό τον συνδιασμό, οι περισσότερες συντομεύσεις μπορούν να αλλαχθούν στο πάνελ Keyboard & Mouse στο System Preferences.";
|
||||
|
||||
/* Value for the NSLocalizedDescriptionKey key of validator's menu error */
|
||||
"The \"%@\" shortcut can't be used because it's already used by the \"%@\" menu item." = "Ο συνδιασμός πλήκτρων «%@» δεν μπορεί να χρησιμοποιηθεί γιατί χρησιμοποιείται ήδη από το μενού «%@».";
|
||||
|
||||
/* VoiceOver name of the Command modifier key */
|
||||
"Command" = "Command";
|
||||
|
||||
/* VoiceOver name of the Option modifier key */
|
||||
"Option" = "Option";
|
||||
|
||||
/* VoiceOver name of the Shift modifier key */
|
||||
"Shift" = "Shift";
|
||||
|
||||
/* VoiceOver name of the Control modifier key */
|
||||
"Control" = "Control";
|
||||
|
||||
/* VoiceOver name of the modifier keys delimiter */
|
||||
"-" = "-";
|
||||
@@ -1,56 +0,0 @@
|
||||
/* Literal for the Space key */
|
||||
"Space" = "Mellomrom";
|
||||
|
||||
/* VoiceOver type of the object recorded by the control */
|
||||
"Shortcut" = "Snarvei";
|
||||
|
||||
/* VoiceOver value for the value-less control */
|
||||
"Empty" = "Tomt";
|
||||
|
||||
/* Tooltip for the cancel button when the control has a value */
|
||||
"Use old shortcut" = "Bruk forrige snarvei";
|
||||
|
||||
/* Tooltip for the cancel button when the control has no value */
|
||||
"Cancel recording" = "Avbryt innspillingen";
|
||||
|
||||
/* Tooltip for the clear button */
|
||||
"Delete current shortcut" = "Slett gjeldende snarvei";
|
||||
|
||||
/* Tooltip for the recording mode */
|
||||
"Type shortcut" = "Trykk snarvei";
|
||||
|
||||
/* Label for the value-less control */
|
||||
"Record Shortcut" = "Spill inn snarvei";
|
||||
|
||||
/* Tooltip for the normal mode */
|
||||
"Click to record shortcut" = "Klikk for å velge snarvei";
|
||||
|
||||
/* Value for the NSLocalizedFailureReasonErrorKey key of validator's error */
|
||||
"The \"%@\" shortcut can't be used!" = "Tastekombinasjonen «%@» kan ikke brukes!";
|
||||
|
||||
/* Value for the NSLocalizedDescriptionKey key of validator's delegate error */
|
||||
"The \"%@\" shortcut can't be used because %@." = "Tastekombinasjonen «%@» kan ikke brukes fordi %@.";
|
||||
|
||||
/* Default value for the NSLocalizedDescriptionKey key of validator's error */
|
||||
"The \"%@\" shortcut is already in use." = "Tastekombinasjonen «%@» allerede i bruk.";
|
||||
|
||||
/* Value for the NSLocalizedDescriptionKey key of validator's system shortcuts error */
|
||||
"The \"%@\" shortcut can't be used because it's already used by a system-wide keyboard shortcut. If you really want to use this shortcut, most shortcuts can be changed in the Keyboard panel in System Preferences." = "Tastekombinasjonen «%@» kan ikke brukes fordi den allerede er brukt som en tastatursnarvei i Systemvalg. Hvis du ønsker å bruke denne tastekombinasjonen må du først endre den i Tastatur-panelet i Systemvalg. ";
|
||||
|
||||
/* Value for the NSLocalizedDescriptionKey key of validator's menu error */
|
||||
"The \"%@\" shortcut can't be used because it's already used by the \"%@\" menu item." = "Tastekombinasjonen «%@» kan ikke brukes fordi den allerede er i bruk av menyvalget «%@».";
|
||||
|
||||
/* Accessibility name of the Command modifier key */
|
||||
"Command" = "Kommando";
|
||||
|
||||
/* VoiceOver name of the Option modifier key */
|
||||
"Option" = "Tilvalg";
|
||||
|
||||
/* VoiceOver name of the Shift modifier key */
|
||||
"Shift" = "Skift";
|
||||
|
||||
/* VoiceOver name of the Control modifier key */
|
||||
"Control" = "Kontroll";
|
||||
|
||||
/* VoiceOver name of the modifier keys delimiter */
|
||||
"-" = " - ";
|
||||
@@ -1,56 +0,0 @@
|
||||
/* Literal for the Space key */
|
||||
"Space" = "Espaço";
|
||||
|
||||
/* VoiceOver type of the object recorded by the control */
|
||||
"Shortcut" = "Atalho";
|
||||
|
||||
/* VoiceOver value for the value-less control */
|
||||
"Empty" = "Vazia";
|
||||
|
||||
/* Tooltip for the cancel button when the control has a value */
|
||||
"Use old shortcut" = "Usar atalho antigo";
|
||||
|
||||
/* Tooltip for the cancel button when the control has no value */
|
||||
"Cancel recording" = "Cancelar gravação";
|
||||
|
||||
/* Tooltip for the clear button */
|
||||
"Delete current shortcut" = "Apagar atalho atual";
|
||||
|
||||
/* Tooltip for the recording mode */
|
||||
"Type shortcut" = "Digitar atalho";
|
||||
|
||||
/* Label for the value-less control */
|
||||
"Record Shortcut" = "Gravar Atalho";
|
||||
|
||||
/* Tooltip for the normal mode */
|
||||
"Click to record shortcut" = "Clique para gravar atalho";
|
||||
|
||||
/* Value for the NSLocalizedFailureReasonErrorKey key of validator's error */
|
||||
"The \"%@\" shortcut can't be used!" = "A combinação de teclas “%@“ não pode ser usada!";
|
||||
|
||||
/* Value for the NSLocalizedDescriptionKey key of validator's delegate error */
|
||||
"The \"%@\" shortcut can't be used because %@." = "A combinação de teclas “%@“ não pode ser usada porque %@.";
|
||||
|
||||
/* Default value for the NSLocalizedDescriptionKey key of validator's error */
|
||||
"The \"%@\" shortcut is already in use." = "A combinação de teclas “%@“ já está em uso.";
|
||||
|
||||
/* Value for the NSLocalizedDescriptionKey key of validator's system shortcuts error */
|
||||
"The \"%@\" shortcut can't be used because it's already used by a system-wide keyboard shortcut. If you really want to use this shortcut, most shortcuts can be changed in the Keyboard panel in System Preferences." = "A combinação de teclas “%@“ não pode ser usada porque já está a ser usada por um atalho global do sistema. Se quiser realmente usar esta combinação, a maioria dos atalhos podem ser mudados no painel Teclado das Preferências do Sistema.";
|
||||
|
||||
/* Value for the NSLocalizedDescriptionKey key of validator's menu error */
|
||||
"The \"%@\" shortcut can't be used because it's already used by the \"%@\" menu item." = "A combinação de teclas “%@“ não pode ser usada porque já está a ser usada pelo item de menu “%@“.";
|
||||
|
||||
/* Accessibility name of the Command modifier key */
|
||||
"Command" = "Comando";
|
||||
|
||||
/* VoiceOver name of the Option modifier key */
|
||||
"Option" = "Opção";
|
||||
|
||||
/* VoiceOver name of the Shift modifier key */
|
||||
"Shift" = "Maiúsculas";
|
||||
|
||||
/* VoiceOver name of the Control modifier key */
|
||||
"Control" = "Controle";
|
||||
|
||||
/* VoiceOver name of the modifier keys delimiter */
|
||||
"-" = "-";
|
||||
@@ -1,56 +0,0 @@
|
||||
/* Literal for the Space key */
|
||||
"Space" = "Spațiu";
|
||||
|
||||
/* VoiceOver type of the object recorded by the control */
|
||||
"Shortcut" = "Scurtătură";
|
||||
|
||||
/* VoiceOver value for the value-less control */
|
||||
"Empty" = "Gol";
|
||||
|
||||
/* Tooltip for the cancel button when the control has a value */
|
||||
"Use old shortcut" = "Folosește vechea scurtătură";
|
||||
|
||||
/* Tooltip for the cancel button when the control has no value */
|
||||
"Cancel recording" = "Anulați înregistrarea";
|
||||
|
||||
/* Tooltip for the clear button */
|
||||
"Delete current shortcut" = "Ștergeți scurtătură curente";
|
||||
|
||||
/* Tooltip for the recording mode */
|
||||
"Type shortcut" = "Tastează scurtătură";
|
||||
|
||||
/* Label for the value-less control */
|
||||
"Record Shortcut" = "Înregistra scurtătură";
|
||||
|
||||
/* Tooltip for the normal mode */
|
||||
"Click to record shortcut" = "Apasă pentru a înregistra scurtătură";
|
||||
|
||||
/* Value for the NSLocalizedFailureReasonErrorKey key of validator's error */
|
||||
"The \"%@\" shortcut can't be used!" = "Combinația de taste “%@“ nu poate fi utilizată!";
|
||||
|
||||
/* Value for the NSLocalizedDescriptionKey key of validator's delegate error */
|
||||
"The \"%@\" shortcut can't be used because %@." = "Combinația de taste “%@“ nu poate fi utilizată din cauza %@.";
|
||||
|
||||
/* Default value for the NSLocalizedDescriptionKey key of validator's error */
|
||||
"The \"%@\" shortcut is already in use." = "Combinația de taste “%@“ este deja în uz.";
|
||||
|
||||
/* Value for the NSLocalizedDescriptionKey key of validator's system shortcuts error */
|
||||
"The \"%@\" shortcut can't be used because it's already used by a system-wide keyboard shortcut. If you really want to use this shortcut, most shortcuts can be changed in the Keyboard panel in System Preferences." = "Combinaţia de taste “%@“ nu poate fi utilizată, deoarece aceasta este deja utilizată de către sistem de operare. Dacă chiar vrei să utilizezi această combinaţie de taste, cele mai multe comenzi rapide pot fi schimbate în panoul-Tastatură şi Maus-în Preferințe Sistem.";
|
||||
|
||||
/* Value for the NSLocalizedDescriptionKey key of validator's menu error */
|
||||
"The \"%@\" shortcut can't be used because it's already used by the \"%@\" menu item." = "Combinaţie de taste “%@“ nu poate fi utilizată, deoarece este deja utilizată de meniul “%@“.";
|
||||
|
||||
/* Accessibility name of the Command modifier key */
|
||||
"Command" = "Comandă";
|
||||
|
||||
/* VoiceOver name of the Option modifier key */
|
||||
"Option" = "Opțiune";
|
||||
|
||||
/* VoiceOver name of the Shift modifier key */
|
||||
"Shift" = "Shift";
|
||||
|
||||
/* VoiceOver name of the Control modifier key */
|
||||
"Control" = "Control";
|
||||
|
||||
/* VoiceOver name of the modifier keys delimiter */
|
||||
"-" = "-";
|
||||
@@ -1,56 +0,0 @@
|
||||
/* Literal for the Space key */
|
||||
"Space" = "Medzera";
|
||||
|
||||
/* VoiceOver type of the object recorded by the control */
|
||||
"Shortcut" = "Skratku";
|
||||
|
||||
/* VoiceOver value for the value-less control */
|
||||
"Empty" = "Prázdny";
|
||||
|
||||
/* Tooltip for the cancel button when the control has a value */
|
||||
"Use old shortcut" = "Použiť pôvodnú skratku";
|
||||
|
||||
/* Tooltip for the cancel button when the control has no value */
|
||||
"Cancel recording" = "Zrušte nahrávanie";
|
||||
|
||||
/* Tooltip for the clear button */
|
||||
"Delete current shortcut" = "Odstrániť aktuálne skratku";
|
||||
|
||||
/* Tooltip for the recording mode */
|
||||
"Type shortcut" = "Napísať skratku";
|
||||
|
||||
/* Label for the value-less control */
|
||||
"Record Shortcut" = "Nahrať skratku";
|
||||
|
||||
/* Tooltip for the normal mode */
|
||||
"Click to record shortcut" = "Kliknite pre nahratie skratky";
|
||||
|
||||
/* Value for the NSLocalizedFailureReasonErrorKey key of validator's error */
|
||||
"The \"%@\" shortcut can't be used!" = "Kombináciu kláves „%@“ nie je možné použiť!";
|
||||
|
||||
/* Value for the NSLocalizedDescriptionKey key of validator's delegate error */
|
||||
"The \"%@\" shortcut can't be used because %@." = "Kombináciu kláves „%@“ nie je možné použiť z dôvodu: %@.";
|
||||
|
||||
/* Default value for the NSLocalizedDescriptionKey key of validator's error */
|
||||
"The \"%@\" shortcut is already in use." = "Combinația de taste „%@“ sa už používa.";
|
||||
|
||||
/* Value for the NSLocalizedDescriptionKey key of validator's system shortcuts error */
|
||||
"The \"%@\" shortcut can't be used because it's already used by a system-wide keyboard shortcut. If you really want to use this shortcut, most shortcuts can be changed in the Keyboard panel in System Preferences." = "Kombináciu kláves „%@“ nie je možné použiť pretože sa používa ako systémová skratka. Ak chcete použiť túto kombináciu kláves, je možné väčšinu klávesových skratiek zmeniť v Systémových nastaveniach v panely Klávesnica";
|
||||
|
||||
/* Value for the NSLocalizedDescriptionKey key of validator's menu error */
|
||||
"The \"%@\" shortcut can't be used because it's already used by the \"%@\" menu item." = "Kombináciu kláves „%@“ nie je možné použiť pretože je používaná pre položku menu „%@“.";
|
||||
|
||||
/* Accessibility name of the Command modifier key */
|
||||
"Command" = "Command";
|
||||
|
||||
/* VoiceOver name of the Option modifier key */
|
||||
"Option" = "Option";
|
||||
|
||||
/* VoiceOver name of the Shift modifier key */
|
||||
"Shift" = "Shift";
|
||||
|
||||
/* VoiceOver name of the Control modifier key */
|
||||
"Control" = "Control";
|
||||
|
||||
/* VoiceOver name of the modifier keys delimiter */
|
||||
"-" = "-";
|
||||
-61
@@ -1,61 +0,0 @@
|
||||
Copyright (c) 2006-2013 Andy Matuschak.
|
||||
Copyright (c) 2009-2013 Elgato Systems GmbH.
|
||||
Copyright (c) 2011-2014 Kornel Lesiński.
|
||||
Copyright (c) 2015-2017 Mayur Pawashe.
|
||||
Copyright (c) 2014 C.W. Betts.
|
||||
Copyright (c) 2014 Petroules Corporation.
|
||||
Copyright (c) 2014 Big Nerd Ranch.
|
||||
All rights reserved.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in
|
||||
the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
=================
|
||||
EXTERNAL LICENSES
|
||||
=================
|
||||
|
||||
bspatch.c and bsdiff.c, from bsdiff 4.3 <http://www.daemonology.net/bsdiff/>:
|
||||
Copyright (c) 2003-2005 Colin Percival.
|
||||
|
||||
sais.c and sais.c, from sais-lite (2010/08/07) <https://sites.google.com/site/yuta256/sais>:
|
||||
Copyright (c) 2008-2010 Yuta Mori.
|
||||
|
||||
SUDSAVerifier.m:
|
||||
Copyright (c) 2011 Mark Hamlin.
|
||||
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted providing that the following conditions
|
||||
are met:
|
||||
1. Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
2. Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in the
|
||||
documentation and/or other materials provided with the distribution.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
|
||||
IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
|
||||
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
||||
OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
||||
HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
|
||||
STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
|
||||
IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
POSSIBILITY OF SUCH DAMAGE.
|
||||
-20
@@ -1,20 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>English</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>com.apple.xcode.dsym.org.sparkle-project.Sparkle</string>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>dSYM</string>
|
||||
<key>CFBundleSignature</key>
|
||||
<string>????</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>1.23.1</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>1.23.1</string>
|
||||
</dict>
|
||||
</plist>
|
||||
BIN
Binary file not shown.
-1
@@ -1 +0,0 @@
|
||||
Versions/Current/Headers
|
||||
-1
@@ -1 +0,0 @@
|
||||
Versions/Current/Modules
|
||||
-1
@@ -1 +0,0 @@
|
||||
Versions/Current/PrivateHeaders
|
||||
-1
@@ -1 +0,0 @@
|
||||
Versions/Current/Resources
|
||||
Loaded 100 of 1676 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user