Process "ANY" text!

Mac OSX specific forum
User avatar
Piero
Addict
Addict
Posts: 1135
Joined: Sat Apr 29, 2023 6:04 pm
Location: Italy

Process "ANY" text!

Post by Piero »

1st, save this Automator Quick Action as "writesel":

Image
Shell Script:

Code: Select all

cat > ~/Documents/myseltext.txt
 
PB Text Processor:
 

Code: Select all

; Make an Automator Quick Action named #servicen (writesel) for selected text, with Run Shell Script:
; cat > ~/Documents/myseltext.txt

EnableExplicit

#codepb="/code" ; trick to paste on Forum!
#servicen= "writesel" ; service name (it will avoid using clipboard if available)
#maxtype= 255 ; typing max (use clipboard if too much chars to type); clipboard will be restored… (0 for no typing)
#addesc= #False ; #True ; type ESC after "typing" (close PB autocomplete… but better leave it off for other apps…)
Global say = #False ; no speech (see menu)
Global seltfile.s=GetUserDirectory(#PB_Directory_Documents)+"myseltext.txt" ; this file will be written and deleted!
Global seltxt.s
#NSSquareStatusBarItemLength = -2
Define StatusBar,StatusItem

ImportC ""
   CFRelease(CFTypeRef)
   CGEventKeyboardSetUnicodeString(keyEvent, len, uChar.s)
   CGEventCreateKeyboardEvent(CGEventSourceRef, CGVirtualKeyCode.U, KeyDown.L)
   CGEventPost(CGEventTapLocation, CGEventRef)
EndImport

Procedure Typestring(string.s)
   Protected keyEvent,i ; , *objPool = CocoaMessage(0, 0, "NSAutoreleasePool new")
   keyEvent = CGEventCreateKeyboardEvent(#nil, 0, #True)
   CGEventKeyboardSetUnicodeString(keyEvent, Len(string), string)
   CGEventPost(#kCGHIDEventTap, keyEvent)
   CFRelease(keyEvent) ; : CocoaMessage(0, *objPool, "release")
EndProcedure

Procedure.s Shell(ShellCommand$)
   Protected Output$, shell, tmper$
   shell = RunProgram("/bin/sh","","",
      #PB_Program_Open|#PB_Program_Write|#PB_Program_Read)
   If shell
      WriteProgramStringN(shell,ShellCommand$)
      WriteProgramData(shell,#PB_Program_Eof,0)
      While ProgramRunning(shell)
         If AvailableProgramOutput(shell)
            Output$ + ReadProgramString(shell)+ Chr(10)
         EndIf
      Wend
   EndIf
   ProcedureReturn Left(Output$,Len(Output$)-1)
EndProcedure

Procedure simpleShell(ShellCommand$)
   Protected shell = RunProgram("/bin/sh","","", #PB_Program_Open|#PB_Program_Write)
   WriteProgramStringN(shell,ShellCommand$) : WriteProgramData(shell,#PB_Program_Eof,0) : CloseProgram(shell)
EndProcedure

Procedure.s QP(str$) ; to quote paths and strings for shell
   ProcedureReturn "'" + ReplaceString(str$, "'", "'\''") + "'"
EndProcedure

Procedure.s GetText()
   Protected s.s,i.q
   simpleShell("rm -f "+QP(seltfile))
   s=Shell(~"osascript\n"+
      ~"tell application \"System Events\" to tell (1st application process whose frontmost is true)\n"+
      ~"try\n"+
      ~"perform action \"AXPick\" of menu item \""+#servicen+~"\" of menu 1 of menu item \"Services\""+
      ~" of menu 1 of menu bar item 2 of menu bar 1\n"+
      ~"return\n"+
      ~"on error\n"+
      ~"if "+say+~" > 0 then say \"No Service!\"\n"+
      ~"set cc to the clipboard\n"+
      ~"set the clipboard to \"\"\n"+
      ~"keystroke \"c\" using command down\n"+
      ~"delay .3\n"+
      ~"if (the clipboard) as text is \"\" then\n"+
      ~"set the clipboard to cc\n"+
      ~"return \"$%^&Empty$%^&\"\n"+
      ~"else\n"+
      ~"set r to (the clipboard) as text\n"+
      ~"set the clipboard to cc\n"+
      ~"return r\n"+
      ~"end\n"+
      ~"end\n"+
      ~"end\n")
   
   If s="$%^&Empty$%^&"
      simpleShell(~"osascript\n if "+say+~" > 0 then say \"I tried to use clipboard, but there was No Selection\"")
      ProcedureReturn "" 
   ElseIf s : simpleShell("rm -f "+QP(seltfile)) : ProcedureReturn s : EndIf
   i=ElapsedMilliseconds()
   Delay(200)
   s=Shell("cat "+QP(seltfile))
   While FileSize(seltfile)=-1
      Delay(200)
      s=Shell("cat "+QP(seltfile))
      if (ElapsedMilliseconds()-i)>1800 : break : EndIf
   Wend
   simpleShell("rm -f "+QP(seltfile))
   ProcedureReturn s
EndProcedure

Procedure.s TypeText(t.s)
   If t
      If Len(t) <= #maxtype And Not FindString(t,~"\n") And Not FindString(t,~"\r") ; fix for apps not "typing"…
         Typestring(t)
         If #addesc : simpleShell(~"osascript\ntell application \"System Events\" to key code 53") : EndIf
         simpleShell(~"osascript\n if "+say+~" > 0 then say \"I Used Typing!\"")
      Else
         Shell(~"osascript\n"+
            ~"tell application \"System Events\" to tell (1st application process whose frontmost is true)\n"+
            ~"set c to the clipboard\n"+
            ~"set the clipboard to \""+EscapeString(t)+~"\"\n"+
            ~"keystroke \"v\" using command down\n"+
            ~"delay .3\n"+
            ~"set the clipboard to c\n"+
            ~"end")
         simpleShell(~"osascript\n if "+say+~" > 0 then say \"Pasted!\"")
      EndIf
   EndIf
EndProcedure

Procedure Back(s.s,b.s,t.s="")
   TypeText(s+t+b) : Shell(~"osascript\nrepeat "+Len(b+t)+~" times\n tell app \"System Events\" to key code 123\nend")
   If t
      simpleShell(~"osascript\nrepeat "+Len(t)+~" times\ntell app \"System Events\" to key code 124 using shift down\nend")
   EndIf
EndProcedure

Macro mm(m,d=0) : MenuItem(MacroExpandedCount, m) : If d : DisableMenuItem(0,MacroExpandedCount,d) : EndIf : EndMacro
Macro mc(m) : mm(m,1) : EndMacro
Macro cc(m=) : Case MacroExpandedCount : seltxt=GetText() : If seltxt : m : EndIf : EndMacro

OpenWindow(0, #PB_Any, #PB_Any, 300, 100, "Text Replacements",#PB_Window_Invisible)
LoadFont(0,"Apple Color Emoji",23,#PB_Font_HighQuality)
Define ItemLength.CGFloat = 22 ; space taken in menu bar
CreateImage(0, 32, 32, 32, 0) ; RGBA(0, 0, 0, 0)
StartDrawing(ImageOutput(0))
DrawingMode(#PB_2DDrawing_NativeText)
DrawingFont(FontID(0))
DrawText(5, 1, "🍕")
StopDrawing()
StatusBar = CocoaMessage(0, 0, "NSStatusBar systemStatusBar")
If StatusBar
   StatusItem = CocoaMessage(0, CocoaMessage(0, StatusBar,"statusItemWithLength:", #NSSquareStatusBarItemLength), "retain")
   If StatusItem
      CocoaMessage(0, StatusItem, "setLength:@", @ItemLength)
      CocoaMessage(0, StatusItem, "setImage:", ImageID(0))
      CreatePopupMenu(0)
      MenuItem(0,"Talk") : SetMenuItemState(0,0,say)
      MenuBar() ; --------------------
      mc("Case") ; menu title
      mm(~"UPPERCASE\tCmd+Shift+U") ; ⌘⇧U in PB
      mm(~"lowercase\tCmd+Shift+L") ; ⌘⇧L in PB
      mm(~"tOGGLE cASE\tCmd+Shift+X") ; ⌘⇧X in PB
      mm("Title Case")
      MenuBar() ; --------------------
      mc("Enclose") ; menu title
      mm(~"\" \"")
      mm("( )")
      mm("' '")
      MenuBar() ; --------------------
      mc("BBCode") ; menu title
      mm("[code=purebasic]")
      mm("[size=200]")
      mm("[url= <Clipboard> ] <Selection> [/url]")
      mm("[url= <Selection> ] <Cursor> [/url]")
      
      MenuBar()
      MenuItem(999, "Quit")
      CocoaMessage(0, StatusItem, "setMenu:", MenuID(0))
      Repeat
         Select WaitWindowEvent()
            Case #PB_Event_CloseWindow : Break
            Case #PB_Event_Menu
               Select EventMenu()
                  Case 0 : say!1 : SetMenuItemState(0,0,say)
                     If say : simpleShell(~"osascript\nsay \"Speech Activated!\"") : EndIf
                  cc() ; case (title)
                  cc(TypeText(UCase(seltxt)))
                  cc(TypeText(LCase(seltxt)))
                  cc(TypeText(Shell("printf %s "+QP(seltxt)+" | perl -CS -pe 's/(\p{Lu}*)(\p{Ll}*)/\L$1\U$2/g'"))) ; tOGGLE
                  cc(TypeText(Shell("printf %s "+QP(seltxt)+" | perl -CS -pe 's/(\p{L})(\p{L}+)/\U$1\L$2/g'"))) ; Title Case
                  cc() ; enclose (title)
                  cc(TypeText(~"\""+seltxt+~"\""))
                  cc(TypeText("("+seltxt+")"))
                  cc(TypeText("'"+seltxt+"'"))
                  cc() ; bbcode (title)
                  cc(TypeText("[code=purebasic]"+seltxt+"["+#codepb+"]"))
                  cc(TypeText("[size=200]"+seltxt+"[/size]"))
                  cc(TypeText("[url="+GetClipboardText()+"]"+seltxt+"[/url]"))
                  cc(Back("[url="+seltxt+"]","[/url]","link"))
                     
                  Case 999 : Break
                  Case #PB_Menu_Quit : Break
               EndSelect
         EndSelect
      ForEver
   EndIf
EndIf
Last edited by Piero on Sat Dec 06, 2025 9:18 am, edited 10 times in total.
User avatar
mk-soft
Always Here
Always Here
Posts: 6415
Joined: Fri May 12, 2006 6:51 pm
Location: Germany

Re: Process "ANY" text!

Post by mk-soft »

I don't know why you always call a shell for scripts.
You can use AppleScript directly from PB.

Link: AppleScript with ErrorInfo
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
Piero
Addict
Addict
Posts: 1135
Joined: Sat Apr 29, 2023 6:04 pm
Location: Italy

Re: Process "ANY" text!

Post by Piero »

mk-soft wrote: Tue Nov 25, 2025 6:43 pm I don't know why you always call a shell for scripts.
Because it's extremely easier, for many reasons…
…and you use cocoa, while I use shell… (we do not use PB!) :P
User avatar
Piero
Addict
Addict
Posts: 1135
Joined: Sat Apr 29, 2023 6:04 pm
Location: Italy

Re: Process "ANY" text!

Post by Piero »

mk-soft wrote: Tue Nov 25, 2025 6:43 pmdirectly from PB.
I'm using an intermediate disk file to get the selected text of almost any app, NOT to run (complex) applescripts!
Try to get it (the selected text of almost any app) with cocoa! (I found nothing on the net, so this seems to be a good new trick) :D
User avatar
Piero
Addict
Addict
Posts: 1135
Joined: Sat Apr 29, 2023 6:04 pm
Location: Italy

Re: Process "ANY" text!

Post by Piero »

Updated

Code: Select all

Shell(~"osascript\ntell application \"System Events\" to keystroke \""+EscapeString(typet)+~"\"")
:wink:
User avatar
Piero
Addict
Addict
Posts: 1135
Joined: Sat Apr 29, 2023 6:04 pm
Location: Italy

Re: Process "ANY" text!

Post by Piero »

Dramatically improved…

Dedicated to my Idol mk-soft (because we can always do it Better…)

…but NOT to Fred (registerServicesMenuSendTypes)
User avatar
Piero
Addict
Addict
Posts: 1135
Joined: Sat Apr 29, 2023 6:04 pm
Location: Italy

Re: Process "ANY" text!

Post by Piero »

Improved again!

Some shortcuts added just as reminders for PB IDE… but, for example, remember: if ⌘Q is implemented,
you can KEEP ⌘ PRESSED, tap TAB (repeatedly) and then tap Q to quit the selected app!

Useful free soft for this kind of stuff:
iCanHazShortcut free, opensource
FastScripts should be semi-free… (limited number of shortcuts)
WordServices free… and that web page has more good free stuff…

Dedicated to Fred (because I'm getting tired of always having to be critic!) ;)
User avatar
Piero
Addict
Addict
Posts: 1135
Joined: Sat Apr 29, 2023 6:04 pm
Location: Italy

Re: Process "ANY" text!

Post by Piero »

Updated with much better text typing and more…
User avatar
Piero
Addict
Addict
Posts: 1135
Joined: Sat Apr 29, 2023 6:04 pm
Location: Italy

Re: Process "ANY" text!

Post by Piero »

Fix:
Some apps misbehave if you need to type "above a multiline selection" (PB, BBEdit……) so typing will be used only for "inside line" selections
Note: It can be useful to avoid spamming your clipboard history (on apps with services; not PB…)
User avatar
Piero
Addict
Addict
Posts: 1135
Joined: Sat Apr 29, 2023 6:04 pm
Location: Italy

Re: Process "ANY" text!

Post by Piero »

Here's a version that types multiple lines…
Cocoa+Shell+AppleScript :shock:

Code: Select all

; Make an Automator Quick Action named #servicen (writesel) for selected text, with Run Shell Script:
; cat > ~/Documents/myseltext.txt

EnableExplicit

#codepb="/code" ; trick to paste on Forum!
#servicen= "writesel" ; service name (it will avoid using clipboard if available)
#maxtype= 512 ; typing max (use clipboard if too much chars to type); clipboard will be restored… (0 for no typing)
#addesc= #False ; #True ; type ESC after "typing" (close PB autocomplete… but better leave it off for other apps…)
Global say = #False ; no speech (see menu)
Global seltfile.s=GetUserDirectory(#PB_Directory_Documents)+"myseltext.txt" ; this file will be written and deleted!
Global seltxt.s
#NSSquareStatusBarItemLength = -2
Define StatusBar,StatusItem

ImportC ""
   CFRelease(CFTypeRef)
   CGEventKeyboardSetUnicodeString(keyEvent, len, uChar.s)
   CGEventCreateKeyboardEvent(CGEventSourceRef, CGVirtualKeyCode.U, KeyDown.L)
   CGEventPost(CGEventTapLocation, CGEventRef)
EndImport

Procedure Typestring(string.s)
   Protected keyEvent,i ; , *objPool = CocoaMessage(0, 0, "NSAutoreleasePool new")
   keyEvent = CGEventCreateKeyboardEvent(#nil, 0, #True)
   CGEventKeyboardSetUnicodeString(keyEvent, Len(string), string)
   CGEventPost(#kCGHIDEventTap, keyEvent)
   CFRelease(keyEvent) ; : CocoaMessage(0, *objPool, "release")
EndProcedure

Procedure.s Shell(ShellCommand$)
   Protected Output$, shell, tmper$
   shell = RunProgram("/bin/sh","","",
      #PB_Program_Open|#PB_Program_Write|#PB_Program_Read)
   If shell
      WriteProgramStringN(shell,ShellCommand$)
      WriteProgramData(shell,#PB_Program_Eof,0)
      While ProgramRunning(shell)
         If AvailableProgramOutput(shell)
            Output$ + ReadProgramString(shell)+ Chr(10)
         EndIf
      Wend
   EndIf
   ProcedureReturn Left(Output$,Len(Output$)-1)
EndProcedure

Procedure simpleShell(ShellCommand$)
   Protected shell = RunProgram("/bin/sh","","", #PB_Program_Open|#PB_Program_Write)
   WriteProgramStringN(shell,ShellCommand$) : WriteProgramData(shell,#PB_Program_Eof,0) : CloseProgram(shell)
EndProcedure

Procedure.s QP(str$) ; to quote paths and strings for shell
   ProcedureReturn "'" + ReplaceString(str$, "'", "'\''") + "'"
EndProcedure

Procedure.s GetText()
   Protected s.s,i.q
   simpleShell("rm -f "+QP(seltfile))
   s=Shell(~"osascript\n"+
      ~"tell application \"System Events\" to tell (1st application process whose frontmost is true)\n"+
      ~"try\n"+
      ~"perform action \"AXPick\" of menu item \""+#servicen+~"\" of menu 1 of menu item \"Services\""+
      ~" of menu 1 of menu bar item 2 of menu bar 1\n"+
      ~"return\n"+
      ~"on error\n"+
      ~"if "+say+~" > 0 then say \"No Service!\"\n"+
      ~"set cc to the clipboard\n"+
      ~"set the clipboard to \"\"\n"+
      ~"keystroke \"c\" using command down\n"+
      ~"delay .3\n"+
      ~"if (the clipboard) as text is \"\" then\n"+
      ~"set the clipboard to cc\n"+
      ~"return \"$%^&Empty$%^&\"\n"+
      ~"else\n"+
      ~"set r to (the clipboard) as text\n"+
      ~"set the clipboard to cc\n"+
      ~"return r\n"+
      ~"end\n"+
      ~"end\n"+
      ~"end\n")
   If FindString(s,#CRLF$) : ProcedureReturn "" : EndIf
   If s="$%^&Empty$%^&"
      simpleShell(~"osascript\n if "+say+~" > 0 then say \"I tried to use clipboard, but there was No Selection\"")
      ProcedureReturn "" 
   ElseIf s : simpleShell("rm -f "+QP(seltfile)) : ProcedureReturn s : EndIf
   i=ElapsedMilliseconds()
   Delay(200)
   s=Shell("cat "+QP(seltfile))
   While FileSize(seltfile)=-1
      Delay(200)
      s=Shell("cat "+QP(seltfile))
      if (ElapsedMilliseconds()-i)>1800 : break : EndIf
   Wend
   simpleShell("rm -f "+QP(seltfile))
   ProcedureReturn s
EndProcedure

Procedure TypeText2(String.s)
   Protected i, slen=Len(String), s.s, ts.s
   For i = 1 To slen
      s=Mid(String,i,1)
      if s <> ~"\n" And s <> ~"\r"
         ts+s 
      Else
         if ts : Typestring(ts) : EndIf
         ts=""
         Shell(~"osascript\ntell application \"System Events\" to key code 36")
      EndIf
   Next i
   if ts : Typestring(ts) : EndIf
EndProcedure

Procedure.s TypeText(t.s)
   If t
      If Len(t) <= #maxtype
         TypeText2(t)
         If #addesc : simpleShell(~"osascript\ntell application \"System Events\" to key code 53") : EndIf
         simpleShell(~"osascript\n if "+say+~" > 0 then say \"I Used Typing!\"")
      Else
         Shell(~"osascript\n"+
            ~"tell application \"System Events\" to tell (1st application process whose frontmost is true)\n"+
            ~"set c to the clipboard\n"+
            ~"set the clipboard to \""+EscapeString(t)+~"\"\n"+
            ~"keystroke \"v\" using command down\n"+
            ~"delay .3\n"+
            ~"set the clipboard to c\n"+
            ~"end")
         simpleShell(~"osascript\n if "+say+~" > 0 then say \"Pasted!\"")
      EndIf
   EndIf
EndProcedure

Procedure Back(s.s,b.s,t.s="")
   TypeText(s+t+b) : Shell(~"osascript\nrepeat "+Len(b+t)+~" times\n tell app \"System Events\" to key code 123\nend")
   If t
      simpleShell(~"osascript\nrepeat "+Len(t)+~" times\ntell app \"System Events\" to key code 124 using shift down\nend")
   EndIf
EndProcedure

Macro mm(m,d=0) : MenuItem(MacroExpandedCount, m) : If d : DisableMenuItem(0,MacroExpandedCount,d) : EndIf : EndMacro
Macro mc(m) : mm(m,1) : EndMacro
Macro cc(m=) : Case MacroExpandedCount : seltxt=GetText() : If seltxt : m : EndIf : EndMacro

OpenWindow(0, #PB_Any, #PB_Any, 300, 100, "Text Replacements",#PB_Window_Invisible)
LoadFont(0,"Apple Color Emoji",23,#PB_Font_HighQuality)
Define ItemLength.CGFloat = 22 ; space taken in menu bar
CreateImage(0, 32, 32, 32, 0) ; RGBA(0, 0, 0, 0)
StartDrawing(ImageOutput(0))
DrawingMode(#PB_2DDrawing_NativeText)
DrawingFont(FontID(0))
DrawText(5, -1, "🍍")
StopDrawing()
StatusBar = CocoaMessage(0, 0, "NSStatusBar systemStatusBar")
If StatusBar
   StatusItem = CocoaMessage(0, CocoaMessage(0, StatusBar,"statusItemWithLength:", #NSSquareStatusBarItemLength), "retain")
   If StatusItem
      CocoaMessage(0, StatusItem, "setLength:@", @ItemLength)
      CocoaMessage(0, StatusItem, "setImage:", ImageID(0))
      CreatePopupMenu(0)
      MenuItem(0,"Talk") : SetMenuItemState(0,0,say)
      MenuBar() ; --------------------
      mc("Case") ; menu title
      mm(~"UPPERCASE\tCmd+Shift+U") ; ⌘⇧U in PB
      mm(~"lowercase\tCmd+Shift+L") ; ⌘⇧L in PB
      mm(~"tOGGLE cASE\tCmd+Shift+X") ; ⌘⇧X in PB
      mm("Title Case")
      MenuBar() ; --------------------
      mc("Enclose") ; menu title
      mm(~"\" \"")
      mm("( )")
      mm("' '")
      MenuBar() ; --------------------
      mc("BBCode") ; menu title
      mm("[code=purebasic]")
      mm("[size=200]")
      mm("[url= <Clipboard> ] <Selection> [/url]")
      mm("[url= <Selection> ] <Cursor> [/url]")
      
      MenuBar()
      MenuItem(999, "Quit")
      CocoaMessage(0, StatusItem, "setMenu:", MenuID(0))
      Repeat
         Select WaitWindowEvent()
            Case #PB_Event_CloseWindow : Break
            Case #PB_Event_Menu
               Select EventMenu()
                  Case 0 : say!1 : SetMenuItemState(0,0,say)
                     If say : simpleShell(~"osascript\nsay \"Speech Activated!\"") : EndIf
                  cc() ; case (title)
                  cc(TypeText(UCase(seltxt)))
                  cc(TypeText(LCase(seltxt)))
                  cc(TypeText(Shell("printf %s "+QP(seltxt)+" | perl -CS -pe 's/(\p{Lu}*)(\p{Ll}*)/\L$1\U$2/g'"))) ; tOGGLE
                  cc(TypeText(Shell("printf %s "+QP(seltxt)+" | perl -CS -pe 's/(\p{L})(\p{L}+)/\U$1\L$2/g'"))) ; Title Case
                  cc() ; enclose (title)
                  cc(TypeText(~"\""+seltxt+~"\""))
                  cc(TypeText("("+seltxt+")"))
                  cc(TypeText("'"+seltxt+"'"))
                  cc() ; bbcode (title)
                  cc(TypeText("[code=purebasic]"+seltxt+"["+#codepb+"]"))
                  cc(TypeText("[size=200]"+seltxt+"[/size]"))
                  cc(TypeText("[url="+GetClipboardText()+"]"+seltxt+"[/url]"))
                  cc(Back("[url="+seltxt+"]","[/url]","link"))
                     
                  Case 999 : Break
                  Case #PB_Menu_Quit : Break
               EndSelect
         EndSelect
      ForEver
   EndIf
EndIf
Post Reply