Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Macros: multi-stage output events #2711

Closed
Muirium opened this issue May 6, 2021 · 15 comments
Closed

Macros: multi-stage output events #2711

Muirium opened this issue May 6, 2021 · 15 comments
Labels

Comments

@Muirium
Copy link

Muirium commented May 6, 2021

This is probably a feature request. I have searched Karabiner's documentation and other people's rules for macros but can't find anything about it.

What I want to achieve with macros is stuff like this, on a single trigger keystroke:

  • Type the À character: press Option + ` then press Shift + A
  • Select the current word: press Option + Left arrow then press Shift + Option + Right arrow
  • Type ABcd%^: Hold Shift then press A then B then release Shift then press C then D then hold Shift then press 5 then 6
  • Combining the above to do magic like (( wrap )) the current word in [a]tags[/b] instantly with a single keystroke!
  • And throwing the clipboard contents into the mix, for things like [url=pasteboard]selection[/url]. Boom.

In a nutshell: "from": events triggering whole chains of "to": events.

I do lots of macro stuff like this with Soarer's Converter and it can be tremendously useful. I'd love to be able to do it across all my keyboards, including my MacBook Air's own. Are sequences simply beyond Karabiner's scope?

(Karabiner really is a lot of fun, once you get into writing your own complex rules. Brilliant stuff! My biggest thanks to Tekezo and everyone who works on this project. Love it to bits!)

@Muirium
Copy link
Author

Muirium commented Sep 6, 2021

I'm pleased to report Karabiner can totally do this!

Finally occurred to me to just try putting multiple key_code statements into the to definitions. There's a single example of it somewhere in Karabiner's documentation, just shown in passing, typing out the word hello. Once I saw that I was at the races!

So I've made a bunch. Typing out simple strings is just the start of it. I've made word selection and paragraph selection macros—which use sequences of modifier keys to do their work—and wrapping macros which throw the clipboard into the mix with Command + X and V.

First: word selection. It works just as I suggested above. Use the arrow keys, Option and Shift, in sequence:

{
	"title": "🦾 General Macros",
	"rules": [
		{
			"description": "🤖 Select Current Word: ⌃ Control + ⌥ Option + ← Left or → Right ⇨ Select Word",
			"manipulators": [
				{
					"from": {
						"key_code": "left_arrow",
						"modifiers": {
							"mandatory": [
								"option",
								"control"
							]
						}
					},
					"to": [
						{
							"key_code": "left_arrow",
							"modifiers": ["left_option"]
						},
						{
						    "key_code": "right_arrow",
						    "modifiers": ["left_option", "left_shift"]
						}
					],
					"type": "basic"
				},

Wrapping is a bit more involved. For instance, here's 'quote' "wrapping":

{
			"description": "🤖 Wrap Selection: ⌃ Control + ⌥ Option + '\"(*!? ⇨ Paired Bracket Wraps",
			"manipulators": [
				{
{
					"from": {
						"key_code": "quote",
						"modifiers": {
							"mandatory": [
								"option",
								"control"
							]
						}
					},
					"to": [
						{
						    "key_code": "x",
						    "modifiers": ["left_command"]
						},
						{
						    "key_code": "quote"
						},
						{
						    "key_code": "v",
						    "modifiers": ["left_command"]
						},
						{
						    "key_code": "quote"
						}
					],
					"type": "basic"
				},
				{
					"from": {
						"key_code": "quote",
						"modifiers": {
							"mandatory": [
								"option",
								"control",
								"shift"
							]
						}
					},
					"to": [
						{
						    "key_code": "x",
						    "modifiers": ["left_command"]
						},
						{
						    "key_code": "quote",
						     "modifiers": ["left_shift"]
						},
						{
						    "key_code": "v",
						    "modifiers": ["left_command"]
						},
						{
						    "key_code": "quote",
						     "modifiers": ["left_shift"]
						}
					],
					"type": "basic"
				},

Two macros: one for 'single' and one for "double" quotes. Letting Shift into the macro as an optional modifier monkeys around with the cursor movement that goes in within. Had to screen it out and do that case separately.

Now, there's one big flaw with these wrap macros: they use the clipboard.

Using the clipboard to store the text isn't the cleanest way, I know. Use these macros and your existing clipboard contents are destroyed. (I use a clipboard history utility so this doesn't trip me up, but it's definitely suboptimal.) If there's a more elegant way, I'd love to know!

But these macros do really work. Everywhere! These all work in every app across the Mac thanks to Karabiner; much more reliably than Cocoa Keybindings which are sleek but obscure and are often just not supported at all in whatever app you're using. No such problem for Karabiner!

@Muirium
Copy link
Author

Muirium commented Sep 6, 2021

Here's an example of speeding up dead keys for diacritical marks. I want to type àèìòù as easily as holding down Shift to type AEIOU. Nò pròblèmò!

{
	"title": "🏴󠁧󠁢󠁳󠁣󠁴󠁿 Gaelic Macros",
	"rules": [
		{
			"description": "🏴󠁧󠁢󠁳󠁣󠁴󠁿 Gaelic Stràc Macros: 🦾 ⌃ Control + ⌥ Option + aeiou ⇨ àèìòù",
			"manipulators": [
				{
					"from": {
						"key_code": "a",
						"modifiers": {
							"mandatory": [
								"option",
								"control"
							]
						}
					},
					"to": [
						{
							"key_code": "grave_accent_and_tilde",
							"modifiers": ["left_option"]
						},
						{
						    "key_code": "a"
						}
					],
					"type": "basic"
				},
				{
					"from": {
						"key_code": "a",
						"modifiers": {
							"mandatory": [
								"option",
								"control",
								"shift"
							]
						}
					},
					"to": [
						{
							"key_code": "grave_accent_and_tilde",
							"modifiers": ["left_option"]
						},
						{
						    "key_code": "a",
						    "modifiers": ["left_shift"]
						}
					],
					"type": "basic"
				}

Again, a separate macro is necessary for à and À, because Shift gets in the way of the dead key sequence Karabiner's robotically pressing really quickly for me. Previously, every stràc that came up in Gaelic, I'd have to twiddle around with Option + ` then press the key. That made À a four key sequence. Much better when the robot's got your back!

