Global keyboard hook

Mac OSX specific forum
User avatar
mk-soft
Always Here
Always Here
Posts: 5335
Joined: Fri May 12, 2006 6:51 pm
Location: Germany

Re: Global keyboard hook

Post by mk-soft »

Thanks for Hotkey example :D

here the complete HotKeyModifiers

Code: Select all

#cmdKeyBit = 8
#shiftKeyBit = 9
#alphaLockBit = 10
#optionKeyBit = 11
#controlKeyBit = 12
#rightShiftKeyBit = 13
#rightOptionKeyBit = 14
#rightControlKeyBit = 15
#cmdKey = 1 << #cmdKeyBit
#shiftKey = 1 << #shiftKeyBit
#alphaLock = 1 << #alphaLockBit
#optionKey = 1 << #optionKeyBit
#controlKey = 1 << #controlKeyBit
#rightShiftKey = 1 << #rightShiftKeyBit
#rightOptionKey = 1 << #rightOptionKeyBit
#rightControlKey = 1 << #rightControlKeyBit
GT :wink:
My Projects ThreadToGUI / OOP-BaseClass / EventDesigner V3
PB v3.30 / v5.75 - OS Mac Mini OSX 10.xx - VM Window Pro / Linux Ubuntu
Downloads on my Webspace / OneDrive
User avatar
deseven
Enthusiast
Enthusiast
Posts: 362
Joined: Wed Jan 12, 2011 3:48 pm
Location: Serbia
Contact:

Re: Global keyboard hook

Post by deseven »

Thanks Shardik, wilbert and mk-soft! I wrote a module for that based on your code:
http://forums.purebasic.com/english/vie ... 14&t=64532
NY152
User
User
Posts: 29
Joined: Sun May 14, 2006 12:33 am

Re: Global keyboard hook

Post by NY152 »

CocoaMessage() is not functon ... etc

EDIT:

CocoaMessage is a MACOX function sorry ...

Windows equivalent exists ?
.:NY152:.
User avatar
deseven
Enthusiast
Enthusiast
Posts: 362
Joined: Wed Jan 12, 2011 3:48 pm
Location: Serbia
Contact:

Re: Global keyboard hook

Post by deseven »

well, yes
vwidmer
Enthusiast
Enthusiast
Posts: 282
Joined: Mon Jan 20, 2014 6:32 pm

Re: Global keyboard hook

Post by vwidmer »

Should this code still be working it doesnt seem to work for me on 10.13.6

Did some thing change?

Thanks
wilbert wrote:Here's a modification also showing the keyboard related flags

Code: Select all

EnableExplicit

#KeyDownMask      = 1 << 10
#KeyUpMask        = 1 << 11
#FlagsChangedMask = 1 << 12

#kCGKeyboardEventKeycode = 9

ImportC ""
  CGEventTapCreate(tap, place, options, eventsOfInterest.q, callback, refcon)
  CGEventGetFlags.q(event)
  CGEventGetIntegerValueField.q(event, field)
  CGEventKeyboardGetUnicodeString(event, maxStringLength, *actualStringLength, *unicodeString)
EndImport

Define eventTap

