Compare commits

..
139 Commits
Author SHA1 Message Date
toxblh cd75be6c26 v0.14 2018-04-30 22:17:04 +01:00
Anton Palgunov 7646c6e89d Merge pull request #58 from ReDetection/bordered-parameter
WIP: Bordered parameter, background parameter
2018-04-30 19:20:07 +01:00
toxblh 9c970aff63 if button have only image, change position to .imageonly 2018-04-30 19:19:33 +01:00
Serg 49d7333d5e implement highlight for buttons with no border 2018-04-30 23:18:59 +07:00
Serg cd69bc341c fix margin for some buttons 2018-04-30 22:54:15 +07:00
Serg 453895f395 fix wrong title while first execution 2018-04-30 18:53:32 +07:00
Serg 12796d6387 fix for colors in non-bordered buttons 2018-04-30 18:40:26 +07:00
Anton Palgunov f65e72faed Merge pull request #57 from Toxblh/revert-56-master
Revert "tapAction and longTapAction in config now have nested values"
2018-04-30 11:29:52 +01:00
Anton Palgunov df535b2893 Revert "tapAction and longTapAction in config now have nested values" 2018-04-30 11:29:42 +01:00
Anton Palgunov 574ca0fb84 Merge pull request #56 from ad/master
tapAction and longTapAction in config now have nested values
2018-04-30 11:08:28 +01:00
ad 4a03ba79ec + background option for button
# Conflicts:
#	MTMR/CustomButtonTouchBarItem.swift
#	MTMR/ItemsParsing.swift
2018-04-30 15:43:55 +07:00
ad 907b79965d + hexColor 2018-04-30 15:26:29 +07:00
Serg fbec803d47 introduce bordered parameter, restore 0.13 borders 2018-04-30 15:14:27 +07:00
ad 3f9574245a more constants from ev_keymap.h 2018-04-30 10:15:07 +03:00
ad 2bf6b90b04 * fix for brightness 2018-04-30 10:11:22 +03:00
ad 9d596c6adc Merge remote-tracking branch 'upstream/master' 2018-04-30 09:51:42 +03:00
ad 175e9edb8c Merge branch 'nestedActions' 2018-04-30 09:51:26 +03:00
ad 8e58bb179a tap and longTap 2018-04-30 09:50:59 +03:00
Anton Palgunov d37d9607d3 Merge pull request #55 from ad/master
deinit inputsource observer, button highlight on touch
2018-04-29 23:00:07 +01:00
ad 6b553c4d0d + deinit inputsource observer 2018-04-30 00:57:32 +03:00
ad 788927edb3 + highlighting button background on press 2018-04-30 00:57:09 +03:00
ad 9013779f71 Merge remote-tracking branch 'upstream/master' 2018-04-29 22:51:34 +03:00
toxblh a85507c544 upd Readme, upd Default 2018-04-29 19:44:58 +01:00
toxblh 59aa113995 build.sh upd 2018-04-29 19:27:01 +01:00
Anton Palgunov 9c5a19925b Merge pull request #54 from Toxblh/fix-leaked-items-and-timers
Fix leaking timers
2018-04-29 18:19:55 +01:00
Serg 2aa602d77d recreate touchbar to clear it's cache (items survive otherwise, still doing their job) 2018-04-29 23:37:09 +07:00
ad a1b3f13902 Merge remote-tracking branch 'upstream/master' 2018-04-29 14:59:13 +03:00
Anton Palgunov 60384561d2 Merge pull request #53 from Toxblh/fix-warnings
Fix compiler warnings
2018-04-29 12:55:00 +01:00
Serg 77938340d6 fix compiler warnings 2018-04-29 18:03:30 +07:00
ad 93d2f496d0 * first working 2018-04-29 01:18:05 +03:00
ad 0808f0617a * fix 2018-04-28 02:28:09 +03:00
ad 966a924183 Merge remote-tracking branch 'upstream/master' 2018-04-28 02:23:57 +03:00
ad a648402d04 Merge branch 'master' of https://github.com/ad/MTMR 2018-04-28 02:22:24 +03:00
ad 207af6468e * button highlight on touch 2018-04-28 02:22:05 +03:00
Anton Palgunov 6609a7c806 Merge pull request #49 from ad/master
* NSButtonCell
2018-04-27 23:39:02 +01:00
toxblh 479190d12b fix center line for apple script 2018-04-27 23:38:13 +01:00
ad 5ed1790601 * scrollView baseline fix 2018-04-27 21:53:27 +03:00
toxblh b95f5af95f lead position for image in image+title button 2018-04-27 19:13:55 +01:00
ad c76027ca9d * fix for button.image size 2018-04-27 20:18:22 +03:00
ad 55dc551516 * fix for longAction 2018-04-27 19:17:19 +03:00
ad cf5d8bf546 * staticButton image size 2018-04-27 19:03:58 +03:00
ad 8031108fc9 * font baseline like in original touchbar, styled battery widget (time showing as superscript) 2018-04-27 18:46:29 +03:00
ad d1a577868b * button background 2018-04-27 16:17:40 +03:00
ad 5f02bb2962 * font style 2018-04-27 14:32:07 +03:00
ad cc1332dd02 * button font color 2018-04-27 14:05:13 +03:00
ad a0dbfca828 * input source widget image size 2018-04-27 12:55:54 +03:00
ad 76bdf12745 * fixed switching input source 2018-04-26 22:50:38 +03:00
ad e1268d0d65 * NSButtonCell 2018-04-26 22:36:12 +03:00
toxblh 9c1e5f5032 Merge branch 'master' of https://github.com/Toxblh/MTMR 2018-04-26 14:54:36 +01:00
toxblh 88cde9256a v0.13.2 2018-04-26 14:54:24 +01:00
Anton Palgunov d974f92e3d brew instruction 2018-04-25 13:48:39 +01:00
toxblh 4b8ef1cd96 Merge remote-tracking branch 'origin/master' 2018-04-24 22:54:12 +01:00
toxblh e65c57dbaa If level a battery less 10% it will be red. 2018-04-24 22:53:56 +01:00
Anton Palgunov babc8451e0 Merge pull request #48 from jstart/patch-1
Spelling :)
2018-04-24 22:36:20 +01:00
Christopher Truman 7698ea1c8e Spelling :) 2018-04-24 14:10:18 -07:00
Anton Palgunov aa95d1a1bd Merge pull request #46 from ad/master
* trying to terminate app before forceTerminate (to prevent loosing of unsaved data)
2018-04-24 13:36:55 +01:00
ad 3fe6c22068 * trying to terminate app before forceTerminate (to prevent loosing of unsaved data) 2018-04-24 13:19:22 +03:00
toxblh 5ab19ee27b v0.13.1 2018-04-24 11:14:29 +01:00
Anton Palgunov ae4b0aa870 Merge pull request #45 from ad/master
Allow multiple instances of currency and weather widgets (different schedulers for one type widget)
2018-04-24 10:59:22 +01:00
ad 1e2043ebda * allow multiple instances of currency and weather widgets (different schedulers for one type widget) 2018-04-24 12:49:36 +03:00
Anton Palgunov 409b8d8b46 Merge pull request #44 from ad/master
* dock widget improvements
2018-04-24 10:47:34 +01:00
ad 8bccbf3834 * dock widget improvements 2018-04-23 20:09:05 +03:00
toxblh 1b5ae00493 v0.13 2018-04-23 13:09:48 +01:00
toxblh 22c315d631 Parse JSON with comments. 2018-04-23 13:08:55 +01:00
toxblh ffbbafa9d9 Fixed central sort. Close #33 2018-04-23 00:54:29 +01:00
toxblh 719a212913 disable parse with comments now, need improve speed loading for JSONC 2018-04-22 23:34:32 +01:00
toxblh 651d883672 + delete button. Close #37 2018-04-22 23:31:05 +01:00
Anton Palgunov 9853e0448f Merge pull request #41 from ad/master
Switch input source widget
2018-04-22 19:38:22 +01:00
ad 0f41638d0f * file moved to new location 2018-04-22 19:28:23 +03:00
ad f6e5b3ca09 Merge branch 'inputSourceWidget' 2018-04-22 19:25:49 +03:00
ad 04b821510b + switch input source by tap 2018-04-22 19:22:45 +03:00
ad d632bd3ee7 + show input source 2018-04-22 17:55:20 +03:00
toxblh aa341b818f reorginize project 2018-04-22 15:20:00 +01:00
Anton Palgunov a513c0083a Merge pull request #40 from ad/replaceTimers
Weather and Currency timers replaced with NSBackgroundActivityScheduler (more energy efficiency)
2018-04-22 11:42:23 +01:00
ad 683cc4c9f4 Weather and Currency timers replaced with NSBackgroundActivityScheduler (more energy efficiency) 2018-04-22 13:38:16 +03:00
Anton Palgunov 3c171b449a Merge pull request #39 from ad/stripComments
+ strip comments in config file
2018-04-22 10:17:34 +01:00
ad b4f62f6be1 + strip comments in config file 2018-04-22 12:15:04 +03:00
Anton Palgunov 8a9b40a030 Merge pull request #38 from ad/master
+ persistent app in dock (press for 0.5 sec), close app (press for 2.0 sec). Reload view if default config changed (write event fired)
2018-04-21 20:16:59 +01:00
ad 3375308386 + reload if default config changed (write event fired) 2018-04-21 20:50:22 +03:00
ad 2fc9dbccd8 Merge remote-tracking branch 'upstream/master' 2018-04-21 17:22:19 +03:00
ad 051a303f96 + persistent app in dock (press for 0.5 sec), close app (press for 2.0 sec) 2018-04-21 16:40:43 +03:00
Anton Palgunov 41a859aea3 Merge pull request #32 from ad/master
Background color parameter moved to CustomButtonTouchBarItem
2018-04-21 13:16:36 +01:00
ad bb978f2de5 Background color parameter moved to CustomButtonTouchBarItem for future ability to change from config file 2018-04-21 15:01:09 +03:00
Anton Palgunov 9bf42dfb79 Merge pull request #31 from ad/master
Action for battery plugin
2018-04-21 12:08:01 +01:00
ad 641eb18b89 + longAction (action for long press on widgets, now working only with longUrl). TODO: nested options for action/longAction like for image/sourse/actionAppleScript 2018-04-21 10:20:12 +03:00
ad d76e16b9e6 * action for battery plugin 2018-04-21 08:27:02 +03:00
ad 9abb17ee76 Merge remote-tracking branch 'upstream/master' 2018-04-21 08:19:44 +03:00
toxblh 4b39b000e0 Upd default. Readme. Fixed reload for central scroller. 2018-04-21 01:43:37 +01:00
toxblh 9b86ed1dee update Readme 2018-04-21 00:23:48 +01:00
toxblh 7874433d9b Added Native Battery 2018-04-21 00:22:45 +01:00
toxblh d4108d848e merge stash 2018-04-20 22:57:51 +01:00
toxblh f78f82893a merged master 2018-04-20 22:56:47 +01:00
toxblh 05eabdbd0a Added Button mute { "type": "mute", "width": 40, "align": "right" }, #23 2018-04-20 22:54:38 +01:00
toxblh 4109f6efd4 delete .DS_Store 2018-04-20 22:50:36 +01:00
ad bbbbf5b5a4 Merge remote-tracking branch 'upstream/master' 2018-04-20 21:01:42 +03:00
Anton Palgunov 8513ed0fa3 Merge pull request #28 from Toxblh/openUrl
Open url action
2018-04-20 18:42:23 +01:00
ad 0e2a958b5c Merge remote-tracking branch 'upstream/master' 2018-04-20 16:26:28 +03:00
Daniel Apatin ac234aa4bf Fix for temperature value with decimals 2018-04-20 16:24:00 +03:00
Daniel Apatin e6d84888ba Update TouchBarController.swift 2018-04-20 15:21:30 +03:00
Daniel Apatin 2aecb2ffb3 Update ItemsParsing.swift 2018-04-20 15:20:01 +03:00
ad cd8b595552 - 2018-04-20 14:42:36 +03:00
ad 76934e6ff0 + action for currency, weather, battery
It will allow to process custom action on click by widget
2018-04-20 14:26:26 +03:00
Daniel Apatin 3e35e03bfb + action for currency, weather, battery
It will allow to process custom action on click by widget
2018-04-20 14:25:31 +03:00
ad 6f3425f4c3 Merge branch 'master' into actionURL 2018-04-20 14:20:24 +03:00
ad 0c007b7940 + action openURL 2018-04-20 13:53:25 +03:00
ad a7060b74e4 * weather and currency now is CustomButtonTouchBarItem 2018-04-20 13:42:08 +03:00
Daniel Apatin 92c4eed4ed Update WeatherBarItem.swift 2018-04-20 13:35:41 +03:00
Daniel Apatin 94d519cd65 Update CurrencyBarItem.swift 2018-04-20 13:35:21 +03:00
Daniel Apatin 7341136897 Update TouchBarController.swift 2018-04-20 13:34:56 +03:00
Daniel Apatin 4459a95691 Merge pull request #27 from Toxblh/ad-patch-weather
+ text and image sets for weather condition  ad committed
2018-04-20 11:19:52 +03:00
Daniel Apatin 7bc8458d8b Update WeatherBarItem.swift 2018-04-20 11:18:11 +03:00
Daniel Apatin bfc4dcf22c Update ItemsParsing.swift 2018-04-20 11:17:29 +03:00
Daniel Apatin 4eb14ea166 Update TouchBarController.swift 2018-04-20 11:16:50 +03:00
ad c15488b61f + text and image sets for weather condition 2018-04-20 11:13:03 +03:00
toxblh 33bb3b8d65 Merge remote-tracking branch 'origin/master' into batteryButton 2018-04-19 20:53:41 +01:00
Daniel Apatin c5a7a3142b Fixed currency formatting (trailing zero), added colours 2018-04-19 21:54:12 +03:00
ad 5b08861dec * fixed formatting (trailing zero), added colours 2018-04-19 21:50:06 +03:00
ad 82f192a5c0 Merge branch 'weatherWidget' 2018-04-19 19:01:22 +03:00
ad 9230b1c3e6 Merge branch 'currencyWidget' 2018-04-19 19:01:14 +03:00
ad 647e0accea Merge branch 'openPresetFile' 2018-04-19 19:01:04 +03:00
Anton Palgunov 0ce6648bf9 Merge pull request #26 from ad/master
fixed deminiaturization and Finder opening
2018-04-19 16:59:36 +01:00
ad fe3e647b56 Merge remote-tracking branch 'upstream/master' 2018-04-19 18:54:14 +03:00
ad fe07ea3b46 * fixed deminiaturization and Finder opening 2018-04-19 18:53:29 +03:00
toxblh 4b22514969 v0.10 - added open preset, better work reload 2018-04-19 16:23:27 +01:00
Anton Palgunov 03bcafd5ff Merge pull request #25 from ad/openPresetFile
+ open preset from dialog (not replacing default file)
2018-04-19 16:19:45 +01:00
ad fdd30413c4 * open dialog at default configs directory 2018-04-19 18:18:54 +03:00
ad 3a57269510 + open preset from dialog (not replacing default file) 2018-04-19 18:01:16 +03:00
Anton Palgunov e9de35ee37 Merge pull request #24 from ad/currencyWidget
* currency list
2018-04-19 12:35:43 +01:00
Anton Palgunov e027c640f7 Merge pull request #22 from ad/weatherWidget
Weather widget / volume slider fix / dock shadow
2018-04-19 12:27:44 +01:00
ad 242e2d3c22 * currency list 2018-04-18 22:14:10 +03:00
Daniel Apatin b3500684d4 Merge pull request #1 from ad/currencyWidget
+ currency widget
2018-04-18 19:53:04 +03:00
ad 42c1ff27c4 + currency widget
{
        "type": "currency",
        "refreshInterval": 600,
        "align": "right",
        "from": "USD",
        "to": "RUB"
    },