I guess this issue is now closed. I'll mark it closed in a bit.

I can imagine improvements to support macros: especially storing the pasteboard. But now I can see how Karabiner approaches this problem, I recognise I'm imagining un-Karabiner things!

@MuhammedZakir
Copy link

About clipboard problem:

You can save the current conent in main clipboard in a different clipboard, and move them back after seding command+v. See pqrs-org/KE-complex_modifications#697 (comment) and pbcopy/pbpaste manpage.

@Muirium
Copy link
Author

Muirium commented Sep 9, 2021

Oh, that's helpful! Thanks. Here it is in action:

{
	"title": "Test Rules",
	"rules": [
		{
			"description": "🤖 Wrap Selection: ⌃ Control + ⌥ Option + '\"(*!? ⇨ Paired Bracket Wraps",
			"manipulators": [
				{
					"from": {
						"key_code": "open_bracket",
						"modifiers": {
							"mandatory": [
								"option",
								"control",
								"command"
							]
						}
					},
					"to": [
					    {
						    "key_code": "x",
						    "modifiers": ["left_command"]
						},
						{
						    "shell_command": "pbpaste | pbcopy -pboard ruler"
						},
						{
						    "key_code": "open_bracket"
						},
						{
						    "shell_command": "pbpaste -pboard ruler"
						},
						{
						    "key_code": "v",
						    "modifiers": ["left_command"]
						},
						{
						    "key_code": "close_bracket"
						}
					],
					"type": "basic"
				}
			]
		}
	]
}

My only complaint is it still messes up clipboard history.

Ideally, I'd like to run these macros without messing up my clipboard or its history, given I can see that as a menu in Jumpcut. I use my clipboard history a lot and would love to keep it clean while using these macros.

Any way to bypass the general clipboard entirely, and send / restore the selection straight to / from the ruler pasteboard instead?

I keep dreaming of Karabiner manipulating the clipboard itself, rather than just the keyboard!

@MuhammedZakir
Copy link

MuhammedZakir commented Sep 9, 2021

Have you tried control+k (cut) and contorl+y shortucts? They use a different clipboard.

Any way to bypass the general clipboard entirely, and send / restore the selection straight to / from the ruler pasteboard instead?

If you want to use pbcopy and pbpaste, then you can use AppleScript to insert text without messing the main clibpoard. Get clipboard content (simplest way is to use shell command) and then insert them using keystroke. I haven't tested it.


Your this to event

						    "shell_command": "pbpaste -pboard ruler"

sends the output to Karabiner which will discard them. You probably forgot to add | pbcopy at the end. Additionaly, I think order of your to events are wrong. It should be

					"to": [
						{
						    "shell_command": "pbpaste | pbcopy -pboard ruler"
						},
	  				        {
						    "key_code": "x",
						    "modifiers": ["left_command"]
						},
						{
						    "key_code": "open_bracket"
						},
						{
						    "key_code": "v",
						    "modifiers": ["left_command"]
						},
						{
						    "shell_command": "pbpaste -pboard ruler | pbcopy"
						},
						{
						    "key_code": "close_bracket"
						}
					],

@Muirium
Copy link
Author

Muirium commented Sep 9, 2021

Control + K seems to use the main system clipboard here on M1 Big Sur. I tested with this:

{
	"title": "Test Rules",
	"rules": [
		{
			"description": "🤖 Wrap Selection: ⌃ Control + ⌥ Option + '\"(*!? ⇨ Paired Bracket Wraps",
			"manipulators": [
				{
					"from": {
						"key_code": "open_bracket",
						"modifiers": {
							"mandatory": [
								"option",
								"control",
								"command"
							]
						}
					},
					"to": [
					    {
						    "key_code": "k",
						    "modifiers": ["left_control"]
						},
						{
						    "key_code": "open_bracket"
						},
						{
						    "key_code": "y",
						    "modifiers": ["left_control"]
						},
						{
						    "key_code": "close_bracket"
						}
					],
					"type": "basic"
				}
			]
		}
	]
}

