Mani

Mani is a window automation tool. Common tasks (such as launching programs, visiting websites, and typing text) can be scripted using a simple Ruby DSL.

While xmonad is currently the only supported window manager, Mani can support any window manager for the X Window System with just a few lines of code. Please open an issue to add support for your favorite window manager.

Demo

Mani Demo

Installation

gem install mani

xmonad

You'll have to do a few things to get Mani to work as expected under xmonad.

  1. Install xdotool.
  2. Add XMonad.Hooks.EwmhDesktops to your xmonad configuration, and then restart xmonad.
import XMonad.Hooks.EwmhDesktops

main = xmonad $ ewmh defaultConfig{ handleEventHook =
           handleEventHook defaultConfig <+> fullscreenEventHook }

Getting Started

Here's a simple example:

# hello.rb
Mani.new(window_manager: :xmonad) do
  window :hello, launch: 'urxvt' do
    run 'echo "Hello, world."'
  end
end

Run it:

$ mani hello.rb

Provided you have rxvt-unicode installed, a new terminal will open and the command echo "Hello, world." will run inside it.

Examples

Check out the examples directory for more examples.

DSL Overview

Mani.new should be at the beginning of every script. It takes an options hash, which can take the following keys:

  • :window_manager - the window manager (currently only :xmonad is supported)
  • :switch_to_workspace - an optional proc which, when called, returns a sequence which, when interpreted, will switch to the specified workspace (defaults to the default sequence used by the :window_manager to change workspaces)
switcher = ->(space) { "super+#{space}" }
Mani.new(window_manager: :xmonad, switch_to_workspace: switcher) do
  # Switch to workspace 1
  workspace 1

  # Switch to workspace 2
  workspace 2
end

workspace switches to the specified workspace. If a block is supplied, it will be called.

Mani.new(window_manager: :xmonad) do
  # Switch to workspace 1, launch a terminal and run 'echo "Hello, world."'
  workspace 1 do
    window :hello, launch: 'urxvt' do
      run 'echo "Hello, world."'
    end
  end

  # Switch to workspace 2
  workspace 2
end

window serves as the container for window commands. Its first argument is the window name. Its second (optional) argument is an options hash, which can take the following keys:

  • :launch - the program to be launched
  • :delay - the amount of time, in seconds, to delay after launching the program (defaults to 0.5)

If a block is supplied, it will be called.

Mani.new(window_manager: :xmonad) do
  # Create a new terminal window, wait one second, and then run 'ls'
  window :ls do
    launch 'urxvt', delay: 1 do
      run 'ls'
    end
  end

  # The same process can be accomplished using this condensed syntax:
  window :ls, launch: 'urxvt', delay: 1 do
    run 'ls'
  end

  # Just launch a program
  window :thunderbird, launch: 'thunderbird'

  # Return to the :ls window, and then run 'date'
  window :ls do
    run 'date'
  end
end

launch launches a program. It takes an optional options hash, which can take the following keys:

  • :delay - the amount of time, in seconds, to delay after launching the program (defaults to 0.5)
Mani.new(window_manager: :xmonad) do
  # Create a new terminal window, wait one second, and then run 'ls'
  window :ls do
    launch 'urxvt', delay: 1 do
      run 'ls'
    end
  end
end

run executes a combination within a window. It takes an optional options hash, which can take the following keys:

  • :delay - the amount of time, in seconds, to delay after running the command (defaults to 0.5)
Mani.new(window_manager: :xmonad) do
  # Run 'ls'
  window :ls, launch: 'urxvt', delay: 1 do
    run 'ls'
  end
end

type types a combination within a window. It takes an optional options hash, which can take the following keys:

  • :delay - the amount of time, in seconds, to delay after typing the text (defaults to 0.5)
Mani.new(window_manager: :xmonad) do
  # Launch gvim
  window :gvim, launch: 'gvim -f', delay: 2 do
    # Wait two seconds, then move to the end of the buffer, start a new line,
    # and type "Hello, world."
    type 'GoHello, world.'
  end
end

visit is used to visit a website. It takes an optional options hash, which can take the following keys:

  • :delay - the amount of time, in seconds, to delay after entering the url (defaults to 0.5)

Note that depending on the browser, you may need to insert an "F6" keystroke at the beginning of the url.

Mani.new(window_manager: :xmonad) do
  window :chromium, launch: 'chromium', delay: 1.5 do
    visit 'localhost:8080'
  end

  window :firefox, launch: 'firefox', delay: 1.5 do
    # Type "F6" first so that the cursor is in the address bar before typing
    # the url.
    visit '{{F6}}gmail.com'
  end
end

browser_tab is used to either create a new browser tab or switch to a specific tab. Its first argument, when :new, will create a new tab. If it is an integer, the browser will instead switch to that tab. Its second argument is an optional options hash, which can take the following keys:

  • :delay - the amount of time, in seconds, to delay after handling the tab (defaults to 0.5)

If a block is supplied, it will be called.

Mani.new(window_manager: :xmonad) do
  window :chromium, launch: 'chromium', delay: 1.5 do
    # Visit 'localhost:8080' within the initial tab
    visit 'localhost:8080'

    # Create a new tab and visit 'news.ycombinator.com'
    browser_tab :new do
      visit 'news.ycombinator.com'
    end

    # Switch back to the first tab (localhost:8080)
    browser_tab 1
  end
end

Combination Syntax

Simulating keystrokes for function keys and modifiers requires the use of a special syntax. The {{ and }} tags are used to indicate a sequence. Chording is achieved by separating keys with a + sign.

Mani.new(window_manager: :xmonad) do
  window :firefox, launch: 'firefox', delay: 1.5 do
    # Type "F6" first so that the cursor is in the address bar before typing
    # the url.
    visit '{{F6}}gmail.com'

    # Paste the contents of the clipboard into the url.
    visit 'localhost:8080/reports/{{ctrl+v}}/preview'
  end
end

In the event a literal {{ or }} is desired, a % sign can be added as a prefix to escape the sequence.

Mani.new(window_manager: :xmonad) do
  window :hello, launch: 'urxvt' do
    run 'echo "%{{literal opening brackets"'
    run 'echo "literal closing brackets%}}"'
    run 'echo "%{{literal opening and closing brackets%}}"'
  end
end

Contributing

Please follow the guidelines below.

Issue Reporting

  • Use the issue tracker to report any issues or ideas for improvements.
  • Check that the issue has not already been reported.
  • Check that the issue has not already been fixed in the latest code (i.e., on master).
  • Be clear, concise, and precise in your description of the problem.
  • Open an issue with a descriptive title and a summary in grammatically correct, complete sentences.
  • Include any relevant code to the issue summary.

Pull Requests

  • Fork the project.
  • Use a topic/feature branch to easily amend a pull request later, if necessary.
  • Write good commit messages.
  • Use the same coding conventions as the rest of the project.
  • Commit and push until you are happy with your contribution.
  • Make sure to add tests for it. This is important so I don't break it in a future version unintentionally.
  • Please try not to mess with the version or history. If you want to have your own version, please isolate the change to its own commit so I can cherry-pick around it.
  • Squash related commits together.
  • Open a pull request that relates to only one subject with a clear title and description in grammatically correct, complete sentences.

Code Conventions

Mani follows the conventions laid out in the Ruby Style Guide. These conventions are enforced by RuboCop.

make install
make style

Please ensure that any contributed code does not produce any RuboCop offenses.

Tests

make test