2018-04-18 19:50:23 +03:00
ad 4c16321a4a + weather widget 2018-04-18 18:27:28 +03:00
ad adaed36fbd Merge remote-tracking branch 'upstream/master' 2018-04-18 16:20:36 +03:00
toxblh f724a0d355 Draft. Has issues 2018-04-18 09:51:22 +01:00
toxblh 6b2e06b967 update readme 2018-04-17 18:00:59 +01:00
toxblh 4663eb9b27 v0.9 dock plugin 2018-04-17 09:35:24 +01:00
ad 7774b1fa5e * add shadow to app scrubber 2018-04-17 10:55:21 +03:00
ad 20699e9de7 * removed unused code 2018-04-16 23:42:44 +03:00
27 changed files with 1405 additions and 253 deletions
Vendored
BIN
View File
Binary file not shown.
+2
View File
@@ -1,3 +1,5 @@
.DS_Store
# Xcode # Xcode
# #
# gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore
+36 -12
View File
@@ -18,7 +18,11 @@
6027D1BA2080E52A004FFDC7 /* VolumeViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6027D1B82080E52A004FFDC7 /* VolumeViewController.swift */; }; 6027D1BA2080E52A004FFDC7 /* VolumeViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6027D1B82080E52A004FFDC7 /* VolumeViewController.swift */; };
6042B6A72083E03A00C525C8 /* AppScrubberTouchBarItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6042B6A62083E03A00C525C8 /* AppScrubberTouchBarItem.swift */; }; 6042B6A72083E03A00C525C8 /* AppScrubberTouchBarItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6042B6A62083E03A00C525C8 /* AppScrubberTouchBarItem.swift */; };
6042B6AA2083E27000C525C8 /* DeprecatedCarbonAPI.c in Sources */ = {isa = PBXBuildFile; fileRef = 6042B6A92083E27000C525C8 /* DeprecatedCarbonAPI.c */; }; 6042B6AA2083E27000C525C8 /* DeprecatedCarbonAPI.c in Sources */ = {isa = PBXBuildFile; fileRef = 6042B6A92083E27000C525C8 /* DeprecatedCarbonAPI.c */; };
607EEA4B2087835F009DA5F0 /* WeatherBarItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 607EEA4A2087835F009DA5F0 /* WeatherBarItem.swift */; };
607EEA4D2087A8DA009DA5F0 /* CurrencyBarItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 607EEA4C2087A8DA009DA5F0 /* CurrencyBarItem.swift */; };
60F7D454208CC31400ABF5D2 /* InputSourceBarItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 60F7D453208CC31400ABF5D2 /* InputSourceBarItem.swift */; };
B0008E552080286C003AD4DD /* SupportHelpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = B0008E542080286C003AD4DD /* SupportHelpers.swift */; }; B0008E552080286C003AD4DD /* SupportHelpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = B0008E542080286C003AD4DD /* SupportHelpers.swift */; };
B04B7BB72087398C00C835D0 /* BatteryBarItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = B04B7BB62087398C00C835D0 /* BatteryBarItem.swift */; };
B05600D32083E9BB00EB218D /* CustomSlider.swift in Sources */ = {isa = PBXBuildFile; fileRef = B05600D22083E9BB00EB218D /* CustomSlider.swift */; }; B05600D32083E9BB00EB218D /* CustomSlider.swift in Sources */ = {isa = PBXBuildFile; fileRef = B05600D22083E9BB00EB218D /* CustomSlider.swift */; };
B059D622205E03F5006E6B86 /* TouchBarController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B059D621205E03F5006E6B86 /* TouchBarController.swift */; }; B059D622205E03F5006E6B86 /* TouchBarController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B059D621205E03F5006E6B86 /* TouchBarController.swift */; };
B059D624205E04F3006E6B86 /* CustomButtonTouchBarItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = B059D623205E04F3006E6B86 /* CustomButtonTouchBarItem.swift */; }; B059D624205E04F3006E6B86 /* CustomButtonTouchBarItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = B059D623205E04F3006E6B86 /* CustomButtonTouchBarItem.swift */; };
@@ -66,7 +70,11 @@
6042B6A62083E03A00C525C8 /* AppScrubberTouchBarItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppScrubberTouchBarItem.swift; sourceTree = "<group>"; }; 6042B6A62083E03A00C525C8 /* AppScrubberTouchBarItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppScrubberTouchBarItem.swift; sourceTree = "<group>"; };
6042B6A82083E1F500C525C8 /* DeprecatedCarbonAPI.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DeprecatedCarbonAPI.h; sourceTree = "<group>"; }; 6042B6A82083E1F500C525C8 /* DeprecatedCarbonAPI.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DeprecatedCarbonAPI.h; sourceTree = "<group>"; };
6042B6A92083E27000C525C8 /* DeprecatedCarbonAPI.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = DeprecatedCarbonAPI.c; sourceTree = "<group>"; }; 6042B6A92083E27000C525C8 /* DeprecatedCarbonAPI.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = DeprecatedCarbonAPI.c; sourceTree = "<group>"; };
607EEA4A2087835F009DA5F0 /* WeatherBarItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WeatherBarItem.swift; sourceTree = "<group>"; };
607EEA4C2087A8DA009DA5F0 /* CurrencyBarItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CurrencyBarItem.swift; sourceTree = "<group>"; };
60F7D453208CC31400ABF5D2 /* InputSourceBarItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InputSourceBarItem.swift; sourceTree = "<group>"; };
B0008E542080286C003AD4DD /* SupportHelpers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SupportHelpers.swift; sourceTree = "<group>"; }; B0008E542080286C003AD4DD /* SupportHelpers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SupportHelpers.swift; sourceTree = "<group>"; };
B04B7BB62087398C00C835D0 /* BatteryBarItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BatteryBarItem.swift; sourceTree = "<group>"; };
B05600D22083E9BB00EB218D /* CustomSlider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomSlider.swift; sourceTree = "<group>"; }; B05600D22083E9BB00EB218D /* CustomSlider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomSlider.swift; sourceTree = "<group>"; };
B059D621205E03F5006E6B86 /* TouchBarController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TouchBarController.swift; sourceTree = "<group>"; }; B059D621205E03F5006E6B86 /* TouchBarController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TouchBarController.swift; sourceTree = "<group>"; };
B059D623205E04F3006E6B86 /* CustomButtonTouchBarItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomButtonTouchBarItem.swift; sourceTree = "<group>"; }; B059D623205E04F3006E6B86 /* CustomButtonTouchBarItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomButtonTouchBarItem.swift; sourceTree = "<group>"; };
@@ -151,26 +159,23 @@
B082B251205C7D8000BC04DC /* MTMR */ = { B082B251205C7D8000BC04DC /* MTMR */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
B0B88A07208CD12000A2C160 /* Widgets */,
B0B1743B207D6ED40004B740 /* CBridge */, B0B1743B207D6ED40004B740 /* CBridge */,
B0B17426207D6AFE0004B740 /* AppleScripts */, B0B17426207D6AFE0004B740 /* AppleScripts */,
B082B252205C7D8000BC04DC /* AppDelegate.swift */,
B082B256205C7D8000BC04DC /* Assets.xcassets */, B082B256205C7D8000BC04DC /* Assets.xcassets */,
B0A7E9A9205D6AA400EEF070 /* KeyPress.swift */,
B059D623205E04F3006E6B86 /* CustomButtonTouchBarItem.swift */,
36C2ECD8207B74B4003CDA33 /* AppleScriptTouchBarItem.swift */,
36C2ECD6207B6DAE003CDA33 /* TimeTouchBarItem.swift */,
B059D621205E03F5006E6B86 /* TouchBarController.swift */,
36C2ECDA207C3FE7003CDA33 /* ItemsParsing.swift */,
B0008E542080286C003AD4DD /* SupportHelpers.swift */,
B082B258205C7D8000BC04DC /* Main.storyboard */, B082B258205C7D8000BC04DC /* Main.storyboard */,
B082B25B205C7D8000BC04DC /* Info.plist */, B082B25B205C7D8000BC04DC /* Info.plist */,
B082B25C205C7D8000BC04DC /* MTMR.entitlements */, B082B25C205C7D8000BC04DC /* MTMR.entitlements */,
B09EB1E3207C082000D5C1E0 /* HapticFeedback.swift */,
36C2ECDF207CB1B0003CDA33 /* defaultPreset.json */, 36C2ECDF207CB1B0003CDA33 /* defaultPreset.json */,
6027D1B72080E52A004FFDC7 /* BrightnessViewController.swift */, B082B252205C7D8000BC04DC /* AppDelegate.swift */,
6027D1B82080E52A004FFDC7 /* VolumeViewController.swift */, B0A7E9A9205D6AA400EEF070 /* KeyPress.swift */,
B059D623205E04F3006E6B86 /* CustomButtonTouchBarItem.swift */,
36C2ECD8207B74B4003CDA33 /* AppleScriptTouchBarItem.swift */,
B059D621205E03F5006E6B86 /* TouchBarController.swift */,
36C2ECDA207C3FE7003CDA33 /* ItemsParsing.swift */,
B0008E542080286C003AD4DD /* SupportHelpers.swift */,
B09EB1E3207C082000D5C1E0 /* HapticFeedback.swift */,
368EDDE620812A1D00E10953 /* ScrollViewItem.swift */, 368EDDE620812A1D00E10953 /* ScrollViewItem.swift */,
6042B6A62083E03A00C525C8 /* AppScrubberTouchBarItem.swift */,
B05600D22083E9BB00EB218D /* CustomSlider.swift */, B05600D22083E9BB00EB218D /* CustomSlider.swift */,
); );
path = MTMR; path = MTMR;
@@ -215,6 +220,21 @@
path = CBridge; path = CBridge;
sourceTree = "<group>"; sourceTree = "<group>";
}; };
B0B88A07208CD12000A2C160 /* Widgets */ = {
isa = PBXGroup;
children = (
36C2ECD6207B6DAE003CDA33 /* TimeTouchBarItem.swift */,
6027D1B72080E52A004FFDC7 /* BrightnessViewController.swift */,
6027D1B82080E52A004FFDC7 /* VolumeViewController.swift */,
B04B7BB62087398C00C835D0 /* BatteryBarItem.swift */,
607EEA4A2087835F009DA5F0 /* WeatherBarItem.swift */,
607EEA4C2087A8DA009DA5F0 /* CurrencyBarItem.swift */,
6042B6A62083E03A00C525C8 /* AppScrubberTouchBarItem.swift */,
60F7D453208CC31400ABF5D2 /* InputSourceBarItem.swift */,
);
path = Widgets;
sourceTree = "<group>";
};
/* End PBXGroup section */ /* End PBXGroup section */
/* Begin PBXNativeTarget section */ /* Begin PBXNativeTarget section */
@@ -340,13 +360,17 @@
B082B253205C7D8000BC04DC /* AppDelegate.swift in Sources */, B082B253205C7D8000BC04DC /* AppDelegate.swift in Sources */,
B059D624205E04F3006E6B86 /* CustomButtonTouchBarItem.swift in Sources */, B059D624205E04F3006E6B86 /* CustomButtonTouchBarItem.swift in Sources */,
6027D1BA2080E52A004FFDC7 /* VolumeViewController.swift in Sources */, 6027D1BA2080E52A004FFDC7 /* VolumeViewController.swift in Sources */,
607EEA4B2087835F009DA5F0 /* WeatherBarItem.swift in Sources */,
6042B6AA2083E27000C525C8 /* DeprecatedCarbonAPI.c in Sources */, 6042B6AA2083E27000C525C8 /* DeprecatedCarbonAPI.c in Sources */,
B09EB1E4207C082000D5C1E0 /* HapticFeedback.swift in Sources */, B09EB1E4207C082000D5C1E0 /* HapticFeedback.swift in Sources */,
60F7D454208CC31400ABF5D2 /* InputSourceBarItem.swift in Sources */,
36C2ECDB207C3FE7003CDA33 /* ItemsParsing.swift in Sources */, 36C2ECDB207C3FE7003CDA33 /* ItemsParsing.swift in Sources */,
B0A7E9AA205D6AA400EEF070 /* KeyPress.swift in Sources */, B0A7E9AA205D6AA400EEF070 /* KeyPress.swift in Sources */,
36C2ECD7207B6DAE003CDA33 /* TimeTouchBarItem.swift in Sources */, 36C2ECD7207B6DAE003CDA33 /* TimeTouchBarItem.swift in Sources */,
607EEA4D2087A8DA009DA5F0 /* CurrencyBarItem.swift in Sources */,
6027D1B92080E52A004FFDC7 /* BrightnessViewController.swift in Sources */, 6027D1B92080E52A004FFDC7 /* BrightnessViewController.swift in Sources */,
368EDDE720812A1D00E10953 /* ScrollViewItem.swift in Sources */, 368EDDE720812A1D00E10953 /* ScrollViewItem.swift in Sources */,
B04B7BB72087398C00C835D0 /* BatteryBarItem.swift in Sources */,
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
}; };
@@ -0,0 +1,8 @@
<?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>BuildSystemType</key>
<string>Latest</string>
</dict>
</plist>
+50 -2
View File
@@ -11,19 +11,21 @@ import Cocoa
@NSApplicationMain @NSApplicationMain
class AppDelegate: NSObject, NSApplicationDelegate { class AppDelegate: NSObject, NSApplicationDelegate {
let statusItem = NSStatusBar.system.statusItem(withLength:NSStatusItem.squareLength) let statusItem = NSStatusBar.system.statusItem(withLength:NSStatusItem.squareLength)
private var fileSystemSource: DispatchSourceFileSystemObject?
func applicationDidFinishLaunching(_ aNotification: Notification) { func applicationDidFinishLaunching(_ aNotification: Notification) {
// Insert code here to initialize your application
TouchBarController.shared.setupControlStripPresence() TouchBarController.shared.setupControlStripPresence()
if let button = statusItem.button { if let button = statusItem.button {
button.image = #imageLiteral(resourceName: "StatusImage") button.image = #imageLiteral(resourceName: "StatusImage")
} }
createMenu() createMenu()
reloadOnDefaultConfigChanged()
} }
func applicationWillTerminate(_ aNotification: Notification) { func applicationWillTerminate(_ aNotification: Notification) {
// Insert code here to tear down your application
} }
@objc func openPrefereces(_ sender: Any?) { @objc func openPrefereces(_ sender: Any?) {
@@ -39,14 +41,60 @@ class AppDelegate: NSObject, NSApplicationDelegate {
TouchBarController.shared.createAndUpdatePreset() TouchBarController.shared.createAndUpdatePreset()
} }
@objc func openPreset(_ sender: Any?) {
let dialog = NSOpenPanel();
dialog.title = "Choose a items.json file"
dialog.showsResizeIndicator = true
dialog.showsHiddenFiles = true
dialog.canChooseDirectories = false
dialog.canCreateDirectories = false
dialog.allowsMultipleSelection = false
dialog.allowedFileTypes = ["json"]
dialog.directoryURL = NSURL.fileURL(withPath: NSSearchPathForDirectoriesInDomains(.applicationSupportDirectory, .userDomainMask, true).first!.appending("/MTMR"), isDirectory: true)
if (dialog.runModal() == NSApplication.ModalResponse.OK) {
let result = dialog.url
if (result != nil) {
let path = result!.path
let jsonData = path.fileData
let jsonItems = jsonData?.barItemDefinitions() ?? [BarItemDefinition(type: .staticButton(title: "bad preset"), action: .none, longAction: .none, additionalParameters: [:])]
TouchBarController.shared.createAndUpdatePreset(jsonItems: jsonItems)
}
}
}
func createMenu() { func createMenu() {
let menu = NSMenu() let menu = NSMenu()
menu.addItem(withTitle: "Preferences", action: #selector(openPrefereces(_:)), keyEquivalent: ",") menu.addItem(withTitle: "Preferences", action: #selector(openPrefereces(_:)), keyEquivalent: ",")
menu.addItem(withTitle: "Reload Preset", action: #selector(updatePreset(_:)), keyEquivalent: "r") menu.addItem(withTitle: "Reload Preset", action: #selector(updatePreset(_:)), keyEquivalent: "r")
menu.addItem(withTitle: "Open Preset", action: #selector(openPreset(_:)), keyEquivalent: "O")
menu.addItem(NSMenuItem.separator()) menu.addItem(NSMenuItem.separator())
menu.addItem(withTitle: "Quit", action: #selector(NSApplication.terminate(_:)), keyEquivalent: "q") menu.addItem(withTitle: "Quit", action: #selector(NSApplication.terminate(_:)), keyEquivalent: "q")
statusItem.menu = menu statusItem.menu = menu
} }
func reloadOnDefaultConfigChanged() {
let file = NSURL.fileURL(withPath: NSSearchPathForDirectoriesInDomains(.applicationSupportDirectory, .userDomainMask, true).first!.appending("/MTMR/items.json"))
let fd = open(file.path, O_EVTONLY)
self.fileSystemSource = DispatchSource.makeFileSystemObjectSource(fileDescriptor: fd, eventMask: .write, queue: DispatchQueue(label: "DefaultConfigChanged"))
self.fileSystemSource?.setEventHandler(handler: {
print("Config changed, reloading...")
DispatchQueue.main.async {
TouchBarController.shared.createAndUpdatePreset()
}
})
self.fileSystemSource?.setCancelHandler(handler: {
close(fd)
})
self.fileSystemSource?.resume()
}
} }
-118
View File
@@ -1,118 +0,0 @@
//
// AppScrubberTouchBarItem.swift
//
// This file is part of TouchDock
// Copyright (C) 2017 Xander Deng
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//
import Cocoa
class AppScrubberTouchBarItem: NSCustomTouchBarItem, NSScrubberDelegate, NSScrubberDataSource {
var scrubber: NSScrubber!
var runningApplications: [NSRunningApplication] = []
override init(identifier: NSTouchBarItem.Identifier) {
super.init(identifier: identifier)
scrubber = NSScrubber();
scrubber.delegate = self
scrubber.dataSource = self
scrubber.mode = .free // .fixed
let layout = NSScrubberFlowLayout();
layout.itemSize = NSSize(width: 44, height: 30)
scrubber.scrubberLayout = layout
scrubber.selectionBackgroundStyle = .roundedBackground
view = scrubber
scrubber.register(NSScrubberImageItemView.self, forItemIdentifier: NSUserInterfaceItemIdentifier(rawValue: "ScrubberApplicationsItemReuseIdentifier"))
NSWorkspace.shared.notificationCenter.addObserver(self, selector: #selector(activeApplicationChanged), name: NSWorkspace.didLaunchApplicationNotification, object: nil)
NSWorkspace.shared.notificationCenter.addObserver(self, selector: #selector(activeApplicationChanged), name: NSWorkspace.didTerminateApplicationNotification, object: nil)
NSWorkspace.shared.notificationCenter.addObserver(self, selector: #selector(activeApplicationChanged), name: NSWorkspace.didActivateApplicationNotification, object: nil)
updateRunningApplication()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
@objc func activeApplicationChanged(n: Notification) {
updateRunningApplication()
}
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
updateRunningApplication()
}
func updateRunningApplication() {
let newApplications = launchedApplications().filter {
!$0.isTerminated && $0.bundleIdentifier != nil
}
let frontmost = NSWorkspace.shared.frontmostApplication
let index = newApplications.index {
$0.processIdentifier == frontmost?.processIdentifier
}
runningApplications = newApplications
scrubber.reloadData()
scrubber.selectedIndex = index ?? 0
}
public func numberOfItems(for scrubber: NSScrubber) -> Int {
return runningApplications.count
}
public func scrubber(_ scrubber: NSScrubber, viewForItemAt index: Int) -> NSScrubberItemView {
let item = scrubber.makeItem(withIdentifier: NSUserInterfaceItemIdentifier(rawValue: "ScrubberApplicationsItemReuseIdentifier"), owner: self) as? NSScrubberImageItemView ?? NSScrubberImageItemView()
item.imageView.imageScaling = .scaleProportionallyDown
if let icon = runningApplications[index].icon {
item.image = icon
}
return item
}
public func didFinishInteracting(with scrubber: NSScrubber) {
runningApplications[scrubber.selectedIndex].activate(options: [ .activateIgnoringOtherApps ])
// NB: if you can't open app which on another space, try to check mark
// "When switching to an application, switch to a Space with open windows for the application"
// in Mission control settings
// TODO: deminiaturize app
// if let info = CGWindowListCopyWindowInfo(.optionOnScreenOnly, kCGNullWindowID) as? [[ String : Any]] {
// for dict in info {
// if dict["kCGWindowOwnerName"] as! String == runningApplications[scrubber.selectedIndex].localizedName {
// print(dict["kCGWindowNumber"], dict["kCGWindowOwnerName"])
// }
// }
// }
}
}
private func launchedApplications() -> [NSRunningApplication] {
let asns = _LSCopyApplicationArrayInFrontToBackOrder(~0)?.takeRetainedValue()
return (0..<CFArrayGetCount(asns)).compactMap { index in
let asn = CFArrayGetValueAtIndex(asns, index)
let pid = pidFromASN(asn)
return NSRunningApplication(processIdentifier: pid)
}
}
+9 -5
View File
@@ -5,9 +5,9 @@ class AppleScriptTouchBarItem: CustomButtonTouchBarItem {
private let interval: TimeInterval private let interval: TimeInterval
private var forceHideConstraint: NSLayoutConstraint! private var forceHideConstraint: NSLayoutConstraint!
init?(identifier: NSTouchBarItem.Identifier, source: SourceProtocol, interval: TimeInterval, onTap: @escaping ()->()) { init?(identifier: NSTouchBarItem.Identifier, source: SourceProtocol, interval: TimeInterval, onTap: @escaping ()->(), onLongTap: @escaping ()->()) {
self.interval = interval self.interval = interval
super.init(identifier: identifier, title: "compile", onTap: onTap) super.init(identifier: identifier, title: "⏳", onTap: onTap, onLongTap: onLongTap)
self.forceHideConstraint = self.view.widthAnchor.constraint(equalToConstant: 0) self.forceHideConstraint = self.view.widthAnchor.constraint(equalToConstant: 0)
guard let script = source.appleScript else { guard let script = source.appleScript else {
button.title = "no script" button.title = "no script"
@@ -18,9 +18,11 @@ class AppleScriptTouchBarItem: CustomButtonTouchBarItem {
DispatchQueue.main.async { DispatchQueue.main.async {
var error: NSDictionary? var error: NSDictionary?
guard script.compileAndReturnError(&error) else { guard script.compileAndReturnError(&error) else {
print(error?.description ?? "unknown error") #if DEBUG
print(error?.description ?? "unknown error")
#endif
DispatchQueue.main.async { DispatchQueue.main.async {
self.button.title = "compile error" self.button.title = "error"
} }
return return
} }
@@ -33,7 +35,9 @@ class AppleScriptTouchBarItem: CustomButtonTouchBarItem {
} }
func refreshAndSchedule() { func refreshAndSchedule() {
print("refresh happened") #if DEBUG
print("refresh happened (interval \(self.interval)), self \(self.identifier.rawValue))")
#endif
let scriptResult = self.execute() let scriptResult = self.execute()
DispatchQueue.main.async { DispatchQueue.main.async {
self.button.title = scriptResult self.button.title = scriptResult
+95 -7
View File
@@ -8,26 +8,114 @@
import Cocoa import Cocoa
class CustomButtonTouchBarItem: NSCustomTouchBarItem { class CustomButtonTouchBarItem: NSCustomTouchBarItem, NSGestureRecognizerDelegate {
let tapClosure: () -> () private let tapClosure: (() -> ())?
private let longTapClosure: (() -> ())?
private(set) var button: NSButton! private(set) var button: NSButton!
init(identifier: NSTouchBarItem.Identifier, title: String, onTap callback: @escaping () -> ()) { private var singleClick: NSClickGestureRecognizer!
private var longClick: NSPressGestureRecognizer!
init(identifier: NSTouchBarItem.Identifier, title: String, onTap callback: @escaping () -> (), onLongTap callbackLong: @escaping () -> (), bezelColor: NSColor? = .clear) {
self.tapClosure = callback self.tapClosure = callback
self.longTapClosure = callbackLong
super.init(identifier: identifier) super.init(identifier: identifier)
button = NSButton(title: title, target: self, action: #selector(didTapped)) button = NSButton(title: title, target: nil, action: nil)
button.cell = CustomButtonCell()
button.isBordered = true
button.bezelStyle = .rounded
button.title = title button.title = title
self.view = button self.view = button
longClick = NSPressGestureRecognizer(target: self, action: #selector(handleGestureLong))
longClick.allowedTouchTypes = .direct
longClick.delegate = self
singleClick = NSClickGestureRecognizer(target: self, action: #selector(handleGestureSingle))
singleClick.allowedTouchTypes = .direct
singleClick.delegate = self
self.view.addGestureRecognizer(longClick)
self.view.addGestureRecognizer(singleClick)
} }
required init?(coder: NSCoder) { required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented") fatalError("init(coder:) has not been implemented")
} }
@objc func didTapped() {
self.tapClosure() func gestureRecognizer(_ gestureRecognizer: NSGestureRecognizer, shouldRequireFailureOf otherGestureRecognizer: NSGestureRecognizer) -> Bool {
if gestureRecognizer == singleClick && otherGestureRecognizer == longClick {
return false
}
return true
}
@objc func handleGestureSingle(gr: NSClickGestureRecognizer) {
let hf: HapticFeedback = HapticFeedback() let hf: HapticFeedback = HapticFeedback()
hf.tap(strong: 6) switch gr.state {
case .ended:
hf.tap(strong: 2)
self.tapClosure?()
break
default:
break
}
}
@objc func handleGestureLong(gr: NSPressGestureRecognizer) {
let hf: HapticFeedback = HapticFeedback()
switch gr.state {
case .began:
if let closure = self.longTapClosure {
hf.tap(strong: 2)
closure()
} else if let closure = self.tapClosure {
hf.tap(strong: 6)
closure()
print("long click")
}
break
default:
break
}
}
}
class CustomButtonCell: NSButtonCell {
init() {
super.init(textCell: "")
}
override func highlight(_ flag: Bool, withFrame cellFrame: NSRect, in controlView: NSView) {
super.highlight(flag, withFrame: cellFrame, in: controlView)
if !self.isBordered {
self.setTitle(self.title, withColor: flag ? .lightGray : .white)
}
}
required init(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override var title: String! {
get {
return self.attributedTitle.string
}
set (newTitle) {
setTitle(newTitle, withColor: .white)
}
}
func setTitle(_ title: String, withColor color: NSColor) {
let attrTitle = NSMutableAttributedString(string: title as String, attributes: [.foregroundColor: color, .font: NSFont.systemFont(ofSize: 15, weight: .regular), .baselineOffset: 1])
attrTitle.setAlignment(.center, range: NSRange(location: 0, length: title.count))
self.attributedTitle = attrTitle
} }
} }
+8 -8
View File
@@ -49,19 +49,19 @@ class CustomSliderCell: NSSliderCell {
override func drawBar(inside aRect: NSRect, flipped: Bool) { override func drawBar(inside aRect: NSRect, flipped: Bool) {
_barRect = aRect _barRect = aRect
var rect = aRect
rect.size.height = CGFloat(4)
let barRadius = CGFloat(2) let barRadius = CGFloat(2)
let value = CGFloat((self.doubleValue - self.minValue) / (self.maxValue - self.minValue))
let finalWidth = CGFloat(value * (self.controlView!.frame.size.width - 12))
var leftRect = rect var bgRect = aRect
leftRect.size.width = finalWidth bgRect.size.height = CGFloat(4)
let bg = NSBezierPath(roundedRect: rect, xRadius: barRadius, yRadius: barRadius)
let bg = NSBezierPath(roundedRect: bgRect, xRadius: barRadius, yRadius: barRadius)
NSColor.lightGray.setFill() NSColor.lightGray.setFill()
bg.fill() bg.fill()
let active = NSBezierPath(roundedRect: leftRect, xRadius: barRadius, yRadius: barRadius) var activeRect = bgRect
activeRect.size.width = CGFloat((Double(bgRect.size.width) / (self.maxValue - self.minValue)) * self.doubleValue)
let active = NSBezierPath(roundedRect: activeRect, xRadius: barRadius, yRadius: barRadius)
NSColor.darkGray.setFill() NSColor.darkGray.setFill()
active.fill() active.fill()
} }
+7 -1
View File
@@ -17,7 +17,7 @@
<key>CFBundlePackageType</key> <key>CFBundlePackageType</key>
<string>APPL</string> <string>APPL</string>
<key>CFBundleShortVersionString</key> <key>CFBundleShortVersionString</key>
<string>0.8.1</string> <string>0.14</string>
<key>CFBundleVersion</key> <key>CFBundleVersion</key>
<string>1</string> <string>1</string>
<key>LSMinimumSystemVersion</key> <key>LSMinimumSystemVersion</key>
@@ -26,6 +26,12 @@
<true/> <true/>
<key>NSHumanReadableCopyright</key> <key>NSHumanReadableCopyright</key>
<string>Copyright © 2018 Anton Palgunov. All rights reserved.</string> <string>Copyright © 2018 Anton Palgunov. All rights reserved.</string>
<key>NSLocationAlwaysUsageDescription</key>
<string>Weather widget need your location for correct work</string>
<key>NSLocationUsageDescription</key>
<string>Weather widget need your location for correct work</string>
<key>NSLocationWhenInUseUsageDescription</key>
<string>Weather widget need your location for correct work</string>
<key>NSMainStoryboardFile</key> <key>NSMainStoryboardFile</key>
<string>Main</string> <string>Main</string>
<key>NSPrincipalClass</key> <key>NSPrincipalClass</key>
+172 -38
View File
@@ -2,25 +2,25 @@ import Foundation
import AppKit import AppKit
extension Data { extension Data {
func barItemDefinitions() -> [BarItemDefinition]? { func barItemDefinitions() -> [BarItemDefinition]? {
return try? JSONDecoder().decode([BarItemDefinition].self, from: self) return try? JSONDecoder().decode([BarItemDefinition].self, from: self.utf8string!.stripComments().data(using: .utf8)!)
} }
} }
struct BarItemDefinition: Decodable { struct BarItemDefinition: Decodable {
let type: ItemType let type: ItemType
let action: ActionType let action: ActionType
let longAction: LongActionType
let additionalParameters: [GeneralParameters.CodingKeys: GeneralParameter] let additionalParameters: [GeneralParameters.CodingKeys: GeneralParameter]
private enum CodingKeys: String, CodingKey { private enum CodingKeys: String, CodingKey {
case type case type
} }
init(type: ItemType, action: ActionType, additionalParameters: [GeneralParameters.CodingKeys:GeneralParameter]) { init(type: ItemType, action: ActionType, longAction: LongActionType, additionalParameters: [GeneralParameters.CodingKeys:GeneralParameter]) {
self.type = type self.type = type
self.action = action self.action = action
self.longAction = longAction
self.additionalParameters = additionalParameters self.additionalParameters = additionalParameters
} }
@@ -30,66 +30,87 @@ struct BarItemDefinition: Decodable {
let parametersDecoder = SupportedTypesHolder.sharedInstance.lookup(by: type) let parametersDecoder = SupportedTypesHolder.sharedInstance.lookup(by: type)
var additionalParameters = try GeneralParameters(from: decoder).parameters var additionalParameters = try GeneralParameters(from: decoder).parameters
if let result = try? parametersDecoder(decoder), if let result = try? parametersDecoder(decoder),
case let (itemType, action, parameters) = result { case let (itemType, action, longAction, parameters) = result {
parameters.forEach { additionalParameters[$0] = $1 } parameters.forEach { additionalParameters[$0] = $1 }
self.init(type: itemType, action: action, additionalParameters: additionalParameters) self.init(type: itemType, action: action, longAction: longAction, additionalParameters: additionalParameters)
} else { } else {
self.init(type: .staticButton(title: "unknown"), action: .none, additionalParameters: additionalParameters) self.init(type: .staticButton(title: "unknown"), action: .none, longAction: .none, additionalParameters: additionalParameters)
} }
} }
} }
class SupportedTypesHolder { class SupportedTypesHolder {
typealias ParametersDecoder = (Decoder) throws ->(item: ItemType, action: ActionType, parameters: [GeneralParameters.CodingKeys: GeneralParameter]) typealias ParametersDecoder = (Decoder) throws ->(item: ItemType, action: ActionType, longAction: LongActionType, parameters: [GeneralParameters.CodingKeys: GeneralParameter])
private var supportedTypes: [String: ParametersDecoder] = [ private var supportedTypes: [String: ParametersDecoder] = [
"escape": { _ in return (item: .staticButton(title: "esc"), action: .keyPress(keycode: 53), parameters: [.align: .align(.left)]) }, "escape": { _ in return (item: .staticButton(title: "esc"), action: .keyPress(keycode: 53), longAction: .none, parameters: [.align: .align(.left)]) },
"delete": { _ in return (item: .staticButton(title: "del"), action: .keyPress(keycode: 117), longAction: .none, parameters: [:])},
"brightnessUp": { _ in "brightnessUp": { _ in
let imageParameter = GeneralParameter.image(source: #imageLiteral(resourceName: "brightnessUp")) let imageParameter = GeneralParameter.image(source: #imageLiteral(resourceName: "brightnessUp"))
return (item: .staticButton(title: ""), action: .keyPress(keycode: 113), parameters: [.image: imageParameter]) return (item: .staticButton(title: ""), action: .keyPress(keycode: 113), longAction: .none, parameters: [.image: imageParameter])
}, },
"brightnessDown": { _ in "brightnessDown": { _ in
let imageParameter = GeneralParameter.image(source: #imageLiteral(resourceName: "brightnessDown")) let imageParameter = GeneralParameter.image(source: #imageLiteral(resourceName: "brightnessDown"))
return (item: .staticButton(title: ""), action: .keyPress(keycode: 107), parameters: [.image: imageParameter]) return (item: .staticButton(title: ""), action: .keyPress(keycode: 107), longAction: .none, parameters: [.image: imageParameter])
}, },
"volumeDown": { _ in "volumeDown": { _ in
let imageParameter = GeneralParameter.image(source: NSImage(named: .touchBarVolumeDownTemplate)!) let imageParameter = GeneralParameter.image(source: NSImage(named: .touchBarVolumeDownTemplate)!)
return (item: .staticButton(title: ""), action: .hidKey(keycode: NX_KEYTYPE_SOUND_DOWN), parameters: [.image: imageParameter]) return (item: .staticButton(title: ""), action: .hidKey(keycode: NX_KEYTYPE_SOUND_DOWN), longAction: .none, parameters: [.image: imageParameter])
}, },
"volumeUp": { _ in "volumeUp": { _ in
let imageParameter = GeneralParameter.image(source: NSImage(named: .touchBarVolumeUpTemplate)!) let imageParameter = GeneralParameter.image(source: NSImage(named: .touchBarVolumeUpTemplate)!)
return (item: .staticButton(title: ""), action: .hidKey(keycode: NX_KEYTYPE_SOUND_UP), parameters: [.image: imageParameter]) return (item: .staticButton(title: ""), action: .hidKey(keycode: NX_KEYTYPE_SOUND_UP), longAction: .none, parameters: [.image: imageParameter])
},
"mute": { _ in
let imageParameter = GeneralParameter.image(source: NSImage(named: .touchBarAudioOutputMuteTemplate)!)
return (item: .staticButton(title: ""), action: .hidKey(keycode: NX_KEYTYPE_MUTE), longAction: .none, parameters: [.image: imageParameter])
}, },
"previous": { _ in "previous": { _ in
let imageParameter = GeneralParameter.image(source: NSImage(named: .touchBarRewindTemplate)!) let imageParameter = GeneralParameter.image(source: NSImage(named: .touchBarRewindTemplate)!)
return (item: .staticButton(title: ""), action: .hidKey(keycode: NX_KEYTYPE_PREVIOUS), parameters: [.image: imageParameter]) return (item: .staticButton(title: ""), action: .hidKey(keycode: NX_KEYTYPE_PREVIOUS), longAction: .none, parameters: [.image: imageParameter])
}, },
"play": { _ in "play": { _ in
let imageParameter = GeneralParameter.image(source: NSImage(named: .touchBarPlayPauseTemplate)!) let imageParameter = GeneralParameter.image(source: NSImage(named: .touchBarPlayPauseTemplate)!)
return (item: .staticButton(title: ""), action: .hidKey(keycode: NX_KEYTYPE_PLAY), parameters: [.image: imageParameter]) return (item: .staticButton(title: ""), action: .hidKey(keycode: NX_KEYTYPE_PLAY), longAction: .none, parameters: [.image: imageParameter])
}, },
"next": { _ in "next": { _ in
let imageParameter = GeneralParameter.image(source: NSImage(named: .touchBarFastForwardTemplate)!) let imageParameter = GeneralParameter.image(source: NSImage(named: .touchBarFastForwardTemplate)!)
return (item: .staticButton(title: ""), action: .hidKey(keycode: NX_KEYTYPE_NEXT), parameters: [.image: imageParameter]) return (item: .staticButton(title: ""), action: .hidKey(keycode: NX_KEYTYPE_NEXT), longAction: .none, parameters: [.image: imageParameter])
}, },
"weather": { decoder in "weather": { decoder in
enum CodingKeys: String, CodingKey { case refreshInterval } enum CodingKeys: String, CodingKey { case refreshInterval; case units; case api_key ; case icon_type }
let container = try decoder.container(keyedBy: CodingKeys.self) let container = try decoder.container(keyedBy: CodingKeys.self)
let interval = try container.decodeIfPresent(Double.self, forKey: .refreshInterval) let interval = try container.decodeIfPresent(Double.self, forKey: .refreshInterval)
let scriptPath = Bundle.main.path(forResource: "Weather", ofType: "scpt")! let units = try container.decodeIfPresent(String.self, forKey: .units)
let item = ItemType.appleScriptTitledButton(source: Source(filePath: scriptPath), refreshInterval: interval ?? 1800.0) let api_key = try container.decodeIfPresent(String.self, forKey: .api_key)
return (item: item, action: .none, parameters: [:]) let icon_type = try container.decodeIfPresent(String.self, forKey: .icon_type)
let action = try ActionType(from: decoder)
let longAction = try LongActionType(from: decoder)
return (item: .weather(interval: interval ?? 1800.00, units: units ?? "metric", api_key: api_key ?? "32c4256d09a4c52b38aecddba7a078f6", icon_type: icon_type ?? "text"), action: action, longAction: longAction, parameters: [:])
},
"currency": { decoder in
enum CodingKeys: String, CodingKey { case refreshInterval; case from; case to }
let container = try decoder.container(keyedBy: CodingKeys.self)
let interval = try container.decodeIfPresent(Double.self, forKey: .refreshInterval)
let from = try container.decodeIfPresent(String.self, forKey: .from)
let to = try container.decodeIfPresent(String.self, forKey: .to)
let action = try ActionType(from: decoder)
let longAction = try LongActionType(from: decoder)
return (item: .currency(interval: interval ?? 600.00, from: from ?? "RUB", to: to ?? "USD"), action: action, longAction: longAction, parameters: [:])
}, },
"dock": { decoder in "dock": { decoder in
return (item: .dock(), action: .none, parameters: [:]) return (item: .dock(), action: .none, longAction: .none, parameters: [:])
},
"inputsource": { decoder in
return (item: .inputsource(), action: .none, longAction: .none, parameters: [:])
}, },
"volume": { decoder in "volume": { decoder in
enum CodingKeys: String, CodingKey { case image } enum CodingKeys: String, CodingKey { case image }
let container = try decoder.container(keyedBy: CodingKeys.self) let container = try decoder.container(keyedBy: CodingKeys.self)
if var img = try container.decodeIfPresent(Source.self, forKey: .image) { if var img = try container.decodeIfPresent(Source.self, forKey: .image) {
return (item: .volume(), action: .none, parameters: [.image: .image(source: img)]) return (item: .volume(), action: .none, longAction: .none, parameters: [.image: .image(source: img)])
} else { } else {
return (item: .volume(), action: .none, parameters: [:]) return (item: .volume(), action: .none, longAction: .none, parameters: [:])
} }
}, },
"brightness": { decoder in "brightness": { decoder in
@@ -97,28 +118,20 @@ class SupportedTypesHolder {
let container = try decoder.container(keyedBy: CodingKeys.self) let container = try decoder.container(keyedBy: CodingKeys.self)
let interval = try container.decodeIfPresent(Double.self, forKey: .refreshInterval) let interval = try container.decodeIfPresent(Double.self, forKey: .refreshInterval)
if var img = try container.decodeIfPresent(Source.self, forKey: .image) { if var img = try container.decodeIfPresent(Source.self, forKey: .image) {
return (item: .brightness(refreshInterval: interval ?? 0.5), action: .none, parameters: [.image: .image(source: img)]) return (item: .brightness(refreshInterval: interval ?? 0.5), action: .none, longAction: .none, parameters: [.image: .image(source: img)])
} else { } else {
return (item: .brightness(refreshInterval: interval ?? 0.5), action: .none, parameters: [:]) return (item: .brightness(refreshInterval: interval ?? 0.5), action: .none, longAction: .none, parameters: [:])
} }
}, },
"battery": { decoder in "sleep": { _ in return (item: .staticButton(title: "☕️"), action: .shellScript(executable: "/usr/bin/pmset", parameters: ["sleepnow"]), longAction: .none, parameters: [:]) },
enum CodingKeys: String, CodingKey { case refreshInterval } "displaySleep": { _ in return (item: .staticButton(title: "☕️"), action: .shellScript(executable: "/usr/bin/pmset", parameters: ["displaysleepnow"]), longAction: .none, parameters: [:])},
let container = try decoder.container(keyedBy: CodingKeys.self)
let interval = try container.decodeIfPresent(Double.self, forKey: .refreshInterval)
let scriptPath = Bundle.main.path(forResource: "Battery", ofType: "scpt")!
let item = ItemType.appleScriptTitledButton(source: Source(filePath: scriptPath), refreshInterval: interval ?? 1800.0)
return (item: item, action: .none, parameters: [:])
},
"sleep": { _ in return (item: .staticButton(title: "☕️"), action: .shellScript(executable: "/usr/bin/pmset", parameters: ["sleepnow"]), parameters: [:]) },
"displaySleep": { _ in return (item: .staticButton(title: "☕️"), action: .shellScript(executable: "/usr/bin/pmset", parameters: ["displaysleepnow"]), parameters: [:]) },
] ]
static let sharedInstance = SupportedTypesHolder() static let sharedInstance = SupportedTypesHolder()
func lookup(by type: String) -> ParametersDecoder { func lookup(by type: String) -> ParametersDecoder {
return supportedTypes[type] ?? { decoder in return supportedTypes[type] ?? { decoder in
return (item: try ItemType(from: decoder), action: try ActionType(from: decoder), parameters: [:]) return (item: try ItemType(from: decoder), action: try ActionType(from: decoder), longAction: try LongActionType(from: decoder), parameters: [:])
} }
} }
@@ -126,9 +139,9 @@ class SupportedTypesHolder {
supportedTypes[typename] = decoder supportedTypes[typename] = decoder
} }
func register(typename: String, item: ItemType, action: ActionType) { func register(typename: String, item: ItemType, action: ActionType, longAction: LongActionType) {
register(typename: typename) { _ in register(typename: typename) { _ in
return (item: item, action: action, parameters: [:]) return (item: item, action: action, longAction: longAction, parameters: [:])
} }
} }
} }
@@ -137,26 +150,41 @@ enum ItemType: Decodable {
case staticButton(title: String) case staticButton(title: String)
case appleScriptTitledButton(source: SourceProtocol, refreshInterval: Double) case appleScriptTitledButton(source: SourceProtocol, refreshInterval: Double)
case timeButton(formatTemplate: String) case timeButton(formatTemplate: String)
case battery()
case dock() case dock()
case volume() case volume()
case brightness(refreshInterval: Double) case brightness(refreshInterval: Double)
case weather(interval: Double, units: String, api_key: String, icon_type: String)
case currency(interval: Double, from: String, to: String)
case inputsource()
private enum CodingKeys: String, CodingKey { private enum CodingKeys: String, CodingKey {
case type case type
case title case title
case source case source
case refreshInterval case refreshInterval
case from
case to
case units
case api_key
case icon_type
case formatTemplate case formatTemplate
case image case image
case url
case longUrl
} }
enum ItemTypeRaw: String, Decodable { enum ItemTypeRaw: String, Decodable {
case staticButton case staticButton
case appleScriptTitledButton case appleScriptTitledButton
case timeButton case timeButton
case battery
case dock case dock
case volume case volume
case brightness case brightness
case weather
case currency
case inputsource
} }
init(from decoder: Decoder) throws { init(from decoder: Decoder) throws {
@@ -173,6 +201,8 @@ enum ItemType: Decodable {
case .timeButton: case .timeButton:
let template = try container.decodeIfPresent(String.self, forKey: .formatTemplate) ?? "HH:mm" let template = try container.decodeIfPresent(String.self, forKey: .formatTemplate) ?? "HH:mm"
self = .timeButton(formatTemplate: template) self = .timeButton(formatTemplate: template)
case .battery:
self = .battery()
case .dock: case .dock:
self = .dock() self = .dock()
case .volume: case .volume:
@@ -180,6 +210,19 @@ enum ItemType: Decodable {
case .brightness: case .brightness:
let interval = try container.decodeIfPresent(Double.self, forKey: .refreshInterval) ?? 0.5 let interval = try container.decodeIfPresent(Double.self, forKey: .refreshInterval) ?? 0.5
self = .brightness(refreshInterval: interval) self = .brightness(refreshInterval: interval)
case .weather:
let interval = try container.decodeIfPresent(Double.self, forKey: .refreshInterval) ?? 1800.0
let units = try container.decodeIfPresent(String.self, forKey: .units) ?? "metric"
let api_key = try container.decodeIfPresent(String.self, forKey: .api_key) ?? "32c4256d09a4c52b38aecddba7a078f6"
let icon_type = try container.decodeIfPresent(String.self, forKey: .icon_type) ?? "text"
self = .weather(interval: interval, units: units, api_key: api_key, icon_type: icon_type)
case .currency:
let interval = try container.decodeIfPresent(Double.self, forKey: .refreshInterval) ?? 600.0
let from = try container.decodeIfPresent(String.self, forKey: .from) ?? "RUB"
let to = try container.decodeIfPresent(String.self, forKey: .to) ?? "USD"
self = .currency(interval: interval, from: from, to: to)
case .inputsource:
self = .inputsource()
} }
} }
} }
@@ -191,6 +234,7 @@ enum ActionType: Decodable {
case appleSctipt(source: SourceProtocol) case appleSctipt(source: SourceProtocol)
case shellScript(executable: String, parameters: [String]) case shellScript(executable: String, parameters: [String])
case custom(closure: ()->()) case custom(closure: ()->())
case openUrl(url: String)
private enum CodingKeys: String, CodingKey { private enum CodingKeys: String, CodingKey {
case action case action
@@ -198,6 +242,7 @@ enum ActionType: Decodable {
case actionAppleScript case actionAppleScript
case executablePath case executablePath
case shellArguments case shellArguments
case url
} }
private enum ActionTypeRaw: String, Decodable { private enum ActionTypeRaw: String, Decodable {
@@ -205,6 +250,7 @@ enum ActionType: Decodable {
case keyPress case keyPress
case appleScript case appleScript
case shellScript case shellScript
case openUrl
} }
init(from decoder: Decoder) throws { init(from decoder: Decoder) throws {
@@ -224,6 +270,62 @@ enum ActionType: Decodable {
let executable = try container.decode(String.self, forKey: .executablePath) let executable = try container.decode(String.self, forKey: .executablePath)
let parameters = try container.decodeIfPresent([String].self, forKey: .shellArguments) ?? [] let parameters = try container.decodeIfPresent([String].self, forKey: .shellArguments) ?? []
self = .shellScript(executable: executable, parameters: parameters) self = .shellScript(executable: executable, parameters: parameters)
case .some(.openUrl):
let url = try container.decode(String.self, forKey: .url)
self = .openUrl(url: url)
case .none:
self = .none
}
}
}
enum LongActionType: Decodable {
case none
case hidKey(keycode: Int)
case keyPress(keycode: Int)
case appleSctipt(source: SourceProtocol)
case shellScript(executable: String, parameters: [String])
case custom(closure: ()->())
case openUrl(url: String)
private enum CodingKeys: String, CodingKey {
case longAction
case keycode
case actionAppleScript
case executablePath
case shellArguments
case longUrl
}
private enum LongActionTypeRaw: String, Decodable {
case hidKey
case keyPress
case appleScript
case shellScript
case openUrl
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
let longType = try container.decodeIfPresent(LongActionTypeRaw.self, forKey: .longAction)
switch longType {
case .some(.hidKey):
let keycode = try container.decode(Int.self, forKey: .keycode)
self = .hidKey(keycode: keycode)
case .some(.keyPress):
let keycode = try container.decode(Int.self, forKey: .keycode)
self = .keyPress(keycode: keycode)
case .some(.appleScript):
let source = try container.decode(Source.self, forKey: .actionAppleScript)
self = .appleSctipt(source: source)
case .some(.shellScript):
let executable = try container.decode(String.self, forKey: .executablePath)
let parameters = try container.decodeIfPresent([String].self, forKey: .shellArguments) ?? []
self = .shellScript(executable: executable, parameters: parameters)
case .some(.openUrl):
let longUrl = try container.decode(String.self, forKey: .longUrl)
self = .openUrl(url: longUrl)
case .none: case .none:
self = .none self = .none
} }
@@ -255,6 +357,28 @@ func ==(lhs: ActionType, rhs: ActionType) -> Bool {
return a == b return a == b
case let (.shellScript(a, b), .shellScript(c, d)): case let (.shellScript(a, b), .shellScript(c, d)):
return a == c && b == d return a == c && b == d
case let (.openUrl(a), .openUrl(b)):
return a == b
default:
return false
}
}
extension LongActionType: Equatable {}
func ==(lhs: LongActionType, rhs: LongActionType) -> Bool {
switch (lhs, rhs) {
case (.none, .none):
return true
case let (.hidKey(a), .hidKey(b)),
let (.keyPress(a), .keyPress(b)):
return a == b
case let (.appleSctipt(a), .appleSctipt(b)):
return a == b
case let (.shellScript(a, b), .shellScript(c, d)):
return a == c && b == d
case let (.openUrl(a), .openUrl(b)):
return a == b
default: default:
return false return false
} }
@@ -264,6 +388,8 @@ enum GeneralParameter {
case width(_: CGFloat) case width(_: CGFloat)
case image(source: SourceProtocol) case image(source: SourceProtocol)
case align(_: Align) case align(_: Align)
case bordered(_: Bool)
case background(_:NSColor)
} }
struct GeneralParameters: Decodable { struct GeneralParameters: Decodable {
@@ -273,6 +399,8 @@ struct GeneralParameters: Decodable {
case width case width
case image case image
case align case align
case bordered
case background
} }
init(from decoder: Decoder) throws { init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self) let container = try decoder.container(keyedBy: CodingKeys.self)
@@ -285,6 +413,12 @@ struct GeneralParameters: Decodable {
} }
let align = try container.decodeIfPresent(Align.self, forKey: .align) ?? .center let align = try container.decodeIfPresent(Align.self, forKey: .align) ?? .center
result[.align] = .align(align) result[.align] = .align(align)
if let borderedFlag = try container.decodeIfPresent(Bool.self, forKey: .bordered) {
result[.bordered] = .bordered(borderedFlag)
}
if let backgroundColor = try container.decodeIfPresent(String.self, forKey: .background)?.hexColor {
result[.background] = .background(backgroundColor)
}
parameters = result parameters = result
} }
} }
+2
View File
@@ -58,6 +58,7 @@ func HIDPostAuxKey(_ key: Int) {
// hidsystem/ev_keymap.h // hidsystem/ev_keymap.h
let NX_KEYTYPE_SOUND_UP = 0 let NX_KEYTYPE_SOUND_UP = 0
let NX_KEYTYPE_SOUND_DOWN = 1 let NX_KEYTYPE_SOUND_DOWN = 1
let NX_KEYTYPE_MUTE = 7
let NX_KEYTYPE_BRIGHTNESS_UP = 2 let NX_KEYTYPE_BRIGHTNESS_UP = 2
let NX_KEYTYPE_BRIGHTNESS_DOWN = 3 let NX_KEYTYPE_BRIGHTNESS_DOWN = 3
@@ -69,3 +70,4 @@ let NX_KEYTYPE_PREVIOUS = 18
+1
View File
@@ -10,6 +10,7 @@ class ScrollViewItem: NSCustomTouchBarItem {
stackView.orientation = .horizontal stackView.orientation = .horizontal
let scrollView = NSScrollView(frame: CGRect(origin: .zero, size: stackView.fittingSize)) let scrollView = NSScrollView(frame: CGRect(origin: .zero, size: stackView.fittingSize))
scrollView.documentView = stackView scrollView.documentView = stackView
// scrollView.documentView?.bounds.origin = CGPoint(x: 0.0, y: -2.5)
self.view = scrollView self.view = scrollView
} }
+23
View File
@@ -12,6 +12,29 @@ extension String {
func trim() -> String { func trim() -> String {
return self.trimmingCharacters(in: NSCharacterSet.whitespaces) return self.trimmingCharacters(in: NSCharacterSet.whitespaces)
} }
func stripComments() -> String {
// ((\s|,)\/\*[\s\S]*?\*\/)|(( |, ")\/\/.*)
return self.replacingOccurrences(of: "((\\s|,)\\/\\*[\\s\\S]*?\\*\\/)|(( |, \\\")\\/\\/.*)", with: "", options: .regularExpression)
}
var hexColor: NSColor? {
let hex = trimmingCharacters(in: CharacterSet.alphanumerics.inverted)
var int = UInt32()
Scanner(string: hex).scanHexInt32(&int)
let a, r, g, b: UInt32
switch hex.count {
case 3: // RGB (12-bit)
(r, g, b, a) = ((int >> 8) * 17, (int >> 4 & 0xF) * 17, (int & 0xF) * 17, 255)
case 6: // RGB (24-bit)
(r, g, b, a) = (int >> 16, int >> 8 & 0xFF, int & 0xFF, 255)
case 8: // ARGB (32-bit)
(r, g, b, a) = (int >> 24, int >> 16 & 0xFF, int >> 8 & 0xFF, int & 0xFF)
default:
return nil
}
return NSColor(red: CGFloat(r) / 255, green: CGFloat(g) / 255, blue: CGFloat(b) / 255, alpha: CGFloat(a) / 255)
}
} }
extension NSImage { extension NSImage {
+134 -22
View File
@@ -23,12 +23,20 @@ extension ItemType {
return "com.toxblh.mtmr.appleScriptButton." return "com.toxblh.mtmr.appleScriptButton."
case .timeButton(formatTemplate: _): case .timeButton(formatTemplate: _):
return "com.toxblh.mtmr.timeButton." return "com.toxblh.mtmr.timeButton."
case .battery():
return "com.toxblh.mtmr.battery."
case .dock(): case .dock():
return "com.toxblh.mtmr.dock" return "com.toxblh.mtmr.dock"
case .volume(): case .volume():
return "com.toxblh.mtmr.volume" return "com.toxblh.mtmr.volume"
case .brightness(refreshInterval: _): case .brightness(refreshInterval: _):
return "com.toxblh.mtmr.brightness" return "com.toxblh.mtmr.brightness"
case .weather(interval: _, units: _, api_key: _, icon_type: _):
return "com.toxblh.mtmr.weather"
case .currency(interval: _, from: _, to: _):
return "com.toxblh.mtmr.currency"
case .inputsource():
return "com.toxblh.mtmr.inputsource."
} }
} }
@@ -36,49 +44,62 @@ extension ItemType {
extension NSTouchBarItem.Identifier { extension NSTouchBarItem.Identifier {
static let controlStripItem = NSTouchBarItem.Identifier("com.toxblh.mtmr.controlStrip") static let controlStripItem = NSTouchBarItem.Identifier("com.toxblh.mtmr.controlStrip")
static let centerScrollArea = NSTouchBarItem.Identifier("com.toxblh.mtmr.scrollArea")
} }
class TouchBarController: NSObject, NSTouchBarDelegate { class TouchBarController: NSObject, NSTouchBarDelegate {
static let shared = TouchBarController() static let shared = TouchBarController()
let touchBar = NSTouchBar() var touchBar: NSTouchBar!
var itemDefinitions: [NSTouchBarItem.Identifier: BarItemDefinition] = [:] var itemDefinitions: [NSTouchBarItem.Identifier: BarItemDefinition] = [:]
var items: [NSTouchBarItem.Identifier: NSTouchBarItem] = [:] var items: [NSTouchBarItem.Identifier: NSTouchBarItem] = [:]
var leftIdentifiers: [NSTouchBarItem.Identifier] = [] var leftIdentifiers: [NSTouchBarItem.Identifier] = []
var centerIdentifiers: [NSTouchBarItem.Identifier] = []
var centerItems: [NSTouchBarItem] = [] var centerItems: [NSTouchBarItem] = []
var rightIdentifiers: [NSTouchBarItem.Identifier] = [] var rightIdentifiers: [NSTouchBarItem.Identifier] = []
var scrollArea: NSCustomTouchBarItem?
var centerScrollArea = NSTouchBarItem.Identifier("com.toxblh.mtmr.scrollArea.".appending(UUID().uuidString))
private override init() { private override init() {
super.init() super.init()
SupportedTypesHolder.sharedInstance.register(typename: "exitTouchbar", item: .staticButton(title: "exit"), action: .custom(closure: { [weak self] in SupportedTypesHolder.sharedInstance.register(typename: "exitTouchbar", item: .staticButton(title: "exit"), action: .custom(closure: { [weak self] in self?.dismissTouchBar()}), longAction: .none)
self?.dismissTouchBar()
}))
createAndUpdatePreset() createAndUpdatePreset()
} }
func createAndUpdatePreset() { func createAndUpdatePreset(jsonItems: [BarItemDefinition]? = nil) {
if let oldBar = self.touchBar {
NSTouchBar.minimizeSystemModalFunctionBar(oldBar)
}
self.touchBar = NSTouchBar()
var jsonItems = jsonItems
self.itemDefinitions = [:] self.itemDefinitions = [:]
self.items = [:] self.items = [:]
self.leftIdentifiers = [] self.leftIdentifiers = []
self.centerItems = [] self.centerItems = []
self.rightIdentifiers = [] self.rightIdentifiers = []
loadItemDefinitions() if (jsonItems == nil) {
createItems() jsonItems = readConfig()
centerItems = self.itemDefinitions.compactMap { (identifier, definition) -> NSTouchBarItem? in
return definition.align == .center ? items[identifier] : nil
} }
loadItemDefinitions(jsonItems: jsonItems!)
createItems()
centerItems = centerIdentifiers.compactMap({ (identifier) -> NSTouchBarItem? in
return items[identifier]
})
self.centerScrollArea = NSTouchBarItem.Identifier("com.toxblh.mtmr.scrollArea.".appending(UUID().uuidString))
self.scrollArea = ScrollViewItem(identifier: centerScrollArea, items: centerItems)
touchBar.delegate = self touchBar.delegate = self
touchBar.defaultItemIdentifiers = self.leftIdentifiers + [.centerScrollArea] + self.rightIdentifiers touchBar.defaultItemIdentifiers = []
touchBar.defaultItemIdentifiers = self.leftIdentifiers + [centerScrollArea] + self.rightIdentifiers
self.presentTouchBar() self.presentTouchBar()
} }
func loadItemDefinitions() { func readConfig() -> [BarItemDefinition]? {
let appSupportDirectory = NSSearchPathForDirectoriesInDomains(.applicationSupportDirectory, .userDomainMask, true).first!.appending("/MTMR") let appSupportDirectory = NSSearchPathForDirectoriesInDomains(.applicationSupportDirectory, .userDomainMask, true).first!.appending("/MTMR")
let presetPath = appSupportDirectory.appending("/items.json") let presetPath = appSupportDirectory.appending("/items.json")
if !FileManager.default.fileExists(atPath: presetPath), if !FileManager.default.fileExists(atPath: presetPath),
@@ -86,11 +107,18 @@ class TouchBarController: NSObject, NSTouchBarDelegate {
try? FileManager.default.createDirectory(atPath: appSupportDirectory, withIntermediateDirectories: true, attributes: nil) try? FileManager.default.createDirectory(atPath: appSupportDirectory, withIntermediateDirectories: true, attributes: nil)
try? FileManager.default.copyItem(atPath: defaultPreset, toPath: presetPath) try? FileManager.default.copyItem(atPath: defaultPreset, toPath: presetPath)
} }
let jsonData = presetPath.fileData
let jsonItems = jsonData?.barItemDefinitions() ?? [BarItemDefinition(type: .staticButton(title: "bad preset"), action: .none, additionalParameters: [:])]
let jsonData = presetPath.fileData
return jsonData?.barItemDefinitions() ?? [BarItemDefinition(type: .staticButton(title: "bad preset"), action: .none, longAction: .none, additionalParameters: [:])]
}
func loadItemDefinitions(jsonItems: [BarItemDefinition]) {
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "HH-mm-ss"
let time = dateFormatter.string(from: Date())
for item in jsonItems { for item in jsonItems {
let identifierString = item.type.identifierBase.appending(UUID().uuidString) let identifierString = item.type.identifierBase.appending(time + "--" + UUID().uuidString)
let identifier = NSTouchBarItem.Identifier(identifierString) let identifier = NSTouchBarItem.Identifier(identifierString)
itemDefinitions[identifier] = item itemDefinitions[identifier] = item
if item.align == .left { if item.align == .left {
@@ -99,6 +127,9 @@ class TouchBarController: NSObject, NSTouchBarDelegate {
if item.align == .right { if item.align == .right {
rightIdentifiers.append(identifier) rightIdentifiers.append(identifier)
} }
if item.align == .center {
centerIdentifiers.append(identifier)
}
} }
} }
@@ -129,8 +160,8 @@ class TouchBarController: NSObject, NSTouchBarDelegate {
} }
func touchBar(_ touchBar: NSTouchBar, makeItemForIdentifier identifier: NSTouchBarItem.Identifier) -> NSTouchBarItem? { func touchBar(_ touchBar: NSTouchBar, makeItemForIdentifier identifier: NSTouchBarItem.Identifier) -> NSTouchBarItem? {
if identifier == .centerScrollArea { if identifier == centerScrollArea {
return ScrollViewItem(identifier: identifier, items: centerItems) return self.scrollArea
} }
guard let item = self.items[identifier], guard let item = self.items[identifier],
@@ -143,15 +174,18 @@ class TouchBarController: NSObject, NSTouchBarDelegate {
func createItem(forIdentifier identifier: NSTouchBarItem.Identifier, definition item: BarItemDefinition) -> NSTouchBarItem? { func createItem(forIdentifier identifier: NSTouchBarItem.Identifier, definition item: BarItemDefinition) -> NSTouchBarItem? {
let action = self.action(forItem: item) let action = self.action(forItem: item)
let longAction = self.longAction(forItem: item)
var barItem: NSTouchBarItem! var barItem: NSTouchBarItem!
switch item.type { switch item.type {
case .staticButton(title: let title): case .staticButton(title: let title):
barItem = CustomButtonTouchBarItem(identifier: identifier, title: title, onTap: action) barItem = CustomButtonTouchBarItem(identifier: identifier, title: title, onTap: action, onLongTap: longAction, bezelColor: NSColor.controlColor)
case .appleScriptTitledButton(source: let source, refreshInterval: let interval): case .appleScriptTitledButton(source: let source, refreshInterval: let interval):
barItem = AppleScriptTouchBarItem(identifier: identifier, source: source, interval: interval, onTap: action) barItem = AppleScriptTouchBarItem(identifier: identifier, source: source, interval: interval, onTap: action, onLongTap: longAction)
case .timeButton(formatTemplate: let template): case .timeButton(formatTemplate: let template):
barItem = TimeTouchBarItem(identifier: identifier, formatTemplate: template) barItem = TimeTouchBarItem(identifier: identifier, formatTemplate: template, onTap: action, onLongTap: longAction)
case .battery():
barItem = BatteryBarItem(identifier: identifier, onTap: action, onLongTap: longAction)
case .dock: case .dock:
barItem = AppScrubberTouchBarItem(identifier: identifier) barItem = AppScrubberTouchBarItem(identifier: identifier)
case .volume: case .volume:
@@ -166,16 +200,42 @@ class TouchBarController: NSObject, NSTouchBarDelegate {
} else { } else {
barItem = BrightnessViewController(identifier: identifier, refreshInterval: interval) barItem = BrightnessViewController(identifier: identifier, refreshInterval: interval)
} }
case .weather(interval: let interval, units: let units, api_key: let api_key, icon_type: let icon_type):
barItem = WeatherBarItem(identifier: identifier, interval: interval, units: units, api_key: api_key, icon_type: icon_type, onTap: action, onLongTap: longAction)
case .currency(interval: let interval, from: let from, to: let to):
barItem = CurrencyBarItem(identifier: identifier, interval: interval, from: from, to: to, onTap: action, onLongTap: longAction)
case .inputsource():
barItem = InputSourceBarItem(identifier: identifier, onTap: action, onLongTap: longAction)
} }
if case .width(let value)? = item.additionalParameters[.width], let widthBarItem = barItem as? CanSetWidth { if case .width(let value)? = item.additionalParameters[.width], let widthBarItem = barItem as? CanSetWidth {
widthBarItem.setWidth(value: value) widthBarItem.setWidth(value: value)
} }
if case .bordered(let bordered)? = item.additionalParameters[.bordered], let item = barItem as? CustomButtonTouchBarItem {
item.button.isBordered = bordered
item.button.bezelStyle = bordered ? .rounded : .inline
}
if case .background(let color)? = item.additionalParameters[.background], let item = barItem as? CustomButtonTouchBarItem {
if item.button.cell?.isBordered == false {
let newCell = NSButtonCell()
newCell.title = item.button.title
item.button.cell = newCell
item.button.cell?.isBordered = true
}
item.button.bezelColor = color
(item.button.cell as? NSButtonCell)?.backgroundColor = color
}
if case .image(let source)? = item.additionalParameters[.image], let item = barItem as? CustomButtonTouchBarItem { if case .image(let source)? = item.additionalParameters[.image], let item = barItem as? CustomButtonTouchBarItem {
let button = item.button! let button = item.button!
button.image = source.image button.imageScaling = .scaleProportionallyDown
button.imagePosition = .imageLeading button.imagePosition = .imageLeading
if (button.title == "") {
button.imagePosition = .imageOnly
}
button.imageHugsTitle = true button.imageHugsTitle = true
button.bezelColor = .clear button.image = source.image
} }
return barItem return barItem
} }
@@ -205,6 +265,16 @@ class TouchBarController: NSObject, NSTouchBarDelegate {
task.arguments = parameters task.arguments = parameters
task.launch() task.launch()
} }
case .openUrl(url: let url):
return {
if let url = URL(string: url), NSWorkspace.shared.open(url) {
#if DEBUG
print("URL was successfully opened")
#endif
} else {
print("error", url)
}
}
case .custom(closure: let closure): case .custom(closure: let closure):
return closure return closure
case .none: case .none:
@@ -212,6 +282,48 @@ class TouchBarController: NSObject, NSTouchBarDelegate {
} }
} }
func longAction(forItem item: BarItemDefinition) -> ()->() {
switch item.longAction {
case .hidKey(keycode: let keycode):
return { HIDPostAuxKey(keycode) }
case .keyPress(keycode: let keycode):
return { GenericKeyPress(keyCode: CGKeyCode(keycode)).send() }
case .appleSctipt(source: let source):
guard let appleScript = source.appleScript else {
print("cannot create apple script for item \(item)")
return {}
}
return {
var error: NSDictionary?
appleScript.executeAndReturnError(&error)
if let error = error {
print("error \(error) when handling \(item) ")
}
}
case .shellScript(executable: let executable, parameters: let parameters):
return {
let task = Process()
task.launchPath = executable
task.arguments = parameters
task.launch()
}
case .openUrl(url: let url):
return {
if let url = URL(string: url), NSWorkspace.shared.open(url) {
#if DEBUG
print("URL was successfully opened")
#endif
} else {
print("error", url)
}
}
case .custom(closure: let closure):
return closure
case .none:
return {}
}
}
} }
protocol CanSetWidth { protocol CanSetWidth {
+254
View File
@@ -0,0 +1,254 @@
//
// AppScrubberTouchBarItem.swift
// MTMR
//
// Created by Daniel Apatin on 18.04.2018.
// Copyright © 2018 Anton Palgunov. All rights reserved.
import Cocoa
class AppScrubberTouchBarItem: NSCustomTouchBarItem, NSScrubberDelegate, NSScrubberDataSource {
private var scrubber: NSScrubber!
private let hf: HapticFeedback = HapticFeedback()
private var timer: Timer!
private var ticks: Int = 0
private let minTicks: Int = 5
private let maxTicks: Int = 20
private var lastSelected: Int = 0
private var persistentAppIdentifiers: [String] = []
private var runningAppsIdentifiers: [String] = []
private var frontmostApplicationIdentifier: String? {
get {
guard let frontmostId = NSWorkspace.shared.frontmostApplication?.bundleIdentifier else { return nil }
return frontmostId
}
}
private var applications: [DockItem] = []
override init(identifier: NSTouchBarItem.Identifier) {
super.init(identifier: identifier)
scrubber = NSScrubber();
scrubber.delegate = self
scrubber.dataSource = self
scrubber.mode = .free // .fixed
let layout = NSScrubberFlowLayout();
layout.itemSize = NSSize(width: 36, height: 32)
layout.itemSpacing = 2
scrubber.scrubberLayout = layout
scrubber.selectionBackgroundStyle = .roundedBackground
scrubber.showsAdditionalContentIndicators = true
view = scrubber
scrubber.register(NSScrubberImageItemView.self, forItemIdentifier: NSUserInterfaceItemIdentifier(rawValue: "ScrubberApplicationsItemReuseIdentifier"))
NSWorkspace.shared.notificationCenter.addObserver(self, selector: #selector(activeApplicationChanged), name: NSWorkspace.didLaunchApplicationNotification, object: nil)
NSWorkspace.shared.notificationCenter.addObserver(self, selector: #selector(activeApplicationChanged), name: NSWorkspace.didTerminateApplicationNotification, object: nil)
NSWorkspace.shared.notificationCenter.addObserver(self, selector: #selector(activeApplicationChanged), name: NSWorkspace.didActivateApplicationNotification, object: nil)
if let persistent = UserDefaults.standard.stringArray(forKey: "com.toxblh.mtmr.dock.persistent") {
self.persistentAppIdentifiers = persistent
}
updateRunningApplication()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
@objc func activeApplicationChanged(n: Notification) {
updateRunningApplication()
}
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
updateRunningApplication()
}
func updateRunningApplication() {
let newApplications = launchedApplications()
let index = newApplications.index {
$0.bundleIdentifier == frontmostApplicationIdentifier
}
applications = newApplications
applications += getDockPersistentAppsList()
scrubber.reloadData()
scrubber.selectedIndex = index ?? 0
}
public func numberOfItems(for scrubber: NSScrubber) -> Int {
return applications.count
}
public func scrubber(_ scrubber: NSScrubber, viewForItemAt index: Int) -> NSScrubberItemView {
let item = scrubber.makeItem(withIdentifier: NSUserInterfaceItemIdentifier(rawValue: "ScrubberApplicationsItemReuseIdentifier"), owner: self) as? NSScrubberImageItemView ?? NSScrubberImageItemView()
item.imageView.imageScaling = .scaleProportionallyDown
let app = applications[index]
if let icon = app.icon {
item.image = icon
item.image.size = NSSize(width: 26, height: 26)
}
item.removeFromSuperview()
let dotView = NSView(frame: .zero)
dotView.wantsLayer = true
if self.runningAppsIdentifiers.contains(app.bundleIdentifier!) {
dotView.layer?.backgroundColor = NSColor.white.cgColor
} else {
dotView.layer?.backgroundColor = NSColor.black.cgColor
}
dotView.layer?.cornerRadius = 1.5
dotView.setFrameOrigin(NSPoint(x: 17, y: 1))
dotView.frame.size = NSSize(width: 3, height: 3)
item.addSubview(dotView)
return item
}
public func didBeginInteracting(with scrubber: NSScrubber) {
stopTimer()
self.ticks = 0
self.timer = Timer.scheduledTimer(timeInterval: 0.1, target: self, selector: #selector(checkTimer), userInfo: nil, repeats: true)
}
@objc private func checkTimer() {
self.ticks += 1
if (self.ticks == minTicks) {
hf.tap(strong: 2)
}
if (self.ticks > maxTicks) {
stopTimer()
hf.tap(strong: 6)
}
}
private func stopTimer() {
self.timer?.invalidate()
self.timer = nil
self.lastSelected = 0
}
public func didCancelInteracting(with scrubber: NSScrubber) {
stopTimer()
}
public func didFinishInteracting(with scrubber: NSScrubber) {
stopTimer()
if (ticks == 0) {
return
}
if (self.ticks >= minTicks && scrubber.selectedIndex > 0) {
self.longPress(selected: scrubber.selectedIndex)
return
}
let bundleIdentifier = applications[scrubber.selectedIndex].bundleIdentifier
if bundleIdentifier!.contains("file://") {
NSWorkspace.shared.openFile(bundleIdentifier!.replacingOccurrences(of: "file://", with: ""))
} else {
NSWorkspace.shared.launchApplication(withBundleIdentifier: bundleIdentifier!, options: [.default], additionalEventParamDescriptor: nil, launchIdentifier: nil)
hf.tap(strong: 6)
}
updateRunningApplication()
// NB: if you can't open app which on another space, try to check mark
// "When switching to an application, switch to a Space with open windows for the application"
// in Mission control settings
}
private func longPress(selected: Int) {
let bundleIdentifier = applications[selected].bundleIdentifier
if (self.ticks > maxTicks) {
if let processIdentifier = applications[selected].pid {
if !(NSRunningApplication(processIdentifier: processIdentifier)?.terminate())! {
NSRunningApplication(processIdentifier: processIdentifier)?.forceTerminate()
}
}
} else {
hf.tap(strong: 6)
if let index = self.persistentAppIdentifiers.index(of: bundleIdentifier!) {
self.persistentAppIdentifiers.remove(at: index)
} else {
self.persistentAppIdentifiers.append(bundleIdentifier!)
}
UserDefaults.standard.set(self.persistentAppIdentifiers, forKey: "com.toxblh.mtmr.dock.persistent")
UserDefaults.standard.synchronize()
}
self.ticks = 0
updateRunningApplication()
}
private func launchedApplications() -> [DockItem] {
self.runningAppsIdentifiers = []
var returnable: [DockItem] = []
for app in NSWorkspace.shared.runningApplications {
guard app.activationPolicy == NSApplication.ActivationPolicy.regular else { continue }
guard let bundleIdentifier = app.bundleIdentifier else { continue }
self.runningAppsIdentifiers.append(bundleIdentifier)
let dockItem = DockItem(bundleIdentifier: bundleIdentifier, icon: getIcon(forBundleIdentifier: bundleIdentifier), pid: app.processIdentifier)
returnable.append(dockItem)
}
return returnable
}
public func getIcon(forBundleIdentifier bundleIdentifier: String? = nil, orPath path: String? = nil, orType type: String? = nil) -> NSImage {
if bundleIdentifier != nil {
if let appPath = NSWorkspace.shared.absolutePathForApplication(withBundleIdentifier: bundleIdentifier!) {
return NSWorkspace.shared.icon(forFile: appPath)
}
}
if path != nil {
return NSWorkspace.shared.icon(forFile: path!)
}
let genericIcon = NSImage(contentsOfFile: "/System/Library/CoreServices/CoreTypes.bundle/Contents/Resources/GenericDocumentIcon.icns")
return genericIcon ?? NSImage(size: .zero)
}
public func getDockPersistentAppsList() -> [DockItem] {
var returnable: [DockItem] = []
for bundleIdentifier in persistentAppIdentifiers {
if !self.runningAppsIdentifiers.contains(bundleIdentifier) {
let dockItem = DockItem(bundleIdentifier: bundleIdentifier, icon: getIcon(forBundleIdentifier: bundleIdentifier))
returnable.append(dockItem)
}
}
return returnable
}
}
public class DockItem: NSObject {
var bundleIdentifier: String!, icon: NSImage!, pid: Int32!
convenience init(bundleIdentifier: String, icon: NSImage, pid: Int32? = nil) {
self.init()
self.bundleIdentifier = bundleIdentifier
self.icon = icon
self.pid = pid
}
}
+145
View File
@@ -0,0 +1,145 @@
//
// BatteryBarItem.swift
// MTMR
//
// Created by Anton Palgunov on 18/04/2018.
// Copyright © 2018 Anton Palgunov. All rights reserved.
//
import IOKit.ps
import Foundation
class BatteryBarItem: CustomButtonTouchBarItem {
private var timer: Timer!
init(identifier: NSTouchBarItem.Identifier, onTap: @escaping () -> (), onLongTap: @escaping () -> ()) {
super.init(identifier: identifier, title: " ", onTap: onTap, onLongTap: onLongTap)
self.view = button
let batteryInfo = BatteryInfo(button: button)
batteryInfo.start()
batteryInfo.updateInfo()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
class BatteryInfo: NSObject {
var current: Int = 0
var timeToEmpty: Int = 0
var timeToFull: Int = 0
var isCharged: Bool = false
var isCharging: Bool = false
var ACPower: String = ""
var timeRemaining: String = ""
var button: NSButton
var loop:CFRunLoopSource?
init(button: NSButton) {
self.button = button
super.init()
self.start()
}
func start() {
let opaque = Unmanaged.passRetained(self).toOpaque()
let context = UnsafeMutableRawPointer(opaque)
loop = IOPSNotificationCreateRunLoopSource({ (context) in
guard let ctx = context else {
return
}
let watcher = Unmanaged<BatteryInfo>.fromOpaque(ctx).takeUnretainedValue()
watcher.updateInfo()
}, context).takeRetainedValue() as CFRunLoopSource
CFRunLoopAddSource(CFRunLoopGetCurrent(), loop, CFRunLoopMode.defaultMode)
}
func stop() {
if !(self.loop != nil) {
return
}
CFRunLoopRemoveSource(CFRunLoopGetCurrent(), self.loop, CFRunLoopMode.defaultMode)
self.loop = nil
}
func getPSInfo() {
let psInfo = IOPSCopyPowerSourcesInfo().takeRetainedValue()
let psList = IOPSCopyPowerSourcesList(psInfo).takeRetainedValue() as [CFTypeRef]
for ps in psList {
if let psDesc = IOPSGetPowerSourceDescription(psInfo, ps).takeUnretainedValue() as? [String: Any] {
let current = psDesc[kIOPSCurrentCapacityKey]
if (current != nil) {
self.current = current as! Int
}
let timeToEmpty = psDesc[kIOPSTimeToEmptyKey]
if (timeToEmpty != nil) {
self.timeToEmpty = timeToEmpty as! Int
}
let timeToFull = psDesc[kIOPSTimeToFullChargeKey]
if (timeToFull != nil) {
self.timeToFull = timeToFull as! Int
}
let isCharged = psDesc[kIOPSIsChargedKey]
if (isCharged != nil) {
self.isCharged = isCharged as! Bool
}
let isCharging = psDesc[kIOPSIsChargingKey]
if (isCharging != nil) {
self.isCharging = isCharging as! Bool
}
let ACPower = psDesc[kIOPSPowerSourceStateKey]
if (ACPower != nil) {
self.ACPower = ACPower as! String
}
}
}
}
func getFormattedTime(time: Int) -> String {
if (time > 0) {
let timeFormatted = NSString(format: " %d:%02d", time / 60, time % 60) as String
return timeFormatted
}
return ""
}
public func updateInfo() {
var title = ""
self.getPSInfo()
if ACPower == "AC Power" {
if current < 100 {
title += "⚡️"
}
timeRemaining = getFormattedTime(time: timeToFull)
} else {
timeRemaining = getFormattedTime(time: timeToEmpty)
}
title += String(current) + "%"
var color = NSColor.white
if current <= 10 && ACPower != "AC Power" {
color = NSColor.red
}
let regularFont = button.attributedTitle.attribute(.font, at: 0, effectiveRange: nil) as? NSFont ?? NSFont.systemFont(ofSize: 15)
let newTitle = NSMutableAttributedString(string: title as String, attributes: [.foregroundColor: color, .font: regularFont, .baselineOffset: 1])
let newTitleSecond = NSMutableAttributedString(string: timeRemaining as String, attributes: [NSAttributedStringKey.foregroundColor: color, NSAttributedStringKey.font: NSFont.systemFont(ofSize: 8, weight: .regular), NSAttributedStringKey.baselineOffset: 7])
newTitle.append(newTitleSecond)
newTitle.setAlignment(.center, range: NSRange(location: 0, length: title.count))
button.attributedTitle = newTitle
}
}
+117
View File
@@ -0,0 +1,117 @@
//
// CurrencyBarItem.swift
// MTMR
//
// Created by Daniel Apatin on 18.04.2018.
// Copyright © 2018 Anton Palgunov. All rights reserved.
//
import Cocoa
import CoreLocation
class CurrencyBarItem: CustomButtonTouchBarItem {
private let activity: NSBackgroundActivityScheduler
private var prefix: String
private var from: String
private var to: String
private var oldValue: Float32!
private let currencies = [
"USD": "$",
"EUR": "€",
"RUB": "₽",
"JPY": "¥",
"GBP": "₤",
"CAD": "$",
"KRW": "₩",
"CNY": "¥",
"AUD": "$",
"BRL": "R$",
"IDR": "Rp",
"MXN": "$",
"SGD": "$",
"CHF": "Fr.",
"BTC": "฿",
"LTC": "Ł",
"ETH": "Ξ",
]
init(identifier: NSTouchBarItem.Identifier, interval: TimeInterval, from: String, to: String, onTap: @escaping () -> (), onLongTap: @escaping () -> ()) {
activity = NSBackgroundActivityScheduler(identifier: "\(identifier.rawValue).updatecheck")
activity.interval = interval
self.from = from
self.to = to
if let prefix = currencies[from] {
self.prefix = prefix
} else {
self.prefix = from
}
super.init(identifier: identifier, title: "⏳", onTap: onTap, onLongTap: onLongTap)
self.view = button
activity.repeats = true
activity.qualityOfService = .utility
activity.schedule { (completion: NSBackgroundActivityScheduler.CompletionHandler) in
self.updateCurrency()
completion(NSBackgroundActivityScheduler.Result.finished)
}
updateCurrency()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
@objc func updateCurrency() {
let urlRequest = URLRequest(url: URL(string: "https://api.coinbase.com/v2/exchange-rates?currency=\(from)")!)
let task = URLSession.shared.dataTask(with: urlRequest) { (data, response, error) in
if error == nil {
do {
let json = try JSONSerialization.jsonObject(with: data!, options: .mutableContainers) as! [String : AnyObject]
var value: Float32!
if let data_array = json["data"] as? [String : AnyObject] {
if let rates = data_array["rates"] as? [String : AnyObject] {
if let item = rates["\(self.to)"] as? String {
value = Float32(item)
}
}
}
if value != nil {
DispatchQueue.main.async {
self.setCurrency(value: value!)
}
}
} catch let jsonError {
print(jsonError.localizedDescription)
}
}
}
task.resume()
}
func setCurrency(value: Float32) {
var color = NSColor.white
if let oldValue = self.oldValue {
if oldValue < value {
color = NSColor.green
} else if oldValue > value {
color = NSColor.red
}
}
self.oldValue = value
let title = String(format: "%@%.2f", self.prefix, value)
let regularFont = button.attributedTitle.attribute(.font, at: 0, effectiveRange: nil) as? NSFont ?? NSFont.systemFont(ofSize: 15)
let newTitle = NSMutableAttributedString(string: title as String, attributes: [.foregroundColor: color, .font: regularFont])
newTitle.setAlignment(.center, range: NSRange(location: 0, length: title.count))
button.attributedTitle = newTitle
}
}
+144
View File
@@ -0,0 +1,144 @@
//
// InputSourceBarItem.swift
// MTMR
//
// Created by Daniel Apatin on 22.04.2018.
// Copyright © 2018 Anton Palgunov. All rights reserved.
//
import Cocoa
class InputSourceBarItem: CustomButtonTouchBarItem {
fileprivate var notificationCenter: CFNotificationCenter
let buttonSize = NSSize(width: 21, height: 21)
init(identifier: NSTouchBarItem.Identifier, onTap: @escaping () -> (), onLongTap: @escaping () -> ()) {
notificationCenter = CFNotificationCenterGetDistributedCenter();
super.init(identifier: identifier, title: "⏳", onTap: onTap, onLongTap: onLongTap)
observeIputSourceChangedNotification();
textInputSourceDidChange()
self.button.cell?.action = #selector(switchInputSource)
self.button.action = #selector(switchInputSource)
self.button.frame.size = buttonSize
self.button.bounds.size = buttonSize
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
deinit {
CFNotificationCenterRemoveEveryObserver(notificationCenter, UnsafeRawPointer(Unmanaged.passUnretained(self).toOpaque()));
}
@objc override func handleGestureSingle(gr: NSClickGestureRecognizer) {
super.handleGestureSingle(gr: gr)
switchInputSource()
}
@objc public func textInputSourceDidChange() {
let currentSource = TISCopyCurrentKeyboardInputSource().takeUnretainedValue()
var iconImage: NSImage? = nil
if let imageURL = currentSource.iconImageURL {
if let image = NSImage(contentsOf: imageURL) {
iconImage = image
}
}
if iconImage == nil, let iconRef = currentSource.iconRef {
iconImage = NSImage(iconRef: iconRef)
}
if (iconImage != nil) {
self.button.cell?.image = iconImage
self.button.cell?.image?.size = buttonSize
} else {
self.button.title = currentSource.name
}
}
@objc private func switchInputSource() {
var inputSources: [TISInputSource] = []
let currentSource = TISCopyCurrentKeyboardInputSource().takeUnretainedValue()
let inputSourceNSArray = TISCreateInputSourceList(nil, false).takeRetainedValue() as NSArray
let elements = inputSourceNSArray as! [TISInputSource]
inputSources = elements.filter({
$0.category == TISInputSource.Category.keyboardInputSource && $0.isSelectable
})
for item in inputSources {
if (item.id != currentSource.id) {
TISSelectInputSource(item)
break
}
}
}
@objc public func observeIputSourceChangedNotification(){
let callback: CFNotificationCallback = { center, observer, name, object, info in
let mySelf = Unmanaged<InputSourceBarItem>.fromOpaque(observer!).takeUnretainedValue()
mySelf.textInputSourceDidChange()
}
CFNotificationCenterAddObserver(notificationCenter,
UnsafeRawPointer(Unmanaged.passUnretained(self).toOpaque()),
callback,
kTISNotifySelectedKeyboardInputSourceChanged,
nil,
.deliverImmediately)
}
}
extension TISInputSource {
enum Category {
static var keyboardInputSource: String {
return kTISCategoryKeyboardInputSource as String
}
}
private func getProperty(_ key: CFString) -> AnyObject? {
let cfType = TISGetInputSourceProperty(self, key)
if (cfType != nil) {
return Unmanaged<AnyObject>.fromOpaque(cfType!).takeUnretainedValue()
} else {
return nil
}
}
var id: String {
return getProperty(kTISPropertyInputSourceID) as! String
}
var name: String {
return getProperty(kTISPropertyLocalizedName) as! String
}
var category: String {
return getProperty(kTISPropertyInputSourceCategory) as! String
}
var isSelectable: Bool {
return getProperty(kTISPropertyInputSourceIsSelectCapable) as! Bool
}
var sourceLanguages: [String] {
return getProperty(kTISPropertyInputSourceLanguages) as! [String]
}
var iconImageURL: URL? {
return getProperty(kTISPropertyIconImageURL) as! URL?
}
var iconRef: IconRef? {
return OpaquePointer(TISGetInputSourceProperty(self, kTISPropertyIconRef)) as IconRef?
}
}
@@ -1,13 +1,13 @@
import Cocoa import Cocoa
class TimeTouchBarItem: NSCustomTouchBarItem { class TimeTouchBarItem: CustomButtonTouchBarItem {
private let dateFormatter = DateFormatter() private let dateFormatter = DateFormatter()
private var timer: Timer! private var timer: Timer!
private let button = NSButton(title: "", target: nil, action: nil) // private let button = NSButton(title: "", target: nil, action: nil)
init(identifier: NSTouchBarItem.Identifier, formatTemplate: String) { init(identifier: NSTouchBarItem.Identifier, formatTemplate: String, onTap: @escaping () -> (), onLongTap: @escaping () -> ()) {
dateFormatter.setLocalizedDateFormatFromTemplate(formatTemplate) dateFormatter.setLocalizedDateFormatFromTemplate(formatTemplate)
super.init(identifier: identifier) super.init(identifier: identifier, title: " ", onTap: onTap, onLongTap: onLongTap)
timer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(updateTime), userInfo: nil, repeats: true) timer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(updateTime), userInfo: nil, repeats: true)
self.view = button self.view = button
button.bezelColor = .clear button.bezelColor = .clear
@@ -14,9 +14,7 @@ class VolumeViewController: NSCustomTouchBarItem {
mScope: kAudioDevicePropertyScopeOutput, mScope: kAudioDevicePropertyScopeOutput,
mElement: kAudioObjectPropertyElementMaster) mElement: kAudioObjectPropertyElementMaster)
addListenerBlock(listenerBlock: audioObjectPropertyListenerBlock, AudioObjectAddPropertyListenerBlock(defaultDeviceID, &forPropertyAddress, nil, audioObjectPropertyListenerBlock)
onAudioObjectID: defaultDeviceID,
forPropertyAddress: &forPropertyAddress)
if (image == nil) { if (image == nil) {
sliderItem = CustomSlider() sliderItem = CustomSlider()
@@ -32,27 +30,9 @@ class VolumeViewController: NSCustomTouchBarItem {
self.view = sliderItem self.view = sliderItem
} }
func addListenerBlock( listenerBlock: @escaping AudioObjectPropertyListenerBlock, onAudioObjectID: AudioObjectID, forPropertyAddress: UnsafePointer<AudioObjectPropertyAddress>) {
if (kAudioHardwareNoError != AudioObjectAddPropertyListenerBlock(onAudioObjectID, forPropertyAddress, nil, listenerBlock)) {
print("Error calling: AudioObjectAddPropertyListenerBlock") }
}
func audioObjectPropertyListenerBlock (numberAddresses: UInt32, addresses: UnsafePointer<AudioObjectPropertyAddress>) { func audioObjectPropertyListenerBlock (numberAddresses: UInt32, addresses: UnsafePointer<AudioObjectPropertyAddress>) {
var index: UInt32 = 0 DispatchQueue.main.async {
while index < numberAddresses { self.sliderItem.floatValue = self.getInputGain() * 100
let address: AudioObjectPropertyAddress = addresses[Int(index)]
switch address.mSelector {
case kAudioHardwareServiceDeviceProperty_VirtualMasterVolume:
DispatchQueue.main.async {
self.sliderItem.floatValue = self.getInputGain() * 100
}
default:
print("We didn't expect this!")
}
index += 1
} }
} }
+141
View File
@@ -0,0 +1,141 @@
//
// WeatherBarItem.swift
// MTMR
//
// Created by Daniel Apatin on 18.04.2018.
// Copyright © 2018 Anton Palgunov. All rights reserved.
//
import Cocoa
import CoreLocation
class WeatherBarItem: CustomButtonTouchBarItem, CLLocationManagerDelegate {
private let activity: NSBackgroundActivityScheduler
private var units: String
private var api_key: String
private var units_str = "°F"
private var prev_location: CLLocation!
private var location: CLLocation!
private let iconsImages = ["01d": "☀️", "01n": "☀️", "02d": "⛅️", "02n": "⛅️", "03d": "☁️", "03n": "☁️", "04d": "☁️", "04n": "☁️", "09d": "⛅️", "09n": "⛅️", "10d": "🌦", "10n": "🌦", "11d": "🌩", "11n": "🌩", "13d": "❄️", "13n": "❄️", "50d": "🌫", "50n": "🌫"]
private let iconsText = ["01d": "☀", "01n": "☀", "02d": "☁", "02n": "☁", "03d": "☁", "03n": "☁", "04d": "☁", "04n": "☁", "09d": "☂", "09n": "☂", "10d": "☂", "10n": "☂", "11d": "☈", "11n": "☈", "13d": "☃", "13n": "☃", "50d": "♨", "50n": "♨"]
private var iconsSource: Dictionary<String, String>
private var manager:CLLocationManager!
init(identifier: NSTouchBarItem.Identifier, interval: TimeInterval, units: String, api_key: String, icon_type: String? = "text", onTap: @escaping () -> (), onLongTap: @escaping () -> ()) {
activity = NSBackgroundActivityScheduler(identifier: "\(identifier.rawValue).updatecheck")
activity.interval = interval
self.units = units
self.api_key = api_key
if self.units == "metric" {
units_str = "°C"
}
if self.units == "imperial" {
units_str = "°F"
}
if icon_type == "images" {
iconsSource = iconsImages
} else {
iconsSource = iconsText
}
super.init(identifier: identifier, title: "⏳", onTap: onTap, onLongTap: onLongTap)
self.view = button
let status = CLLocationManager.authorizationStatus()
if status == .restricted || status == .denied {
print("User permission not given")
return
}
if !CLLocationManager.locationServicesEnabled() {
print("Location services not enabled");
return
}
activity.repeats = true
activity.qualityOfService = .utility
activity.schedule { (completion: NSBackgroundActivityScheduler.CompletionHandler) in
self.updateWeather()
completion(NSBackgroundActivityScheduler.Result.finished)
}
updateWeather()
manager = CLLocationManager()
manager.delegate = self
manager.desiredAccuracy = kCLLocationAccuracyHundredMeters
manager.startUpdatingLocation()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
@objc func updateWeather() {
if self.location != nil {
let urlRequest = URLRequest(url: URL(string: "https://api.openweathermap.org/data/2.5/weather?lat=\(location.coordinate.latitude)&lon=\(location.coordinate.longitude)&units=\(self.units)&appid=\(self.api_key)")!)
let task = URLSession.shared.dataTask(with: urlRequest) { (data, response, error) in
if error == nil {
do {
let json = try JSONSerialization.jsonObject(with: data!, options: .mutableContainers) as! [String : AnyObject]
// print(json)
var temperature: Int!
var condition_icon = ""
if let main = json["main"] as? [String : AnyObject] {
if let temp = main["temp"] as? Double {
temperature = Int(temp)
}
}
if let weather = json["weather"] as? NSArray, let item = weather[0] as? NSDictionary {
let icon = item["icon"] as! String
if let test = self.iconsSource[icon] {
condition_icon = test
}
}
if temperature != nil {
DispatchQueue.main.async {
self.setWeather(text: "\(condition_icon) \(temperature!)\(self.units_str)")
}
}
} catch let jsonError {
print(jsonError.localizedDescription)
}
}
}
task.resume()
}
}
func setWeather(text: String) {
button.title = text
}
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
let lastLocation = locations.last!
self.location = lastLocation
if prev_location == nil {
updateWeather()
}
prev_location = lastLocation
}
func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
print(error);
}
func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) {
// print("inside didChangeAuthorization ");
updateWeather()
}
}
+4 -1
View File
@@ -66,11 +66,14 @@
{ "type": "previous", "width": 44 }, { "type": "previous", "width": 44 },
{ "type": "play", "width": 44 }, { "type": "play", "width": 44 },
{ "type": "next", "width": 44 }, { "type": "next", "width": 44 },
{ "type": "weather", "refreshInterval": 1800 }, { "type": "weather", "icon_type": "images", "units": "metric" },
{ "type": "currency", "from": "BTC", "to": "USD" },
{ "type": "sleep", "width": 44 }, { "type": "sleep", "width": 44 },
{ "type": "mute", "width": 40, "align": "right" },
{ "type": "volumeDown", "width": 34, "align": "right" }, { "type": "volumeDown", "width": 34, "align": "right" },
{ "type": "volume", "width": 60, "align": "right" }, { "type": "volume", "width": 60, "align": "right" },
{ "type": "volumeUp", "width": 34, "align": "right" }, { "type": "volumeUp", "width": 34, "align": "right" },
{ "type": "inputsource", "align": "right" },
{ "type": "battery", "align": "right" }, { "type": "battery", "align": "right" },
{ "type": "timeButton", "align": "right" } { "type": "timeButton", "align": "right" }
] ]
+43 -10
View File
@@ -5,9 +5,10 @@
# My TouchBar. My rules # My TouchBar. My rules
[![GitHub release](https://img.shields.io/github/release/toxblh/MTMR.svg)](https://github.com/Toxblh/MTMR/releases) [![GitHub release](https://img.shields.io/github/release/toxblh/MTMR.svg)](https://github.com/Toxblh/MTMR/releases)
[![license](https://img.shields.io/github/license/Toxblh/MTMR.svg)](https://github.com/Toxblh/MTMR/blob/master/LICENSE) [![Total downloads](https://img.shields.io/github/downloads/Toxblh/MTMR/total.svg)](https://github.com/Toxblh/MTMR/releases/latest) ![minimal system requirements](https://img.shields.io/badge/required-macOS%2010.12.2-blue.svg)
<p align="center"> <p align="center">
<img src="Resources/TouchBar-v0.6.png"> <img src="Resources/TouchBar-v0.8.1.png">
</p> </p>
My the idea is to create the program like a platform for plugins for customization TouchBar. I very like BTT and a full custom TouchBar (my [BTT preset](https://github.com/Toxblh/btt-touchbar-preset)). And I want to create it. And it's my the first Swift project for MacOS :) My the idea is to create the program like a platform for plugins for customization TouchBar. I very like BTT and a full custom TouchBar (my [BTT preset](https://github.com/Toxblh/btt-touchbar-preset)). And I want to create it. And it's my the first Swift project for MacOS :)
@@ -34,7 +35,7 @@ My the idea is to create the program like a platform for plugins for customizati
- [ ] System for autoupdate (maybe https://sparkle-project.org/) - [ ] System for autoupdate (maybe https://sparkle-project.org/)
Settings: Settings:
- [ ] Intarface for plugins and export like presets - [ ] Interface for plugins and export like presets
- [ ] Startup at login - [ ] Startup at login
- [ ] Show on/off in Dock - [ ] Show on/off in Dock
- [ ] Show on/off in StatusBar - [ ] Show on/off in StatusBar
@@ -45,10 +46,8 @@ Maybe:
## Installation ## Installation
1. Download last release [Releases](https://github.com/Toxblh/MTMR/releases) - Download last [release](https://github.com/Toxblh/MTMR/releases)
2. Unzip - Or via Homebrew `brew cask install mtmr`
3. Open MTMR
4. Open preset `open ~/Library/Application\ Support/MTMR/items.json` and customize it. Restart MTMR to apply changes.
## Preset ## Preset
@@ -62,6 +61,14 @@ File for customize your preset for MTMR: `open ~/Library/Application\ Support/MT
- brightnessDown - brightnessDown
- volumeDown - volumeDown
- volumeUp - volumeUp
- mute
- dock
> Native Plugins
- battery
- currency
- weather
- inputsource
> Media Keys > Media Keys
- previous - previous
@@ -69,8 +76,6 @@ File for customize your preset for MTMR: `open ~/Library/Application\ Support/MT
- next - next
> AppleScript plugins > AppleScript plugins
- weather
- battery
- sleep - sleep
- displaySleep - displaySleep
@@ -105,8 +110,30 @@ File for customize your preset for MTMR: `open ~/Library/Application\ Support/MT
"formatTemplate": "HH:mm" //optional "formatTemplate": "HH:mm" //optional
``` ```
## Native plugins
- `weather`
> Provider: https://openweathermap.org Need allowance location service
```js
"type": "weather",
"refreshInterval": 600,
"units": "metric", // or imperial
"icon_type": "text" // or images
"api_key": "" // you can get the key on openweather
```
- `currency`
> Provider: https://coinbase.com
```js
"type": "currency",
"refreshInterval": 600,
"align": "right",
"from": "BTC",
"to": "USD",
```
## Actions: ## Actions:
- `hidKey` - `hidKey`
> https://github.com/aosm/IOHIDFamily/blob/master/IOHIDSystem/IOKit/hidsystem/ev_keymap.h use only numbers
```json ```json
"action": "hidKey", "action": "hidKey",
"keycode": 53, "keycode": 53,
@@ -131,11 +158,17 @@ File for customize your preset for MTMR: `open ~/Library/Application\ Support/MT
```js ```js
"action": "shellScript", "action": "shellScript",
"executablePath": "/usr/bin/pmset", "executablePath": "/usr/bin/pmset",
"shellArguments": "sleepnow", // optional "shellArguments": ["sleepnow"], // optional
``` ```
## Additional paramaters: - `openUrl`
```js
"action": "openUrl",
"url": "https://google.com",
```
## Additional parameters:
- `width` allow to restrict how much room a particular button will take - `width` allow to restrict how much room a particular button will take
```json ```json
Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

+2 -1
View File
@@ -18,6 +18,7 @@ rm -r App.xcarchive
# Prerequisite: npm i -g create-dmg # Prerequisite: npm i -g create-dmg
create-dmg "${NAME}.app" create-dmg "${NAME}.app"
VERSION=`mdls -raw -name kMDItemVersion MTMR.app` VERSION=`mdls -raw -name kMDItemVersion ${NAME}.app`
echo $VERSION echo $VERSION
zip -r "${NAME}v${VERSION}.zip" "${NAME}.app" zip -r "${NAME}v${VERSION}.zip" "${NAME}.app"