Running that macro on a text selection successfully wraps it with [brackets] but the text winds up in my clipboard history just as before. Strange.

As for your suggestion about my code: I tested my macros they do work. Try it yourself: these code samples are complete JSONs you can throw straight at Karabiner. You may have a fair point I'm just not understanding, however ;)

@Muirium
Copy link
Author

Muirium commented Sep 9, 2021

Okay, I think I get what you mean now! It's about saving the clipboard before I meddle with it. Here's my test of your suggested code:

{
	"title": "Test Rules",
	"rules": [
		{
			"description": "Test",
			"manipulators": [
				{
					"from": {
						"key_code": "open_bracket",
						"modifiers": {
							"mandatory": [
								"option",
								"control",
								"command"
							]
						}
					},
					"to": [
						{
							"shell_command": "pbpaste | pbcopy -pboard ruler"
						},
						{
							"key_code": "x",
							"modifiers": [
								"left_command"
							]
						},
						{
							"key_code": "open_bracket"
						},
						{
							"key_code": "v",
							"modifiers": [
								"left_command"
							]
						},
						{
							"shell_command": "pbpaste -pboard ruler | pbcopy"
						},
						{
							"key_code": "close_bracket"
						}
					],
					"type": "basic"
				}
			]
		}
	]
}

Problem remains: run this and the selected text still winds up in Jumpcut's clipboard history. Maybe I should go investigate what clipboards Jumpcut is tracking…

Playing with pbpaste and pbcopy in Terminal just now, I see the ruler pasteboard is independent of Jumpcut's monitoring. I can throw stuff in there (an ls listing) and Jumpcut doesn't see it. So this should be possible. Can't say I understand what's going wrong.

@Muirium
Copy link
Author

Muirium commented Sep 9, 2021

Oh boy…

Experimenting with Control + K and Control + Y, I discover their behaviour is app specific. I was using BBEdit for my tests, and BBEdit syncs their clipboard with the general system pasteboard. TextEdit however keeps them apart. Safari considers them separate too. Scrivener is the same way. Oh, and all these apps (besides BBEdit) have separate pasteboards! So, soon enough I found myself with several simultaneous separate hidden pasteboards! Right now, there's different text pasting on Control Y in all 4!

So yeah, it's complicated. But better than nothing. I'll edit my macros to all use Control + K and Y. Most of my writing is not in BBEdit so should stay out of Jumpcut this way. Hopefully. Let's see!

Aye, my macros seem to behave nicely now, outside of BBEdit. I've fired Bare Bones a support email to see if this is intended behaviour.

@MuhammedZakir
Copy link

Playing with pbpaste and pbcopy in Terminal just now, I see the ruler pasteboard is independent of Jumpcut's monitoring. I can throw stuff in there (an ls listing) and Jumpcut doesn't see it. So this should be possible. Can't say I understand what's going wrong.

The problem is with getting the currently selected text. I don't know any method for it except using clipboard.

@Muirium
Copy link
Author

Muirium commented Sep 9, 2021

Yeah, me neither. Thanks for all your help though, it's been very useful indeed! Especially the one about Control Y and K. I vaguely remembered there was "another copy/paste on the Mac somewhere" but I had no idea how to go find it.

Control Y and K seems to be working nicely, outside of BBEdit. Maybe that's just a BBEdit bug. I'll know when Bare Bones get back to me.

I also just added "repeat": false to the last lines of my macros' "to": statements. Holding down keys was producing strange behaviour that's fixed now by suppressing repeat. Coincidentally, looking that function up, I rediscovered Karabiner's documentation page which clued me into macros existing in the first place! I just took that idea and ran with it here.

@Muirium
Copy link
Author

Muirium commented Sep 9, 2021

Bare Bones got back to me real quick. BBEdit doesn't maintain a separate kill buffer, and that can't be reconfigured. Apparently it's just the way their Emacs keybinding emulation works.

Anyway, all other apps I'm trying today are working fine with Control Y and K, so I reckon I'll just keep it this way. Only BBEdit leaves unwanted clipboard history, and my macros still work with it otherwise. I've very almost achieved what I wanted. Phew! My thanks to Muhammed and Karabiner!

@MuhammedZakir
Copy link

To not make this a little easier, you could ask developer of your clipboard manager to add a cli or menu option to enable/disable/ignore clipboard watching. Then, you will be able to toggle it using shell script, or AppleScript.

@stale
Copy link

stale bot commented Jan 9, 2022

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.

@stale stale bot added the stale label Jan 9, 2022
@Muirium
Copy link
Author

Muirium commented Jan 9, 2022

Thanks for reminding me to close this, bot!

I've been doing plenty with Karabiner macros, including single keystroke text selection and clipboard manipulation. For those interested, I've written loads about it here:

https://deskthority.net/viewtopic.php?p=494473#p494473

Macros are awesome!

@FloppyDisco
Copy link

VSCode has this functionality. and it does not mess with the clipboard history. i have no idea how they achieved this. it also works with all wrapping type characters: ( , { , [ , " , '

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

3 participants