ProcedureC eventTapFunction(proxy, type, event, refcon)
  Protected keyCode = CGEventGetIntegerValueField(event, #kCGKeyboardEventKeycode)
  Protected keyFlags = CGEventGetFlags(event) >> 16 & 255
  Protected unicodeBuffer.q, bufferLen.i = 4
  CGEventKeyboardGetUnicodeString(event, bufferLen, @bufferLen, @unicodeBuffer)
  Protected text.s = PeekS(@unicodeBuffer, bufferLen, #PB_Unicode)
  
  SetGadgetText(0, "keyCode : " + Str(keyCode) + #LF$ + "keyFlags : " + RSet(Bin(keyFlags), 8, "0") + #LF$ + "string : " + text)
EndProcedure


If OpenWindow(0, 0, 0, 220, 90, "keyCode tap", #PB_Window_SystemMenu | #PB_Window_ScreenCentered)
  StickyWindow(0, 1)
  StringGadget(0, 10, 10, 200, 70, "")
  
  eventTap = CGEventTapCreate(0, 0, 1, #KeyDownMask | #FlagsChangedMask, @eventTapFunction(), 0)
  If eventTap
    CocoaMessage(0, CocoaMessage(0, 0, "NSRunLoop currentRunLoop"), "addPort:", eventTap, "forMode:$", @"kCFRunLoopDefaultMode")
  EndIf

  Repeat : Until WaitWindowEvent() = #PB_Event_CloseWindow
EndIf
And a practical example of responding to the key combination [fn] + [esc]

Code: Select all

EnableExplicit

#KeyDownMask      = 1 << 10
#FlagsChangedMask = 1 << 12

#kCGKeyboardEventKeycode = 9

ImportC ""
  CGEventTapCreate(tap, place, options, eventsOfInterest.q, callback, refcon)
  CGEventGetFlags.q(event)
  CGEventGetIntegerValueField.q(event, field)
EndImport

Define eventTap


Global SpeechSynthesizer = CocoaMessage(0, CocoaMessage(0, 0, "NSSpeechSynthesizer alloc"), "initWithVoice:", #nil)

ProcedureC eventTapFunction(proxy, type, event, refcon)
  Protected keyCode = CGEventGetIntegerValueField(event, #kCGKeyboardEventKeycode)
  Protected keyFlags = CGEventGetFlags(event) >> 16 & 255
  
  If keyCode = 53 And keyFlags & $80
    ; [fn] + [esc] pressed
    CocoaMessage(0, SpeechSynthesizer, "startSpeakingString:$", @"fn and esc pressed")
  EndIf
  
EndProcedure


If OpenWindow(0, 0, 0, 200, 30, "key tap", #PB_Window_SystemMenu | #PB_Window_Minimize | #PB_Window_NoActivate)
  
  eventTap = CGEventTapCreate(0, 0, 1, #KeyDownMask | #FlagsChangedMask, @eventTapFunction(), 0)
  If eventTap
    CocoaMessage(0, CocoaMessage(0, 0, "NSRunLoop currentRunLoop"), "addPort:", eventTap, "forMode:$", @"kCFRunLoopDefaultMode")
  EndIf

  Repeat : Until WaitWindowEvent() = #PB_Event_CloseWindow
EndIf
WARNING: I dont know what I am doing! I just put stuff here and there and sometimes like magic it works. So please improve on my code and post your changes so I can learn more. TIA
wilbert
PureBasic Expert
PureBasic Expert
Posts: 3870
Joined: Sun Aug 08, 2004 5:21 am
Location: Netherlands

Re: Global keyboard hook

Post by wilbert »

vwidmer wrote:Should this code still be working it doesnt seem to work for me on 10.13.6

Did some thing change?
It seems Apple made some security related changes and this code doesn't work anymore.
Apparently the app needs permission now.
Windows (x64)
Raspberry Pi OS (Arm64)
vwidmer
Enthusiast
Enthusiast
Posts: 282
Joined: Mon Jan 20, 2014 6:32 pm

Re: Global keyboard hook

Post by vwidmer »

Do u know which permissions are required? Or how to make work again?

I tried adding the program to Accessibility and running as Admin but neither seemed to work.
WARNING: I dont know what I am doing! I just put stuff here and there and sometimes like magic it works. So please improve on my code and post your changes so I can learn more. TIA
wilbert
PureBasic Expert
PureBasic Expert
Posts: 3870
Joined: Sun Aug 08, 2004 5:21 am
Location: Netherlands

Re: Global keyboard hook

Post by wilbert »

vwidmer wrote:Do u know which permissions are required? Or how to make work again?

I tried adding the program to Accessibility and running as Admin but neither seemed to work.
I haven't tried it but I read somewhere that besides adding the program to Accessibility, you also have to add

Code: Select all

<key>NSAppleEventsUsageDescription</key>
<string></string>
to the info.plist file.
Windows (x64)
Raspberry Pi OS (Arm64)
vwidmer
Enthusiast
Enthusiast
Posts: 282
Joined: Mon Jan 20, 2014 6:32 pm

Re: Global keyboard hook

Post by vwidmer »

So I got it working with admin permissions no need to do anything in Accessibility.

I didnt try to modify anything in the plist or anything so maybe it is possible to get it to run as user?

Here is my slightly modified version..

Code: Select all

EnableExplicit

#KeyDownMask      = 1 << 10
#KeyUpMask        = 1 << 11
#FlagsChangedMask = 1 << 12

;#kCGSessionEventTap = 0
#kCGHeadInsertEventTap = 0

#kCGKeyboardEventKeycode = 9

ImportC ""
  CGEventTapCreate(tap, place, options, eventsOfInterest.q, callback, refcon)
  CGEventGetFlags.q(event)
  CGEventGetIntegerValueField.q(event, field)
  CGEventKeyboardGetUnicodeString(event, maxStringLength, *actualStringLength, *unicodeString)
EndImport

Define eventTap
Define eventMask

Procedure.s convertKeyCode(keyCode)
  Select keyCode
    Case 0:   pReturn$ = "a"
    Case 1:   pReturn$ = "s"
    Case 2:   pReturn$ = "d"
    Case 3:   pReturn$ = "f"
    Case 4:   pReturn$ = "h"
    Case 5:   pReturn$ = "g"
    Case 6:   pReturn$ = "z"
    Case 7:   pReturn$ = "x"
    Case 8:   pReturn$ = "c"
    Case 9:   pReturn$ = "v"
    Case 11:  pReturn$ = "b"
    Case 12:  pReturn$ = "q"
    Case 13:  pReturn$ = "w"
    Case 14:  pReturn$ = "e"
    Case 15:  pReturn$ = "r"
    Case 16:  pReturn$ = "y"
    Case 17:  pReturn$ = "t"
    Case 18:  pReturn$ = "1"
    Case 19:  pReturn$ = "2"
    Case 20:  pReturn$ = "3"
    Case 21:  pReturn$ = "4"
    Case 22:  pReturn$ = "6"
    Case 23:  pReturn$ = "5"
    Case 24:  pReturn$ = "="
    Case 25:  pReturn$ = "9"
    Case 26:  pReturn$ = "7"
    Case 27:  pReturn$ = "-"
    Case 28:  pReturn$ = "8"
    Case 29:  pReturn$ = "0"
    Case 30:  pReturn$ = "]"
    Case 31:  pReturn$ = "o"
    Case 32:  pReturn$ = "u"
    Case 33:  pReturn$ = "["
    Case 34:  pReturn$ = "i"
    Case 35:  pReturn$ = "p"
    Case 37:  pReturn$ = "l"
    Case 38:  pReturn$ = "j"
    Case 39:  pReturn$ = "'"
    Case 40:  pReturn$ = "k"
    Case 41:  pReturn$ = ";"
    Case 42:  pReturn$ = "\\"
    Case 43:  pReturn$ = ","
    Case 44:  pReturn$ = "/"
    Case 45:  pReturn$ = "n"
    Case 46:  pReturn$ = "m"
    Case 47:  pReturn$ = "."
    Case 50:  pReturn$ = "`"
    Case 65:  pReturn$ = "[decimal]"
    Case 67:  pReturn$ = "[asterisk]"
    Case 69:  pReturn$ = "[plus]"
    Case 71:  pReturn$ = "[clear]"
    Case 75:  pReturn$ = "[divide]"
    Case 76:  pReturn$ = "[enter]"
    Case 78:  pReturn$ = "[hyphen]"
    Case 81:  pReturn$ = "[equals]"
    Case 82:  pReturn$ = "0"
    Case 83:  pReturn$ = "1"
    Case 84:  pReturn$ = "2"
    Case 85:  pReturn$ = "3"
    Case 86:  pReturn$ = "4"
    Case 87:  pReturn$ = "5"
    Case 88:  pReturn$ = "6"
    Case 89:  pReturn$ = "7"
    Case 91:  pReturn$ = "8"
    Case 92:  pReturn$ = "9"
    Case 36:  pReturn$ = "[return]"
    Case 48:  pReturn$ = "[tab]"
    Case 49:  pReturn$ = " "
    Case 51:  pReturn$ = "[del]"
    Case 53:  pReturn$ = "[esc]"
    Case 54:  pReturn$ = "[right-cmd]"
    Case 55:  pReturn$ = "[left-cmd]"
    Case 56:  pReturn$ = "[left-shift]"
    Case 57:  pReturn$ = "[caps]"
    Case 58:  pReturn$ = "[left-option]"
    Case 59:  pReturn$ = "[left-ctrl]"
    Case 60:  pReturn$ = "[right-shift]"
    Case 61:  pReturn$ = "[right-option]"
    Case 62:  pReturn$ = "[right-ctrl]"
    Case 63:  pReturn$ = "[fn]"
    Case 64:  pReturn$ = "[f17]"
    Case 72:  pReturn$ = "[volup]"
    Case 73:  pReturn$ = "[voldown]"
    Case 74:  pReturn$ = "[mute]"
    Case 79:  pReturn$ = "[f18]"
    Case 80:  pReturn$ = "[f19]"
    Case 90:  pReturn$ = "[f20]"
    Case 96:  pReturn$ = "[f5]"
    Case 97:  pReturn$ = "[f6]"
    Case 98:  pReturn$ = "[f7]"
    Case 99:  pReturn$ = "[f3]"
    Case 100: pReturn$ = "[f8]"
    Case 101: pReturn$ = "[f9]"
    Case 103: pReturn$ = "[f11]"
    Case 105: pReturn$ = "[f13]"
    Case 106: pReturn$ = "[f16]"
    Case 107: pReturn$ = "[f14]"
    Case 109: pReturn$ = "[f10]"
    Case 111: pReturn$ = "[f12]"
    Case 113: pReturn$ = "[f15]"
    Case 114: pReturn$ = "[help]"
    Case 115: pReturn$ = "[home]"
    Case 116: pReturn$ = "[pgup]"
    Case 117: pReturn$ = "[fwddel]"
    Case 118: pReturn$ = "[f4]"
    Case 119: pReturn$ = "[end]"
    Case 120: pReturn$ = "[f2]"
    Case 121: pReturn$ = "[pgdown]"
    Case 122: pReturn$ = "[f1]"
    Case 123: pReturn$ = "[left]"
    Case 124: pReturn$ = "[right]"
    Case 125: pReturn$ = "[down]"
    Case 126: pReturn$ = "[up]"
    Default
      pReturn$ = "unknown"
  EndSelect
  ProcedureReturn pReturn$
EndProcedure

ProcedureC eventTapFunction(proxy, type, event, refcon)
  Protected keyCode = CGEventGetIntegerValueField(event, #kCGKeyboardEventKeycode)
  Protected keyFlags = CGEventGetFlags(event) >> 16 & 255
  Protected unicodeBuffer.q, bufferLen.i = 4
  CGEventKeyboardGetUnicodeString(event, bufferLen, @bufferLen, @unicodeBuffer)
  Protected text.s = PeekS(@unicodeBuffer, bufferLen, #PB_Unicode)
  
  SetGadgetText(0, "keyCode : " + Str(keyCode) + #LF$ + "keyFlags : " + RSet(Bin(keyFlags), 8, "0") + #LF$ + "string : " + text)
EndProcedure

If OpenWindow(0, 0, 0, 220, 90, "keyCode tap", #PB_Window_SystemMenu | #PB_Window_ScreenCentered)
  StickyWindow(0, 1)
  StringGadget(0, 10, 10, 200, 70, "")
  
  eventMask = #KeyDownMask | #FlagsChangedMask
  eventTap = CGEventTapCreate(#kCGSessionEventTap, #kCGHeadInsertEventTap, 0, eventMask, @eventTapFunction(), #Null)
  
  If eventTap
    CocoaMessage(0, CocoaMessage(0, 0, "NSRunLoop currentRunLoop"), "addPort:", eventTap, "forMode:$", @"kCFRunLoopDefaultMode")
  EndIf
  
  Repeat : Delay(10): Until WaitWindowEvent() = #PB_Event_CloseWindow
EndIf
working admin on OSX v10.13.6
WARNING: I dont know what I am doing! I just put stuff here and there and sometimes like magic it works. So please improve on my code and post your changes so I can learn more. TIA
Rinzwind
Enthusiast
Enthusiast
Posts: 636
Joined: Wed Mar 11, 2009 4:06 pm
Location: NL

Re: Global keyboard hook

Post by Rinzwind »

Question about 'Accessibility'... I run vwidmer's example below.

When compiled to .app macOS will ask for permissions to accessibility. However, in the accessibility settings dialog the newly added entry is not correct. It points to the executable in the package (test1, points to /Contents/MacOS/test1, with a terminal/console icon), not the package itself (test1.app with show filename extensions on and package icon). So when user places a checkbox and restarts app, still nothing works and same permission question pops up. How to make it work correctly without having the user to drag the app itself to the accessibility dialog? Some plist magic?
jvzuck
User
User
Posts: 14
Joined: Sat Aug 15, 2020 6:06 pm

Re: Global keyboard hook

Post by jvzuck »

did this question ever get answered?
User avatar
deseven
Enthusiast
Enthusiast
Posts: 362
Joined: Wed Jan 12, 2011 3:48 pm
Location: Serbia
Contact:

Re: Global keyboard hook

Post by deseven »

Use a correct and unique app id, that's it.
jvzuck
User
User
Posts: 14
Joined: Sat Aug 15, 2020 6:06 pm

Re: Global keyboard hook

Post by jvzuck »

Where am I setting the App ID? Sorry, if this seems elementary. i haven't had to do that before.
User avatar
deseven
Enthusiast
Enthusiast
Posts: 362
Joined: Wed Jan 12, 2011 3:48 pm
Location: Serbia
Contact:

Re: Global keyboard hook

Post by deseven »

You should edit your Contents/Info.plist, find CFBundleIdentifier key there. Correct id should be in reverse-DNS format (com.example.myapp, doesn't have to be real domain). No signing is required. Also, run it as a standalone app (with or without the debugger), running from IDE is trickier and involve creating your own build process using external tools, because unfortunately PB doesn't handle app bundles correctly.
jvzuck
User
User
Posts: 14
Joined: Sat Aug 15, 2020 6:06 pm

Re: Global keyboard hook

Post by jvzuck »

Is there are good article here on preparing a PBasic app for others. I'm totally new to developing for Mac so I've never even heard of an app "bundle." When I install things, it often seems as though it's just a single file that's being copied into the applications directory so I naively assumed that's what I wold be doing. I don't want you to have to spell something out that's been covered many times so if the topic is detailed elsewhere, I'd love to see it.

You're saying I should not even do "create executable" from within the IDE, but should do that from the command line? Ugh. So frustrating. My app is working so nicely when I run it from within the IDE!

Jonathan
Post Reply