diff --git a/.ruby-version b/.ruby-version index a724a9c..d64a644 100644 --- a/.ruby-version +++ b/.ruby-version @@ -1 +1 @@ -2.4.0-dev +2.3.0-dev diff --git a/Gemfile b/Gemfile index 0ff4c63..1158858 100644 --- a/Gemfile +++ b/Gemfile @@ -1,4 +1,4 @@ source "http://rubygems.org" gem "activesupport" -gem "colored" \ No newline at end of file +gem "colored" diff --git a/Gemfile.lock b/Gemfile.lock index 0fc6ce7..174d170 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,18 +1,17 @@ GEM remote: http://rubygems.org/ specs: - activesupport (4.2.5.1) + activesupport (5.1.3) + concurrent-ruby (~> 1.0, >= 1.0.2) i18n (~> 0.7) - json (~> 1.7, >= 1.7.7) minitest (~> 5.1) - thread_safe (~> 0.3, >= 0.3.4) tzinfo (~> 1.1) colored (1.2) - i18n (0.7.0) - json (1.8.3) - minitest (5.8.4) - thread_safe (0.3.5) - tzinfo (1.2.2) + concurrent-ruby (1.0.5) + i18n (0.8.6) + minitest (5.10.3) + thread_safe (0.3.6) + tzinfo (1.2.3) thread_safe (~> 0.1) PLATFORMS @@ -23,4 +22,4 @@ DEPENDENCIES colored BUNDLED WITH - 1.11.2 + 1.14.6 diff --git a/README.md b/README.md index d869512..3209744 100644 --- a/README.md +++ b/README.md @@ -6,61 +6,74 @@ _usage gif_ ![usage_gif](macros.gif) -### Summary - - This uses the evtest linux program to get a stream of all keyboard events. - - The events are parsed using Regex and keypresses are tracked. - - Phrases are mapped to Ruby methods which are called when the phrase is typed anywhere. - - Text can be programmatically added / removed under the cursor using xdotool helper methods. - - This program evolved from my [artoo-keyboard-macros](https://github.com/maxpleaner/artoo-keyboard-macros) project. - - The reason for redoing it was that artoo-keyboard doesnt support global listeners [link to github issue](https://github.com/hybridgroup/artoo-keyboard/issues/6) - - [pty](http://ruby-doc.org/stdlib-2.2.3/libdoc/pty/rdoc/PTY.html) from Ruby's stdlib is used for the streaming I/O - - I tried to make this project easy to extend. The source code - (minus dependencies) is only ~250 lines including comments, and it's all one file. Obfuscation and labyrinthine OOP is avoided, - and the source code is attemptedly structured to place the most-often-changed sections at the top. - - I'm not packaging it up as a gem because editing the source code is required to add macros. +This uses the evtest linux program to get a stream of all keyboard events. + +The events are parsed using Regex and keypresses are tracked. + +Phrases are mapped to Ruby methods which are called when the phrase is typed anywhere. + +Text can be programmatically added / removed under the cursor using xdotool helper methods. + +This program evolved from my [artoo-keyboard-macros](https://github.com/maxpleaner/artoo-keyboard-macros) project. +The reason for redoing it was that artoo-keyboard doesnt support global listeners [link to github issue](https://github.com/hybridgroup/artoo-keyboard/issues/6) + +[pty](http://ruby-doc.org/stdlib-2.2.3/libdoc/pty/rdoc/PTY.html) from Ruby's stdlib is used for the streaming I/O ### Installation - - install dependencies: - - `sudo apt-get install xdotool evtest` - - clone the repo, `cd` in and run `bundle install`. - - Then run the program: `./macros_server.rb` - - Type anywhere (not just the terminal window) and notice how the text is captured. - - A list of available macros (and the name of the Ruby method they trigger) can be seen in the terminal when the - program is running. - - try typing `hello world`, which will open artoo.io in `chromium-browser` - - or type `text entry`, which will type 'hello world' under the cursor. - Note that the programmatically triggered keystrokes are **not** searched for - additional macros. - -### Usage: How to add a macro: - 1. create an instance method in `CommandParser` (this is the event that is fired) - 2. map the event to a phrase by adding an entry to `@@macro_method_mappings` in `CommandParser` - 3. Note that characters supported in macro trigger strings are: `0-9, a-z (lowercase), and whitespace.` + +first install dependencies: + +- `sudo apt-get install xdotool evtest` + +Then clone the repo, `cd` in and run `bundle install`. + +**_note_: if this fails try removing Gemfile.lock first** + +Then run the program: `./macros_server.rb` + +Type anywhere (not just the terminal window) and notice how the text is captured. + +A list of available macros (and the name of the Ruby method they trigger) can be seen in the terminal when the +program is running. + +try typing `hello world`, which will open artoo.io in `chromium-browser` +or type `text entry`, which will type 'hello world' under the cursor. + +Note that the programmatically triggered keystrokes are **not** searched for +additional macros. + +### How to add a macro: + +1. create an instance method in `CommandParser` (this is the event that is fired) +2. map the event to a phrase by adding an entry to `@@macro_method_mappings` in `CommandParser` +3. Note that characters supported in macro trigger strings are: `0-9, a-z (lowercase), and whitespace.` -### Usage: How to trigger key presses / deletes - - **How to program a macro to enter text for me?** - - There are three helper methods: - - `CommandParser.trigger_deletes(n)` will trigger the 'BackSpace' key n times using `xdotool`. - - `CommandParser.trigger_keystrokes(string)` will translate the string into `xdotool` instructions and enter the keystrokes. - - `CommandParser.trigger_for(method_name)` looks inside `@@macro_method_mappings` to find the macro string which triggers a particular +### How to trigger key presses / deletes + +**How to program a macro to enter text?** + +There are three helper methods: + +- `CommandParser.trigger_deletes(n)` will trigger the 'BackSpace' key n times using `xdotool`. +- `CommandParser.trigger_keystrokes(string)` will translate the string into `xdotool` instructions and enter the keystrokes. +- `CommandParser.trigger_for(method_name)` looks inside `@@macro_method_mappings` to find the macro string which triggers a particular ruby method (event). This is used in conjunction with `trigger_deletes` to delete the trigger text. i.e.: `CommandParser.trigger_deletes(CommandParser.trigger_for("my_ruby_method").length)` will delete whatever text was used to trigger the method. - - More characters are accepted when triggering keypresses than when - defining macro phrases. In addition to supporting `0-9, 'a'-'z' and whitespace` like macro phrases, - triggered keypresses can also include `'/', ':', ';', '@', '?', '&', and '.'`. This is so that urls and email addresses can be - supported. +- More characters are accepted when triggering keypresses than when + defining macro phrases. In addition to supporting `0-9, 'a'-'z' and whitespace` like macro phrases, + triggered keypresses can also include `'/', ':', ';', '@', '?', '&', and '.'`. This is so that urls and email addresses can be + supported. ### Caveat regarding sudo - - This script uses sudo when calling evtest (which requires it) - - **The script does not ask for sudo, and will just hang if the current user needs to use a password to use sudo.** - - For just testing this out one can run a command like `sudo pwd` and then run `macros_server.rb` in the next 5 minutes. - - The timeout of sudo can be increased by running `sudo visudo` and changing the value of `Defaults:user_name timestamp_timeout`. - - Alternatively, the script can be run like `sudo ruby macros_server.rb`. This uses sudo's ruby version (for Ubuntu, the standard is still 1.9.3). - In this case, all gems need to be installed as sudo, and different paths are needed to call system programs like `chromium-browser`. +- If the script errors after "checking for sudo access", try `sudo bundle` and `sudo ruby macros_server.rb` ### Contributing / todos - - The following features would benefit the program. - 1. Being able to trigger any keystroes, not just `0-9, 'a'-'z', whitespace, '/', ':', ';', '@', '?', '&', and '.'`. This could be done by editing `CommandParser.trigger_keystrokes`. - 3. Supporting more characters in macro phrases, including single-keypress named characters like `,./';[]=-` and multi-keypress characters like `!@#$%^&*()_+?><:"{}`. - 3. Supporting variables in macros, i.e. `email maxpleaner@gmail.com` could use the email address as a varaible. + +The main thing I want to do is add a macros YAML file + +Also: + +1. Being able to trigger any keystroes, not just `0-9, 'a'-'z', whitespace, '/', ':', ';', '@', '?', '&', and '.'`. This could be done by editing `CommandParser.trigger_keystrokes`. +3. Supporting more characters in macro phrases, including single-keypress named characters like `,./';[]=-` and multi-keypress characters like `!@#$%^&*()_+?><:"{}`. +3. Supporting variables in macros, i.e. `email maxpleaner@gmail.com` could use the email address as a varaible. diff --git a/lib/macro_definitions.rb b/lib/macro_definitions.rb new file mode 100644 index 0000000..47b593b --- /dev/null +++ b/lib/macro_definitions.rb @@ -0,0 +1,54 @@ +module MacroDefinitions + + def try_to_call(method_name) + method_name_sym = method_name&.to_sym + if method_name_sym && respond_to?(method_name_sym) + CommandParser.class_exec do + trigger_deletes(trigger_for(method_name_sym.to_s).length) + end + CommandParser.class_exec(&send(method_name_sym)) + end + end + + def linkedin_url; -> { + trigger_keystrokes "http://linkedin.com/in/maxpleaner" + }; end + + def github_url; -> { + trigger_keystrokes "http://github.com/maxpleaner" + }; end + + def website_url; -> { + trigger_keystrokes "http://maxpleaner.com" + }; end + + def email_url; -> { + trigger_keystrokes "maxpleaner@gmail.com" + }; end + + def portfolio_url; -> { + trigger_keystrokes "http://maxpleaner.github.io" + }; end + + def cover_letter; -> { + trigger_keystrokes <<-TXT.strip_heredoc + + Hello, + + My name is Max Pleaner and I'm a web developer in the San Francisco area. + I've been programming since 2013. Prior to that I worked/studied in politics. + My strongest languages are Ruby, Javascript, and Elixir. + I'm confident in many facets of full-stack development, from deployment, APIs, + and data on the backend to animation, games, build tools, realtime, and + reactive frameworks on the frontend. I enjoy constantly learning and strive + to follow best practices, including testing and documentation. I'm eager to work + together with a team and have held a few roles in the industry. + + Thanks for your consideration, Max Pleaner + http://maxpleaner.github.io + http://maxpleaner.com/resume + + TXT + }; end + +end \ No newline at end of file diff --git a/macros.yml b/macros.yml new file mode 100644 index 0000000..7b47d45 --- /dev/null +++ b/macros.yml @@ -0,0 +1,8 @@ +--- + +"q laa": "linkedin_url" +"q gaa": "github_url" +"q waa": "website_url" +"q eaa": "email_url" +"q paa": "portfolio_url" +"q cover": "cover_letter" \ No newline at end of file diff --git a/macros_server.rb b/macros_server.rb index 7ba8e78..04e115b 100755 --- a/macros_server.rb +++ b/macros_server.rb @@ -12,8 +12,14 @@ # @@macro_method_mappings maps macro-strings to CommandParser instance methods # --------------------------------------------------------- +require 'yaml' +require 'byebug' +require_relative "./lib/macro_definitions.rb" + class CommandParser + include MacroDefinitions + # --------------------------------------------------------- # Macro triggers - add macro phrases here # --------------------------------------------------------- @@ -23,30 +29,17 @@ class CommandParser @@max_phrase_length = 15 # Declarations of 'phrase' => 'event' mappings - # Add something here when creating a new macro - @@macro_method_mappings = { - # key: the macro trigger phrase - # val: a CommandParser instance method - "hello world" => "hello_world", - "text entry" => "test_text_entry", - } - -# --------------------------------------------------------- -# CommandParser instance methods - add macro events here -# --------------------------------------------------------- +# Add something here when creating a new macro +@@macro_method_mappings = { + # key: the macro trigger phrase + # val: a CommandParser instance method +} + +if File.exists? "macros.yml" + @@macro_method_mappings.merge! YAML.load File.read "macros.yml" +end - def hello_world - self.class.trigger_deletes(self.class.trigger_for("hello_world").length) - `chromium-browser http://artoo.io` - end - def test_text_entry - self.class.trigger_deletes(self.class.trigger_for("test_text_entry").length) - self.class.trigger_keystrokes("hello world") - # these triggered keystrokes are not scanned for additional macros, - # and even though "hello world" is a macro phrase, - # the hello_world method is not called - end # --------------------------------------------------------- # CommandParser configuration @@ -86,8 +79,9 @@ def self.add_key(key) puts "#{"matching method".green}: #{matching_method.to_s}" if matching_method puts "#{"current phrase".yellow}: #{@@current_phrase}" # Run event for a matched phrase, if one was found - CommandParser::ParserInstance.try(matching_method.to_sym) - # Print available methods each time a key is typed. + # THIS IS DONE THROUGH A DELEGATOR FUNCTION IN MacroDefinitions + CommandParser::ParserInstance.try_to_call(matching_method) + # Print available methods each time a key is typed. q waa print_available_methods end @@ -113,6 +107,10 @@ def self.trigger_keystrokes(string='') 'semicolon' when '.' 'period' + when "\n" + "Return" + when "," + "comma" else char end @@ -133,7 +131,11 @@ def self.trigger_keystrokes(string='') `xdotool key 7` `xdotool keyup shift` else - `xdotool key #{translated_char}` + if ["-"].include? translated_char + `xdotool type #{translated_char}` + else + `xdotool key #{translated_char}` + end end end end @@ -162,7 +164,8 @@ def self.shell_thread(cmd) PTY.spawn( cmd ) do |stdout, stdin, pid| begin stdout.each { |line| Macros.process_line(line) } # send each output line to Macros.process_line - rescue Errno::EIO + rescue Errno::EIO => e + puts e, e.backtrace puts "Errno:EIO error, but this probably just means " + "that the process has finished giving output" end @@ -196,13 +199,10 @@ def self.process_line(line) # Environment / dependency configuration # -------------- -## stdlib dependencies require 'pty' # pseudo-terminal -## local file dependencies require_relative './lib/run_with_timeout.rb' -## gems require 'active_support/all' require 'colored' @@ -225,7 +225,9 @@ class NilClass; def to_sym; :nil; end; end; # this is passed to Macros.shell_thread(cmd) when the script is run (see end of file) # evtest produces a streaming log of system events # '3' is echoed to the process to select 'keyboard' events -EventsStreamShellCommand = "(echo '3';) | sudo -S evtest" # The -S is necessary here to used saved sudo password. +# CHANGE THIS NUMBER IF NEEDED. +# USE sudo evtest TO FIND CORRECT DEVICE NUMBER +EventsStreamShellCommand = "(echo '6';) | sudo -S evtest" # The -S is necessary here to used saved sudo password. # --------------------- # Script execution