From 55f96f74caea97a16a3d22453b5c2ee3e0892a70 Mon Sep 17 00:00:00 2001 From: Jim Myhrberg Date: Wed, 6 May 2020 20:39:48 +0100 Subject: [PATCH] Add PushToTalk Spoon to Hammerspoon config --- hammerspoon/Makefile | 1 + hammerspoon/Spoons/PushToTalk.spoon/docs.json | 100 ++++++++++++ hammerspoon/Spoons/PushToTalk.spoon/init.lua | 147 ++++++++++++++++++ hammerspoon/Spoons/PushToTalk.spoon/muted.pdf | Bin 0 -> 1283 bytes .../Spoons/PushToTalk.spoon/record.pdf | Bin 0 -> 1059 bytes hammerspoon/Spoons/PushToTalk.spoon/speak.pdf | 68 ++++++++ .../Spoons/PushToTalk.spoon/unrecord.pdf | 69 ++++++++ hammerspoon/init.lua | 5 + 8 files changed, 390 insertions(+) create mode 100644 hammerspoon/Spoons/PushToTalk.spoon/docs.json create mode 100644 hammerspoon/Spoons/PushToTalk.spoon/init.lua create mode 100644 hammerspoon/Spoons/PushToTalk.spoon/muted.pdf create mode 100644 hammerspoon/Spoons/PushToTalk.spoon/record.pdf create mode 100644 hammerspoon/Spoons/PushToTalk.spoon/speak.pdf create mode 100644 hammerspoon/Spoons/PushToTalk.spoon/unrecord.pdf diff --git a/hammerspoon/Makefile b/hammerspoon/Makefile index 5ee5419..cb6192d 100644 --- a/hammerspoon/Makefile +++ b/hammerspoon/Makefile @@ -70,3 +70,4 @@ endef $(eval $(call dep-file,inspect.lua,"https://github.com/kikito/inspect.lua/raw/v3.1.0/inspect.lua")) $(eval $(call dep-spoon,RoundedCorners,"https://github.com/Hammerspoon/Spoons/raw/master/Spoons/RoundedCorners.spoon.zip")) $(eval $(call dep-spoon,HeadphoneAutoPause,"https://github.com/Hammerspoon/Spoons/raw/master/Spoons/HeadphoneAutoPause.spoon.zip")) +$(eval $(call dep-spoon,PushToTalk,"https://github.com/Hammerspoon/Spoons/raw/master/Spoons/PushToTalk.spoon.zip")) diff --git a/hammerspoon/Spoons/PushToTalk.spoon/docs.json b/hammerspoon/Spoons/PushToTalk.spoon/docs.json new file mode 100644 index 0000000..9501442 --- /dev/null +++ b/hammerspoon/Spoons/PushToTalk.spoon/docs.json @@ -0,0 +1,100 @@ +[ + { + "Command": [], + "Constant": [], + "Constructor": [], + "Deprecated": [], + "Field": [], + "Function": [], + "Method": [ + { + "def": "PushToTalk:init()", + "desc": "Starts menu and key watcher", + "doc": "Starts menu and key watcher", + "name": "init", + "signature": "PushToTalk:init()", + "stripped_doc": "", + "type": "Method" + }, + { + "def": "PushToTalk:stop()", + "desc": "Stops PushToTalk", + "doc": "Stops PushToTalk", + "name": "stop", + "signature": "PushToTalk:stop()", + "stripped_doc": "", + "type": "Method" + }, + { + "def": "PushToTalk:toggleStates()", + "desc": "Cycle states in order", + "doc": "Cycle states in order\n\nParameters:\n * states - A array of states to toggle. For example: `{'push-to-talk', 'release-to-talk'}`", + "name": "toggleStates", + "parameters": [ + " * states - A array of states to toggle. For example: `{'push-to-talk', 'release-to-talk'}`" + ], + "signature": "PushToTalk:toggleStates()", + "stripped_doc": "", + "type": "Method" + } + ], + "Variable": [ + { + "def": "PushToTalk.app_switcher", + "desc": "Takes mapping from application name to mic state.", + "doc": "Takes mapping from application name to mic state.\nFor example this `{ ['zoom.us'] = 'push-to-talk' }` will switch mic to `push-to-talk` state when Zoom app starts.", + "name": "app_switcher", + "signature": "PushToTalk.app_switcher", + "stripped_doc": "For example this `{ ['zoom.us'] = 'push-to-talk' }` will switch mic to `push-to-talk` state when Zoom app starts.", + "type": "Variable" + } + ], + "desc": "Implements push-to-talk and push-to-mute functionality with `fn` key.", + "doc": "Implements push-to-talk and push-to-mute functionality with `fn` key.\nI implemented this after reading Gitlab remote handbook https://about.gitlab.com/handbook/communication/ about Shush utility.\n\nMy workflow:\n\nWhen Zoom starts, PushToTalk automatically changes mic state from `default`\nto `push-to-talk`, so I need to press `fn` key to unmute myself and speak.\nIf I need to actively chat in group meeting or it's one-on-one meeting,\nI'm switching to `push-to-mute` state, so mic will be unmute by default and `fn` key mutes it.\n\nPushToTalk has menubar with colorful icons so you can easily see current mic state.\n\nSample config: `spoon.SpoonInstall:andUse(\"PushToTalk\", {start = true, config = { app_switcher = { ['zoom.us'] = 'push-to-talk' }}})`\nand separate keybinding to toggle states with lambda function `function() spoon.PushToTalk.toggleStates({'push-to-talk', 'release-to-talk'}) end`\n\nCheck out my config: https://github.com/skrypka/hammerspoon_config/blob/master/init.lua", + "items": [ + { + "def": "PushToTalk.app_switcher", + "desc": "Takes mapping from application name to mic state.", + "doc": "Takes mapping from application name to mic state.\nFor example this `{ ['zoom.us'] = 'push-to-talk' }` will switch mic to `push-to-talk` state when Zoom app starts.", + "name": "app_switcher", + "signature": "PushToTalk.app_switcher", + "stripped_doc": "For example this `{ ['zoom.us'] = 'push-to-talk' }` will switch mic to `push-to-talk` state when Zoom app starts.", + "type": "Variable" + }, + { + "def": "PushToTalk:init()", + "desc": "Starts menu and key watcher", + "doc": "Starts menu and key watcher", + "name": "init", + "signature": "PushToTalk:init()", + "stripped_doc": "", + "type": "Method" + }, + { + "def": "PushToTalk:stop()", + "desc": "Stops PushToTalk", + "doc": "Stops PushToTalk", + "name": "stop", + "signature": "PushToTalk:stop()", + "stripped_doc": "", + "type": "Method" + }, + { + "def": "PushToTalk:toggleStates()", + "desc": "Cycle states in order", + "doc": "Cycle states in order\n\nParameters:\n * states - A array of states to toggle. For example: `{'push-to-talk', 'release-to-talk'}`", + "name": "toggleStates", + "parameters": [ + " * states - A array of states to toggle. For example: `{'push-to-talk', 'release-to-talk'}`" + ], + "signature": "PushToTalk:toggleStates()", + "stripped_doc": "", + "type": "Method" + } + ], + "name": "PushToTalk", + "stripped_doc": "I implemented this after reading Gitlab remote handbook https://about.gitlab.com/handbook/communication/ about Shush utility.\n\nMy workflow:\n\nWhen Zoom starts, PushToTalk automatically changes mic state from `default`\nto `push-to-talk`, so I need to press `fn` key to unmute myself and speak.\nIf I need to actively chat in group meeting or it's one-on-one meeting,\nI'm switching to `push-to-mute` state, so mic will be unmute by default and `fn` key mutes it.\n\nPushToTalk has menubar with colorful icons so you can easily see current mic state.\n\nSample config: `spoon.SpoonInstall:andUse(\"PushToTalk\", {start = true, config = { app_switcher = { ['zoom.us'] = 'push-to-talk' }}})`\nand separate keybinding to toggle states with lambda function `function() spoon.PushToTalk.toggleStates({'push-to-talk', 'release-to-talk'}) end`\n\nCheck out my config: https://github.com/skrypka/hammerspoon_config/blob/master/init.lua", + "submodules": [], + "type": "Module" + } +] \ No newline at end of file diff --git a/hammerspoon/Spoons/PushToTalk.spoon/init.lua b/hammerspoon/Spoons/PushToTalk.spoon/init.lua new file mode 100644 index 0000000..240c175 --- /dev/null +++ b/hammerspoon/Spoons/PushToTalk.spoon/init.lua @@ -0,0 +1,147 @@ +--- === PushToTalk === +--- +--- Implements push-to-talk and push-to-mute functionality with `fn` key. +--- I implemented this after reading Gitlab remote handbook https://about.gitlab.com/handbook/communication/ about Shush utility. +--- +--- My workflow: +--- +--- When Zoom starts, PushToTalk automatically changes mic state from `default` +--- to `push-to-talk`, so I need to press `fn` key to unmute myself and speak. +--- If I need to actively chat in group meeting or it's one-on-one meeting, +--- I'm switching to `push-to-mute` state, so mic will be unmute by default and `fn` key mutes it. +--- +--- PushToTalk has menubar with colorful icons so you can easily see current mic state. +--- +--- Sample config: `spoon.SpoonInstall:andUse("PushToTalk", {start = true, config = { app_switcher = { ['zoom.us'] = 'push-to-talk' }}})` +--- and separate keybinding to toggle states with lambda function `function() spoon.PushToTalk.toggleStates({'push-to-talk', 'release-to-talk'}) end` +--- +--- Check out my config: https://github.com/skrypka/hammerspoon_config/blob/master/init.lua + +local obj = {} +obj.__index = obj + +-- Metadata +obj.name = "PushToTalk" +obj.version = "0.1" +obj.author = "Roman Khomenko " +obj.homepage = "https://github.com/Hammerspoon/Spoons" +obj.license = "MIT - https://opensource.org/licenses/MIT" + +obj.defaultState = 'unmute' + +obj.state = obj.defaultState +obj.pushed = false +--- PushToTalk.app_switcher +--- Variable +--- Takes mapping from application name to mic state. +--- For example this `{ ['zoom.us'] = 'push-to-talk' }` will switch mic to `push-to-talk` state when Zoom app starts. +obj.app_switcher = {} + +local function showState() + local device = hs.audiodevice.defaultInputDevice() + local muted = false + if obj.state == 'unmute' then + obj.menubar:setIcon(hs.spoons.resourcePath("speak.pdf")) + elseif obj.state == 'mute' then + obj.menubar:setIcon(hs.spoons.resourcePath("muted.pdf")) + muted = true + elseif obj.state == 'push-to-talk' then + if obj.pushed then + obj.menubar:setIcon(hs.spoons.resourcePath("record.pdf"), false) + else + obj.menubar:setIcon(hs.spoons.resourcePath("unrecord.pdf")) + muted = true + end + elseif obj.state == 'release-to-talk' then + if obj.pushed then + obj.menubar:setIcon(hs.spoons.resourcePath("unrecord.pdf")) + muted = true + else + obj.menubar:setIcon(hs.spoons.resourcePath("record.pdf"), false) + end + end + + device:setMuted(muted) +end + +function obj.setState(s) + obj.state = s + showState() +end + +obj.menutable = { + { title = "UnMuted", fn = function() obj.setState('unmute') end }, + { title = "Muted", fn = function() obj.setState('mute') end }, + { title = "Push-to-talk (fn)", fn = function() obj.setState('push-to-talk') end }, + { title = "Release-to-talk (fn)", fn = function() obj.setState('release-to-talk') end }, +} + +local function appWatcher(appName, eventType, appObject) + local new_app_state = obj.app_switcher[appName]; + if (new_app_state) then + if (eventType == hs.application.watcher.launching) then + obj.setState(new_app_state) + elseif (eventType == hs.application.watcher.terminated) then + obj.setState(obj.defaultState) + end + end +end + +local function eventTapWatcher(event) + device = hs.audiodevice.defaultInputDevice() + if event:getFlags()['fn'] then + obj.pushed = true + else + obj.pushed = false + end + showState() +end + +--- PushToTalk:init() +--- Method +--- Initial setup. It's empty currently +function obj:init() +end + +--- PushToTalk:init() +--- Method +--- Starts menu and key watcher +function obj:start() + self:stop() + obj.appWatcher = hs.application.watcher.new(appWatcher) + obj.appWatcher:start() + + obj.eventTapWatcher = hs.eventtap.new({hs.eventtap.event.types.flagsChanged}, eventTapWatcher) + obj.eventTapWatcher:start() + + obj.menubar = hs.menubar.new() + obj.menubar:setMenu(obj.menutable) + obj.setState(obj.state) +end + +--- PushToTalk:stop() +--- Method +--- Stops PushToTalk +function obj:stop() + if obj.appWatcher then obj.appWatcher:stop() end + if obj.eventTapWatcher then obj.eventTapWatcher:stop() end + if obj.menubar then obj.menubar:delete() end +end + +--- PushToTalk:toggleStates() +--- Method +--- Cycle states in order +--- +--- Parameters: +--- * states - A array of states to toggle. For example: `{'push-to-talk', 'release-to-talk'}` +function obj:toggleStates(states) + new_state = states[1] + for i, v in pairs(states) do + if v == obj.state then + new_state = states[(i % #states) + 1] + end + end + obj.setState(new_state) +end + +return obj diff --git a/hammerspoon/Spoons/PushToTalk.spoon/muted.pdf b/hammerspoon/Spoons/PushToTalk.spoon/muted.pdf new file mode 100644 index 0000000000000000000000000000000000000000..a600956583985a340353ca7a68b2be042cb615a6 GIT binary patch literal 1283 zcmY!laB6FYgQAB!PR+i#oJcm`qX!oF#2oSjLTf~-7c14-%h#Wt*==3<=y@u*|8zxX4}^A_ulDp6*D3>9xrP1Nw*fP?iXneTf6I1 zpY8frJ2KS*`+Ew%YHKJ>2&lSN-<$h3GTw3*DYCzUdRYS1oW#qOq4>-s^n}3vO*Y{?b4rN%hmU$mRJ3zsz)I z9y0Bd%By~xxm)Y(L@nNf$&as1ZSgFgD!oiE{Y57CY}-G(Y#FMQ4$PbGz9EaLzG~{c zFMs*g=l{|^jSWZu2=o&TfE)#VXGaA?1^whiAPJ0ZpahTvBB(j0aB~#&Ln;eW74!oV(^J7_ z1|$}x=9K`o!4i;fYD#9JQ+|a)G*HMu!O&FC+!RX2f^|6O=am4p6f1zzAtcec7v+~0 zAUO(R2Ury5&S0R;MTvRE1t9y9D`CQ(3MECQV3&YA;|x^gl3JFToEqfr1X2R=i+)gQ zaeir0a%!;xC`5vgd!rNJ)N? zf<|&;W>LO^p`M|Mo}qz)Mn*|Vft5Zmbb+GjMTrF&naRa^`9oo_u6iv*+446dF#4If=5n?4piJ3X6Mc|?;II}7h u=wwhl2j%CND1bZ;Nj{!=Y55AEhycfIaYoLZ zA{5b^_23VB5Pz@~i}s+_g2f`DUc_UmLTgX{P)|Zz@Xh9@A)XwTncZ*Zz4^ZPy>&%` z-OZ}IRdP-Jm{^rOpa6}Xk^Fv;`$;;%lhBG#R006G+e&f5a7`JU1c^!GMC#~}Y|e-= zEah(v9#MD9DmC-#msd9($i?O-n!@245~^tkhr)ZMEnn)bPn+YXX8L}9zxnRvLeSj# zYdUv(>&I{|c5J0?^3}){8oSoM|M#87j-|67*6+Qa`jdGuTD#n|@?0lRc9R>UbC+f= zwk__ft^0QVP1l~z>)x@0le_94)-I22d;2<>Z5|))zxrr#{PF#-({nE-W{) z>Dp3Xer@}LdH2E$c-x*yBpokO#y25WC=@GdRCS-&7hf&qQaIv6dEPU?Q8@Vh0>B{~ z3Vwhb=mZsH)4&U)|_4lmGw# literal 0 HcmV?d00001 diff --git a/hammerspoon/Spoons/PushToTalk.spoon/speak.pdf b/hammerspoon/Spoons/PushToTalk.spoon/speak.pdf new file mode 100644 index 0000000..e18d159 --- /dev/null +++ b/hammerspoon/Spoons/PushToTalk.spoon/speak.pdf @@ -0,0 +1,68 @@ +%PDF-1.4 +% +3 0 obj +<< /Length 4 0 R + /Filter /FlateDecode +>> +stream +x]In0 EreVS>(}'ǎ4j*\v0Tz,+t شz((yZ@mftexE'Ck9o:aӶ6 8s;ҩ HkדOuwC+FT{\CIeV6xzsm،@> + >> +>> +endobj +5 0 obj +<< /Type /Page + /Parent 1 0 R + /MediaBox [ 0 0 15.75 15.75 ] + /Contents 3 0 R + /Group << + /Type /Group + /S /Transparency + /I true + /CS /DeviceRGB + >> + /Resources 2 0 R +>> +endobj +1 0 obj +<< /Type /Pages + /Kids [ 5 0 R ] + /Count 1 +>> +endobj +6 0 obj +<< /Creator (cairo 1.14.10 (http://cairographics.org)) + /Producer (cairo 1.14.10 (http://cairographics.org)) +>> +endobj +7 0 obj +<< /Type /Catalog + /Pages 1 0 R +>> +endobj +xref +0 8 +0000000000 65535 f +0000000714 00000 n +0000000424 00000 n +0000000015 00000 n +0000000402 00000 n +0000000496 00000 n +0000000779 00000 n +0000000908 00000 n +trailer +<< /Size 8 + /Root 7 0 R + /Info 6 0 R +>> +startxref +960 +%%EOF diff --git a/hammerspoon/Spoons/PushToTalk.spoon/unrecord.pdf b/hammerspoon/Spoons/PushToTalk.spoon/unrecord.pdf new file mode 100644 index 0000000..7a4abf3 --- /dev/null +++ b/hammerspoon/Spoons/PushToTalk.spoon/unrecord.pdf @@ -0,0 +1,69 @@ +%PDF-1.4 +% +3 0 obj +<< /Length 4 0 R + /Filter /FlateDecode +>> +stream +x]= +A ]Lv{AX-BFP- m Cۛ&a!]i 7h,4qg3:pp+BP$ jNRߘ:E֬k&ckm-?Ϙh/C3b +endstream +endobj +4 0 obj + 149 +endobj +2 0 obj +<< + /ExtGState << + /a0 << /CA 1 /ca 1 >> + >> +>> +endobj +5 0 obj +<< /Type /Page + /Parent 1 0 R + /MediaBox [ 0 0 15.75 15.75 ] + /Contents 3 0 R + /Group << + /Type /Group + /S /Transparency + /I true + /CS /DeviceRGB + >> + /Resources 2 0 R +>> +endobj +1 0 obj +<< /Type /Pages + /Kids [ 5 0 R ] + /Count 1 +>> +endobj +6 0 obj +<< /Creator (cairo 1.14.10 (http://cairographics.org)) + /Producer (cairo 1.14.10 (http://cairographics.org)) +>> +endobj +7 0 obj +<< /Type /Catalog + /Pages 1 0 R +>> +endobj +xref +0 8 +0000000000 65535 f +0000000553 00000 n +0000000263 00000 n +0000000015 00000 n +0000000241 00000 n +0000000335 00000 n +0000000618 00000 n +0000000747 00000 n +trailer +<< /Size 8 + /Root 7 0 R + /Info 6 0 R +>> +startxref +799 +%%EOF diff --git a/hammerspoon/init.lua b/hammerspoon/init.lua index a7e7d4d..00c680b 100644 --- a/hammerspoon/init.lua +++ b/hammerspoon/init.lua @@ -28,6 +28,11 @@ hs.loadSpoon('HeadphoneAutoPause') spoon.HeadphoneAutoPause.autoResume = false spoon.HeadphoneAutoPause:start() +-- Enable push-to-talk microphone functionality when specific apps are running. +hs.loadSpoon('PushToTalk') +spoon.PushToTalk.app_switcher = { ['TeamSpeak 3'] = 'push-to-talk' } +spoon.PushToTalk:start() + -------------------------------------------------------------------------------- -- Host specific configuration --------------------------------------------------------------------------------