diff --git a/README.md b/README.md
index 7df884da2c99c6db09b531c8c1ea68bb3e96453f..ebd088a0e80d1e58ebdad8e16331e61523d13049 100644
--- a/README.md
+++ b/README.md
@@ -1,4 +1,4 @@
-ExFSWatch
+FileSystem
 =========
 
 A file change watcher wrapper based on [fs](https://github.com/synrc/fs)
@@ -15,7 +15,7 @@ NOTE: On Linux you need to install inotify-tools.
 
 ## Usage
 
-Put `exfswatch` in the `deps` and `application` part of your mix.exs
+Put `file_system` in the `deps` and `application` part of your mix.exs
 
 ``` elixir
 defmodule Excellent.Mixfile do
@@ -25,24 +25,44 @@ defmodule Excellent.Mixfile do
   ...
   end
 
-  def application do
-    [applications: [:exfswatch, :logger]]
-  end
-
   defp deps do
     [
-      { :exfswatch, "~> 0.4.2", only: :test },
+      { :file_system, "~> 0.1.0", only: :test },
     ]
   end
   ...
 end
 ```
 
+
+### Subscription API
+
+You can spawn a worker and subscribe to events from it:
+
+```elixir
+{:ok, pid} = FileSystem.Worker.start_link(dirs: ["/path/to/some/files"])
+FileSystem.Worker.subscribe(pid)
+```
+
+The pid you subscribed from will now receive messages like
+
+```
+{:file_event, worker_pid, {file_path, events}}
+```
+and
+```
+{:file_event, worker_pid, :stop}
+```
+
+### Callback API
+
+You can also `use FileSystem` to define a module with a callback that will be called when filesystem events occur. This requires you to specify directories to watch at compile-time.
+
 write `lib/monitor.ex`
 
 ```elixir
 defmodule Monitor do
-  use ExFSWatch, dirs: ["/tmp/fswatch"]
+  use FileSystem, dirs: ["/tmp/test"]
 
   def callback(:stop) do
     IO.puts "STOP"
@@ -67,7 +87,7 @@ For each platform, you can pass extra arguments to the underlying listener proce
 Here is an example to get instant notifications on file changes for Mac OS X:
 
 ```elixir
-use ExFSWatch, dirs: ["/tmp/fswatch"], listener_extra_args: "--latency=0.0"
+use FileSystem, dirs: ["/tmp/test"], listener_extra_args: "--latency=0.0"
 ```
 
 See the [fs source](https://github.com/synrc/fs/tree/master/c_src) for more details.
@@ -75,10 +95,5 @@ See the [fs source](https://github.com/synrc/fs/tree/master/c_src) for more deta
 ## List Events from Backend
 
 ```shell
-iex > ExFSWatch.known_events
+iex > FileSystem.known_events
 ```
-
-## TODO
-
-- [ ] GenEvent mode
-- [ ] Unit Testing
diff --git a/c_src/bsd/main.c b/c_src/bsd/main.c
deleted file mode 100644
index 72bcb2b4f2a94cb21532807226c97a1848edbf2b..0000000000000000000000000000000000000000
--- a/c_src/bsd/main.c
+++ /dev/null
@@ -1,34 +0,0 @@
-#include <sys/time.h>
-#include <sys/event.h>
-#include <sys/stat.h>
-#include <fcntl.h>
-#include <stdlib.h>
-#include <unistd.h>
-
-int main(int argc, char *argv[]) {
-    struct kevent event;
-    struct kevent change;
-    int fd, kq, nev;
-    if ((fd = open(argv[1], O_RDONLY)) == -1) return 1;
-    EV_SET(&change, fd, EVFILT_VNODE , EV_ADD
-                                     | EV_ENABLE
-                                     | EV_DISABLE
-                                     | EV_CLEAR
-                                     | EV_DELETE
-                                     | EV_EOF
-                                     | EV_RECEIPT
-                                     | EV_DISPATCH
-                                     | EV_ONESHOT,
-                                       NOTE_DELETE
-                                     | NOTE_RENAME
-                                     | NOTE_EXTEND
-                                     | NOTE_ATTRIB
-                                     | NOTE_LINK
-                                     | NOTE_REVOKE
-                                     | NOTE_WRITE, 0, 0);
-    if ((kq = kqueue()) == -1) return 1;
-    nev = kevent(kq, &change, 1, &event, 1, NULL);
-    if (nev < 0) { return 1; } else if (nev > 0) { if (event.flags & EV_ERROR) { return 1; } }
-    close(kq);
-    return 0;
-}
diff --git a/lib/exfswatch.ex b/lib/exfswatch.ex
deleted file mode 100644
index 1026ed2a72551395a5e764f96fc7a168b81c883e..0000000000000000000000000000000000000000
--- a/lib/exfswatch.ex
+++ /dev/null
@@ -1,57 +0,0 @@
-require Logger
-
-defmodule ExFSWatch do
-  defmacro __using__(options) do
-    extra_args =
-      options
-      |> Keyword.get(:listener_extra_args, "")
-      |> String.split
-      |> Enum.map(&to_char_list/1)
-    quote do
-      def __dirs__, do: unquote(Keyword.fetch!(options, :dirs))
-      def __listener_extra_args__, do: unquote(extra_args)
-      def start,    do: ExFSWatch.Supervisor.start_child __MODULE__
-      def child_spec do
-        Supervisor.Spec.worker(ExFSWatch.Worker, [__MODULE__], id: __MODULE__)
-      end
-    end
-  end
-
-  @backend (case :os.type() do
-    {:unix,  :darwin}  -> ExFSWatch.Backends.Fsevents
-    {:unix,  :freebsd} -> ExFSWatch.Backends.Kqueue
-    {:unix,  :linux}   -> ExFSWatch.Backends.InotifyWait
-    {:win32, :nt}      -> ExFSWatch.Backends.InotifyWaitWin32
-    _                  -> nil
-  end)
-
-  def start(_, _) do
-    if os_supported?() and port_found?() do
-      ExFSWatch.Supervisor.start_link
-    else
-      Logger.error "ExFSWatch start failed"
-      if os_supported?() do
-        Logger.error "backend port not found: #{@backend}"
-      else
-        Logger.error "fs does not support the current operating system"
-      end
-      {:ok, self()}
-    end
-  end
-
-  def os_supported? do
-    not is_nil @backend
-  end
-
-  def port_found? do
-    @backend.find_executable()
-  end
-
-  def known_events do
-    @backend.known_events()
-  end
-
-  def backend do
-    @backend
-  end
-end
diff --git a/lib/exfswatch/backends/fsevents.ex b/lib/exfswatch/backends/fsevents.ex
deleted file mode 100644
index 314870992d48b225af2038c220cc6f7d38885784..0000000000000000000000000000000000000000
--- a/lib/exfswatch/backends/fsevents.ex
+++ /dev/null
@@ -1,33 +0,0 @@
-alias ExFSWatch.Utils
-
-defmodule ExFSWatch.Backends.Fsevents do
-
-  def find_executable do
-    :code.priv_dir(:exfswatch) ++ '/mac_listener'
-  end
-
-  def start_port(path, listener_extra_args) do
-    path = path |> Utils.format_path()
-    args = listener_extra_args ++ ['-F' | path]
-    Port.open(
-      {:spawn_executable, find_executable()},
-      [:stream, :exit_status, {:line, 16384}, {:args, args}, {:cd, System.tmp_dir!()}]
-    )
-  end
-
-  def known_events do
-    [ :mustscansubdirs, :userdropped, :kerneldropped, :eventidswrapped, :historydone,
-      :rootchanged, :mount, :unmount, :created, :removed, :inodemetamod, :renamed, :modified,
-      :finderinfomod, :changeowner, :xattrmod, :isfile, :isdir, :issymlink, :ownevent,
-    ]
-  end
-
-  def line_to_event(line) do
-    [_event_id, flags, path] = :string.tokens(line, [?\t])
-    [_, flags] = :string.tokens(flags, [?=])
-    {:ok, t, _} = :erl_scan.string(flags ++ '.')
-    {:ok, flags} = :erl_parse.parse_term(t)
-    {path, flags}
-  end
-
-end
diff --git a/lib/exfswatch/backends/inotify_wait.ex b/lib/exfswatch/backends/inotify_wait.ex
deleted file mode 100644
index dba0e7f8fecb7f64bdd02f7c9c9d41b1afad957c..0000000000000000000000000000000000000000
--- a/lib/exfswatch/backends/inotify_wait.ex
+++ /dev/null
@@ -1,65 +0,0 @@
-alias ExFSWatch.Utils
-
-defmodule ExFSWatch.Backends.InotifyWait do
-
-  def find_executable do
-    System.find_executable("sh") |> to_charlist
-  end
-
-  def start_port(path, listener_extra_args) do
-    path = path |> Utils.format_path()
-    args = [
-      '-c', 'inotifywait $0 $@ & PID=$!; read a; kill $PID',
-      '-m', '-e', 'modify', '-e', 'close_write', '-e', 'moved_to', '-e', 'create',
-      '-r'] ++ listener_extra_args ++ path
-    Port.open(
-      {:spawn_executable, find_executable()},
-      [:stream, :exit_status, {:line, 16384}, {:args, args}, {:cd, System.tmp_dir!()}]
-    )
-  end
-
-  def known_events do
-    [:created, :deleted, :renamed, :closed, :modified, :isdir, :attribute, :undefined]
-  end
-
-  def line_to_event(line) do
-    line
-    |> to_string
-    |> scan1
-    |> scan2(line)
-  end
-
-  def scan1(line) do
-    re = ~r/^(.*) ([A-Z_,]+) (.*)$/
-    case Regex.scan re, line do
-      [] -> {:error, :unknown}
-      [[_, path, events, file]] ->
-        {Path.join(path, file), parse_events(events)}
-    end
-
-  end
-  def scan2({:error, :unknown}, line) do
-    re = ~r/^(.*) ([A-Z_,]+)$/
-    case Regex.scan re, line do
-      [] -> {:error, :unknown}
-      [[_, path, events]] ->
-        {path, parse_events(events)}
-    end
-  end
-  def scan2(res, _), do: res
-
-  def parse_events(events) do
-    String.split(events, ",")
-    |> Enum.map(&(convert_flag &1))
-  end
-
-  def convert_flag("CREATE"),      do: :created
-  def convert_flag("DELETE"),      do: :deleted
-  def convert_flag("ISDIR"),       do: :isdir
-  def convert_flag("MODIFY"),      do: :modified
-  def convert_flag("CLOSE_WRITE"), do: :modified
-  def convert_flag("CLOSE"),       do: :closed
-  def convert_flag("MOVED_TO"),    do: :renamed
-  def convert_flag("ATTRIB"),      do: :attribute
-  def convert_flag(_),             do: :undefined
-end
diff --git a/lib/exfswatch/backends/inotify_wait_win32.ex b/lib/exfswatch/backends/inotify_wait_win32.ex
deleted file mode 100644
index acfadc248651263efad7d3079eebd00ec385f381..0000000000000000000000000000000000000000
--- a/lib/exfswatch/backends/inotify_wait_win32.ex
+++ /dev/null
@@ -1,37 +0,0 @@
-alias ExFSWatch.Utils
-
-defmodule ExFSWatch.Backends.InotifyWaitWin32 do
-
-  @re :re.compile('^(.*\\\\.*) ([A-Z_,]+) (.*)$', [:unicode]) |> elem(1)
-
-  def find_executable do
-    :code.priv_dir(:exfswatch) ++ '/inotifywait.exe'
-  end
-
-  def start_port(path, listener_extra_args) do
-    path = path |> Utils.format_path()
-    args = listener_extra_args ++ ['-m', '-r' | path]
-    Port.open(
-      {:spawn_executable, find_executable()},
-      [:stream, :exit_status, {:line, 16384}, {:args, args}, {:cd, System.tmp_dir!()}]
-    )
-  end
-
-  def known_events do
-    [:created, :modified, :removed, :renamed, :undefined]
-  end
-
-  def line_to_event(line) do
-    {:match, [dir, flags, dir_entry]} = :re.run(line, @re, [{:capture, :all_but_first, :list}])
-    flags = for f <- :string.tokens(flags, ','), do: convert_flag(f)
-    path = :filename.join(dir, dir_entry)
-    {path, flags}
-  end
-
-  defp convert_flag('CREATE'),   do: :created
-  defp convert_flag('MODIFY'),   do: :modified
-  defp convert_flag('DELETE'),   do: :removed
-  defp convert_flag('MOVED_TO'), do: :renamed
-  defp convert_flag(_),          do: :undefined
-
-end
diff --git a/lib/exfswatch/backends/kqueue.ex b/lib/exfswatch/backends/kqueue.ex
deleted file mode 100644
index 81adba2f1fe98fa3595e15237aa7ca91a69be539..0000000000000000000000000000000000000000
--- a/lib/exfswatch/backends/kqueue.ex
+++ /dev/null
@@ -1,26 +0,0 @@
-alias ExFSWatch.Utils
-
-defmodule ExFSWatch.Backends.Kqueue do
-
-  def known_events do
-    [:created, :deleted, :renamed, :closed, :modified, :isdir, :undefined]
-  end
-
-  def find_executable do
-    :code.priv_dir(:exfswatch) ++ '/kqueue'
-  end
-
-  def start_port(path, listener_extra_args) do
-    path = path |> Utils.format_path()
-    args = listener_extra_args ++ [path]
-    Port.open(
-      {:spawn_executable, find_executable()},
-      [:stream, :exit_status, {:line, 16384}, {:args, args}, {:cd, System.tmp_dir!()}]
-    )
-  end
-
-  def line_to_event(line) do
-    {'.', line}
-  end
-
-end
diff --git a/lib/exfswatch/supervisor.ex b/lib/exfswatch/supervisor.ex
deleted file mode 100644
index 566935d7f82d435bacf1732dfdb49b077a07695e..0000000000000000000000000000000000000000
--- a/lib/exfswatch/supervisor.ex
+++ /dev/null
@@ -1,16 +0,0 @@
-defmodule ExFSWatch.Supervisor do
-  use Supervisor
-
-  def start_link do
-    Supervisor.start_link(__MODULE__, [], name: __MODULE__)
-  end
-
-  def start_child(module) do
-    Supervisor.start_child(__MODULE__, [module])
-  end
-
-  def init([]) do
-    [ worker(ExFSWatch.Worker, [], [restart: :transient])
-    ] |> supervise(strategy: :simple_one_for_one)
-  end
-end
diff --git a/lib/exfswatch/utils.ex b/lib/exfswatch/utils.ex
deleted file mode 100644
index 218a8db03b642636361b7a86ab89db93b071bcc5..0000000000000000000000000000000000000000
--- a/lib/exfswatch/utils.ex
+++ /dev/null
@@ -1,13 +0,0 @@
-defmodule ExFSWatch.Utils do
-
-  def format_path(path) when is_list(path) do
-    for i <- path do
-      i |> Path.absname |> to_char_list
-    end
-  end
-
-  def format_path(path) do
-    [path] |> format_path
-  end
-
-end
diff --git a/lib/exfswatch/worker.ex b/lib/exfswatch/worker.ex
deleted file mode 100644
index f02fe6d4ba232ec5dd358ab9fd674744a5a2471e..0000000000000000000000000000000000000000
--- a/lib/exfswatch/worker.ex
+++ /dev/null
@@ -1,30 +0,0 @@
-defmodule ExFSWatch.Worker do
-  use GenServer
-
-  defstruct [:port, :backend, :module]
-
-  def start_link(module) do
-    GenServer.start_link(__MODULE__, module, name: module)
-  end
-
-  def init(module) do
-    backend = ExFSWatch.backend
-    port = backend.start_port(module.__dirs__, module.__listener_extra_args__)
-    {:ok, %__MODULE__{port: port, backend: backend, module: module}}
-  end
-
-  def handle_info({port, {:data, {:eol, line}}}, %__MODULE__{port: port, backend: backend, module: module}=sd) do
-    {file_path, events} = backend.line_to_event(line)
-    module.callback(file_path |> to_string, events)
-    {:noreply, sd}
-  end
-
-  def handle_info({port, {:exit_status, 0}}, %__MODULE__{port: port, module: module}) do
-    module.callback(:stop)
-    {:stop, :killed}
-  end
-
-  def handle_info(_, sd) do
-    {:noreply, sd}
-  end
-end
diff --git a/lib/file_system.ex b/lib/file_system.ex
new file mode 100644
index 0000000000000000000000000000000000000000..2d34560991933bc50de6afe35d08ce92a6cbe120
--- /dev/null
+++ b/lib/file_system.ex
@@ -0,0 +1,21 @@
+defmodule FileSystem do
+  defmacro __using__(options) do
+    quote do
+      @file_system_module_options unquote(options)
+      @before_compile FileSystem.ModuleApi
+    end
+  end
+
+  def start_link(args) do
+    FileSystem.Worker.start_link(args)
+  end
+
+  def subscribe(pid) do
+    GenServer.call(pid, :subscribe)
+  end
+
+  @backend FileSystem.Backend.backend
+  def backend, do: @backend
+  def known_events, do: @backend.known_events()
+
+end
diff --git a/lib/file_system/backend.ex b/lib/file_system/backend.ex
new file mode 100644
index 0000000000000000000000000000000000000000..3912622f258fe284ec903c0cb377396e05ff8e3b
--- /dev/null
+++ b/lib/file_system/backend.ex
@@ -0,0 +1,28 @@
+defmodule FileSystem.Backend do
+  @callback bootstrap() :: any()
+  @callback supported_systems() :: [{atom(), atom()}]
+  @callback known_events() :: [atom()]
+  @callback find_executable() :: Sting.t
+
+  def backend do
+    os_type = :os.type()
+    backend =
+      Application.get_env(:file_system, :backend,
+        case os_type do
+          {:unix,  :darwin}  -> :fs_mac
+          {:unix,  :linux}   -> :fs_inotify
+          {:unix,  :freebsd} -> :fs_inotify
+          {:win32, :nt}      -> :fs_windows
+          _                  -> nil
+        end
+      ) |> case do
+        nil         -> raise "undefined backend"
+        :fs_mac     -> FileSystem.Backends.FSMac
+        :fs_inotify -> FileSystem.Backends.FSInotify
+        :fs_windows -> FileSystem.Backends.FSWindows
+        any         -> any
+      end
+    os_type in backend.supported_systems || raise "unsupported system for current backend"
+    backend
+  end
+end
diff --git a/lib/file_system/backends/fs_inotify.ex b/lib/file_system/backends/fs_inotify.ex
new file mode 100644
index 0000000000000000000000000000000000000000..141682fbed2828e3e0aa90f9711c9dce339fc0ae
--- /dev/null
+++ b/lib/file_system/backends/fs_inotify.ex
@@ -0,0 +1,80 @@
+require Logger
+alias FileSystem.Utils
+
+defmodule FileSystem.Backends.FSInotify do
+  use GenServer
+  @behaviour FileSystem.Backend
+  @sep_char <<1>>
+
+  def bootstrap do
+    exec_file = find_executable()
+    unless File.exists?(exec_file) do
+      Logger.error "`inotify-tools` is needed to run `file_system` for your system, check https://github.com/rvoicilas/inotify-tools/wiki for more information about how to install it."
+      raise CompileError
+    end
+  end
+
+  def supported_systems do
+    [{:unix, :linux}, {:unix, :freebsd}]
+  end
+
+  def known_events do
+    [:created, :deleted, :renamed, :closed, :modified, :isdir, :attribute, :undefined]
+  end
+
+  def find_executable do
+    System.find_executable("inotifywait")
+  end
+
+  def start_link(args) do
+    GenServer.start_link(__MODULE__, args, [])
+  end
+
+  def init(args) do
+    port_path = Utils.format_path(args[:dirs])
+    format = ["%w", "%e", "%f"] |> Enum.join(@sep_char) |> to_charlist
+    port_args = [
+      '-e', 'modify', '-e', 'close_write', '-e', 'moved_to', '-e', 'create',
+      '-e', 'delete', '-e', 'attrib', '--format', format, '--quiet', '-m', '-r'
+    ] ++ Utils.format_args(args[:listener_extra_args]) ++ port_path
+    port = Port.open(
+      {:spawn_executable, to_charlist(find_executable())},
+      [:stream, :exit_status, {:line, 16384}, {:args, port_args}, {:cd, System.tmp_dir!()}]
+    )
+    {:ok, %{port: port, worker_pid: args[:worker_pid]}}
+  end
+
+  def handle_info({port, {:data, {:eol, line}}}, %{port: port}=state) do
+    {file_path, events} = line |> parse_line
+    send(state.worker_pid, {:backend_file_event, self(), {file_path, events}})
+    {:noreply, state}
+  end
+
+  def handle_info({port, {:exit_status, _}}, %{port: port}=state) do
+    send(state.worker_pid, {:backend_file_event, self(), :stop})
+    {:stop, :normal, state}
+  end
+
+  def handle_info(_, state) do
+    {:noreply, state}
+  end
+
+  def parse_line(line) do
+    {path, flags} =
+      case line |> to_string |> String.split(@sep_char, trim: true) do
+        [dir, flags, file] -> {Path.join(dir, file), flags}
+        [path, flags]      -> {path, flags}
+      end
+    {path, flags |> String.split(",") |> Enum.map(&convert_flag/1)}
+  end
+
+  defp convert_flag("CREATE"),      do: :created
+  defp convert_flag("DELETE"),      do: :deleted
+  defp convert_flag("ISDIR"),       do: :isdir
+  defp convert_flag("MODIFY"),      do: :modified
+  defp convert_flag("CLOSE_WRITE"), do: :modified
+  defp convert_flag("CLOSE"),       do: :closed
+  defp convert_flag("MOVED_TO"),    do: :renamed
+  defp convert_flag("ATTRIB"),      do: :attribute
+  defp convert_flag(_),             do: :undefined
+end
diff --git a/lib/file_system/backends/fs_mac.ex b/lib/file_system/backends/fs_mac.ex
new file mode 100644
index 0000000000000000000000000000000000000000..f816f05247e7e9a11e14c85e71a2bfcf9caa6a09
--- /dev/null
+++ b/lib/file_system/backends/fs_mac.ex
@@ -0,0 +1,71 @@
+require Logger
+alias FileSystem.Utils
+
+defmodule FileSystem.Backends.FSMac do
+  use GenServer
+  @behaviour FileSystem.Backend
+
+  def bootstrap do
+    exec_file = find_executable()
+    unless File.exists?(exec_file) do
+      Logger.info "Compiling executable file..."
+      cmd = "clang -framework CoreFoundation -framework CoreServices -Wno-deprecated-declarations c_src/mac/*.c -o #{exec_file}"
+      if Mix.shell.cmd(cmd) > 0 do
+        Logger.error "Compile executable file error, try to run `#{cmd}` manually."
+        raise CompileError
+      else
+        Logger.info "Compile executable file, Done."
+      end
+    end
+  end
+
+  def supported_systems do
+    [{:unix, :darwin}]
+  end
+
+  def known_events do
+    [ :mustscansubdirs, :userdropped, :kerneldropped, :eventidswrapped, :historydone,
+      :rootchanged, :mount, :unmount, :created, :removed, :inodemetamod, :renamed, :modified,
+      :finderinfomod, :changeowner, :xattrmod, :isfile, :isdir, :issymlink, :ownevent,
+    ]
+  end
+
+  def find_executable do
+    (:code.priv_dir(:file_system) ++ '/mac_listener') |> to_string
+  end
+
+  def start_link(args) do
+    GenServer.start_link(__MODULE__, args, [])
+  end
+
+  def init(args) do
+    port_path = Utils.format_path(args[:dirs])
+    port_args = Utils.format_args(args[:listener_extra_args]) ++ ['-F' | port_path]
+    port = Port.open(
+      {:spawn_executable, to_charlist(find_executable())},
+      [:stream, :exit_status, {:line, 16384}, {:args, port_args}, {:cd, System.tmp_dir!()}]
+    )
+    {:ok, %{port: port, worker_pid: args[:worker_pid]}}
+  end
+
+  def handle_info({port, {:data, {:eol, line}}}, %{port: port}=state) do
+    {file_path, events} = line |> parse_line
+    send(state.worker_pid, {:backend_file_event, self(), {file_path, events}})
+    {:noreply, state}
+  end
+
+  def handle_info({port, {:exit_status, _}}, %{port: port}=state) do
+    send(state.worker_pid, {:backend_file_event, self(), :stop})
+    {:stop, :normal, state}
+  end
+
+  def handle_info(_, state) do
+    {:noreply, state}
+  end
+
+  def parse_line(line) do
+    [_, _, events, path] = line |> to_string |> String.split(["\t", "="])
+    {path, events |> String.split(~w|[ , ]|, trim: true) |> Enum.map(&String.to_existing_atom/1)}
+  end
+
+end
diff --git a/lib/file_system/backends/fs_windows.ex b/lib/file_system/backends/fs_windows.ex
new file mode 100644
index 0000000000000000000000000000000000000000..1242ebd66b3057ad4a4578c8d32c239e242a9013
--- /dev/null
+++ b/lib/file_system/backends/fs_windows.ex
@@ -0,0 +1,75 @@
+require Logger
+alias FileSystem.Utils
+
+defmodule FileSystem.Backends.FSWindows do
+  use GenServer
+  @behaviour FileSystem.Backend
+  @sep_char <<1>>
+
+  def bootstrap do
+    exec_file = find_executable()
+    unless File.exists?(exec_file) do
+      Logger.error "Can't find executable `inotifywait.exe`, make sure the file is in your priv dir."
+      raise CompileError
+    end
+  end
+
+  def supported_systems do
+    [{:win32, :nt}]
+  end
+
+  def known_events do
+    [:created, :modified, :removed, :renamed, :undefined]
+  end
+
+  def find_executable do
+    (:code.priv_dir(:file_system) ++ '/inotifywait.exe') |> to_string
+  end
+
+  def start_link(args) do
+    GenServer.start_link(__MODULE__, args, [])
+  end
+
+  def init(args) do
+    port_path = Utils.format_path(args[:dirs])
+    format = ["%w", "%e", "%f"] |> Enum.join(@sep_char) |> to_charlist
+    port_args = Utils.format_args(args[:listener_extra_args]) ++ [
+      '--format', format, '--quiet', '-m', '-r' | port_path
+    ]
+    port = Port.open(
+      {:spawn_executable, to_charlist(find_executable())},
+      [:stream, :exit_status, {:line, 16384}, {:args, port_args}, {:cd, System.tmp_dir!()}]
+    )
+    {:ok, %{port: port, worker_pid: args[:worker_pid]}}
+  end
+
+  def handle_info({port, {:data, {:eol, line}}}, %{port: port}=state) do
+    {file_path, events} = line |> parse_line
+    send(state.worker_pid, {:backend_file_event, self(), {file_path, events}})
+    {:noreply, state}
+  end
+
+  def handle_info({port, {:exit_status, _}}, %{port: port}=state) do
+    send(state.worker_pid, {:backend_file_event, self(), :stop})
+    {:stop, :normal, state}
+  end
+
+  def handle_info(_, state) do
+    {:noreply, state}
+  end
+
+  def parse_line(line) do
+    {path, flags} =
+      case line |> to_string |> String.split(@sep_char, trim: true) do
+        [dir, flags, file] -> {Enum.join([dir, file], "\\"), flags}
+        [path, flags]      -> {path, flags}
+      end
+    {path, flags |> String.split(",") |> Enum.map(&convert_flag/1)}
+  end
+
+  defp convert_flag("CREATE"),   do: :created
+  defp convert_flag("MODIFY"),   do: :modified
+  defp convert_flag("DELETE"),   do: :removed
+  defp convert_flag("MOVED_TO"), do: :renamed
+  defp convert_flag(_),          do: :undefined
+end
diff --git a/lib/file_system/module_api.ex b/lib/file_system/module_api.ex
new file mode 100644
index 0000000000000000000000000000000000000000..3139ba66530942a2c498781eac7c512584d67de3
--- /dev/null
+++ b/lib/file_system/module_api.ex
@@ -0,0 +1,25 @@
+defmodule FileSystem.ModuleApi do
+  defmacro __before_compile__(%Macro.Env{module: module}) do
+    options = Module.get_attribute(module, :file_system_module_options)
+    quote do
+      def start do
+        {:ok, worker_pid} = FileSystem.start_link(unquote(options))
+        pid = spawn_link(fn ->
+          FileSystem.subscribe(worker_pid)
+          await_events(worker_pid)
+        end)
+        {:ok, pid}
+      end
+
+      defp await_events(pid) do
+        receive do
+          {:file_event, ^pid, :stop} ->
+            callback(:stop)
+          {:file_event, ^pid, {file_path, events}} ->
+            callback(file_path, events)
+            await_events(pid)
+        end
+      end
+    end
+  end
+end
diff --git a/lib/file_system/utils.ex b/lib/file_system/utils.ex
new file mode 100644
index 0000000000000000000000000000000000000000..b24eeb9a337e1d8374c42e32f83d5d58bf96306b
--- /dev/null
+++ b/lib/file_system/utils.ex
@@ -0,0 +1,20 @@
+defmodule FileSystem.Utils do
+  def format_path(path) when is_list(path) do
+    for i <- path do
+      i |> Path.absname |> to_charlist
+    end
+  end
+
+  def format_path(path) do
+    [path] |> format_path
+  end
+
+  def format_args(nil), do: []
+  def format_args(str) when is_binary(str) do
+    str |> String.split |> format_args
+  end
+  def format_args(list) when is_list(list) do
+    list |> Enum.map(&to_charlist/1)
+  end
+
+end
diff --git a/lib/file_system/worker.ex b/lib/file_system/worker.ex
new file mode 100644
index 0000000000000000000000000000000000000000..f110770bc27a2bdbd1b72bd5e4509a04ec4eb52f
--- /dev/null
+++ b/lib/file_system/worker.ex
@@ -0,0 +1,34 @@
+defmodule FileSystem.Worker do
+  use GenServer
+
+  def start_link(args) do
+    {args, opts} = Keyword.split(args, [:dirs, :listener_extra_args])
+    GenServer.start_link(__MODULE__, args, opts)
+  end
+
+  def init(args) do
+    {:ok, backend_pid} = FileSystem.backend.start_link([{:worker_pid, self()} | args])
+    {:ok, %{backend_pid: backend_pid, subscribers: %{}}}
+  end
+
+  def handle_call(:subscribe, {pid, _}, state) do
+    ref = Process.monitor(pid)
+    state = put_in(state, [:subscribers, ref], pid)
+    {:reply, :ok, state}
+  end
+
+  def handle_info({:backend_file_event, backend_pid, file_event}, %{backend_pid: backend_pid}=state) do
+    state.subscribers |> Enum.each(fn {_ref, subscriber_pid} ->
+      send(subscriber_pid, {:file_event, self(), file_event})
+    end)
+    {:noreply, state}
+  end
+
+  def handle_info({:DOWN, _pid, _, ref, _reason}, state) do
+    {:noreply, pop_in(state.subscribers[ref])}
+  end
+
+  def handle_info(_, state) do
+    {:noreply, state}
+  end
+end
diff --git a/mix.exs b/mix.exs
index 3b7be3ec32a342d433247236c86caaef787d7fdd..00767448646fa68603cf46f4cec57f56ef51a2bd 100644
--- a/mix.exs
+++ b/mix.exs
@@ -1,27 +1,21 @@
-defmodule Mix.Tasks.Compile.Exfswatch do
+defmodule Mix.Tasks.Compile.FileSystem do
   def run(_) do
-    case :os.type() do
-      {:unix, :darwin} ->
-        Mix.shell.cmd("clang -framework CoreFoundation -framework CoreServices -Wno-deprecated-declarations c_src/mac/*.c -o priv/mac_listener")
-      {:unix, :freebsd} ->
-        Mix.shell.cmd("cc c_src/bsd/*.c -o priv/kqueue")
-      _ ->
-        :ok
-    end
+    FileSystem.backend.bootstrap
   end
 end
 
-defmodule ExFSWatch.Mixfile do
+
+defmodule FileSystem.Mixfile do
   use Mix.Project
 
   def project do
-    [ app: :exfswatch,
-      version: "0.4.2",
-      elixir: "~> 1.0",
-      compilers: [ :exfswatch, :elixir, :app ],
+    [ app: :file_system,
+      version: "0.1.0",
+      elixir: "~> 1.5-rc",
+      compilers: [:elixir, :app, :file_system],
       deps: deps(),
-      description: "A file change watcher wrapper based on [fs](https://github.com/synrc/fs)",
-      source_url: "https://github.com/falood/exfswatch",
+      description: "A file system change watcher wrapper based on [fs](https://github.com/synrc/fs)",
+      source_url: "https://github.com/falood/file_system",
       package: package(),
       docs: [
         extras: ["README.md"],
@@ -31,18 +25,19 @@ defmodule ExFSWatch.Mixfile do
   end
 
   def application do
-    [ mod: { ExFSWatch, [] },
-      applications: [:logger],
+    [
+      extra_applications: [:logger],
     ]
   end
 
   defp deps do
-    [ { :ex_doc, "~> 0.14", only: :docs },
+    [
+      { :ex_doc, "~> 0.14", only: :docs },
     ]
   end
 
   defp package do
-    %{ maintainers: ["Xiangrong Hao"],
+    %{ maintainers: ["Xiangrong Hao", "Max Veytsman"],
        files: [
          "lib", "README.md", "mix.exs",
          "c_src/bsd/main.c",
@@ -55,7 +50,7 @@ defmodule ExFSWatch.Mixfile do
          "priv/inotifywait.exe",
        ],
        licenses: ["WTFPL"],
-       links: %{"Github" => "https://github.com/falood/exfswatch"}
+       links: %{"Github" => "https://github.com/falood/file_system"}
      }
   end
 end
diff --git a/test/backends/fs_inotify_test.exs b/test/backends/fs_inotify_test.exs
new file mode 100644
index 0000000000000000000000000000000000000000..74ceb13a8e53ad518b05a943a3eff7dd9aa282e9
--- /dev/null
+++ b/test/backends/fs_inotify_test.exs
@@ -0,0 +1,48 @@
+defmodule FileSystem.Backends.FSInotifyTest do
+  use ExUnit.Case
+  import FileSystem.Backends.FSInotify
+
+  test "dir write close" do
+    assert {"/one/two/file", [:modified, :closed]} ==
+      ~w|/one/two/ CLOSE_WRITE,CLOSE file| |> to_port_line |> parse_line
+  end
+
+  test "dir create" do
+    assert {"/one/two/file", [:created]} ==
+      ~w|/one/two/ CREATE file| |> to_port_line |> parse_line
+  end
+
+  test "dir moved to" do
+    assert {"/one/two/file", [:renamed]} ==
+      ~w|/one/two/ MOVED_TO file| |> to_port_line |> parse_line
+  end
+
+  test "dir is_dir create" do
+    assert {"/one/two/dir", [:created, :isdir]} ==
+      ~w|/one/two/ CREATE,ISDIR dir| |> to_port_line |> parse_line
+  end
+
+  test "file write close" do
+    assert {"/one/two/file", [:modified, :closed]} ==
+      ~w|/one/two/file CLOSE_WRITE,CLOSE| |> to_port_line |> parse_line
+  end
+
+  test "file delete_self" do
+    assert {"/one/two/file", [:undefined]} ==
+      ~w|/one/two/file DELETE_SELF| |> to_port_line |> parse_line
+  end
+
+  test "whitespace in path" do
+    assert {"/one two/file", [:modified, :closed]} ==
+      ["/one two", "CLOSE_WRITE,CLOSE", "file"] |> to_port_line |> parse_line
+
+    assert {"/one/two/file 1", [:modified, :closed]} ==
+      ["/one/two", "CLOSE_WRITE,CLOSE", "file 1"] |> to_port_line |> parse_line
+
+    assert {"/one two/file 1", [:modified, :closed]} ==
+      ["/one two", "CLOSE_WRITE,CLOSE", "file 1"] |> to_port_line |> parse_line
+  end
+
+  defp to_port_line(list), do: list |> Enum.join(<<1>>) |> to_charlist
+
+end
diff --git a/test/backends/fs_mac_test.exs b/test/backends/fs_mac_test.exs
new file mode 100644
index 0000000000000000000000000000000000000000..dd6ee658adf67008a9714c0579aee2021b1b61e8
--- /dev/null
+++ b/test/backends/fs_mac_test.exs
@@ -0,0 +1,14 @@
+defmodule FileSystem.Backends.FSMacTest do
+  use ExUnit.Case
+  import FileSystem.Backends.FSMac
+
+  test "file modified" do
+    assert {"/one/two/file", [:inodemetamod, :modified]} ==
+      parse_line('37425557\t0x00011400=[inodemetamod,modified]\t/one/two/file')
+  end
+
+  test "whitespace in path" do
+    assert {"/one two/file", [:inodemetamod, :modified]} ==
+      parse_line('37425557\t0x00011400=[inodemetamod,modified]\t/one two/file')
+  end
+end
diff --git a/test/backends/fsevents.exs b/test/backends/fsevents.exs
deleted file mode 100644
index d50a0c95b5f9fff9776a3eb46b8298e19bea9a2e..0000000000000000000000000000000000000000
--- a/test/backends/fsevents.exs
+++ /dev/null
@@ -1,9 +0,0 @@
-defmodule ExFSWatch.Backends.FseventsTest do
-  use ExUnit.Case
-  import ExFSWatch.Backends.Fsevents
-
-  test "file modified" do
-    assert line_to_event('37425557\t0x00011400=[inodemetamod,modified]\t/one/two/file') ==
-      {'/one/two/file', [:inodemetamod, :modified]}
-  end
-end
diff --git a/test/backends/inotify_wait_test.exs b/test/backends/inotify_wait_test.exs
deleted file mode 100644
index 64b383c12ca0bd2dd4827cfae73c12eb25d57eff..0000000000000000000000000000000000000000
--- a/test/backends/inotify_wait_test.exs
+++ /dev/null
@@ -1,34 +0,0 @@
-defmodule ExFSWatch.Backends.InotifyWaitTest do
-  use ExUnit.Case
-  import ExFSWatch.Backends.InotifyWait
-
-  test "dir write close" do
-    assert line_to_event("/one/two/ CLOSE_WRITE,CLOSE file") ==
-      {"/one/two/file", [:modified, :closed]}
-  end
-
-  test "dir create" do
-    assert line_to_event("/one/two/ CREATE file") ==
-      {"/one/two/file", [:created]}
-  end
-
-  test "dir moved to" do
-    assert line_to_event("/one/two/ MOVED_TO file") ==
-      {"/one/two/file", [:renamed]}
-  end
-
-  test "dir is_dir create" do
-    assert line_to_event("/one/two/ CREATE,ISDIR dir") ==
-      {"/one/two/dir", [:created, :isdir]}
-  end
-
-  test "file write close" do
-    assert line_to_event("/one/two/file CLOSE_WRITE,CLOSE") ==
-      {"/one/two/file", [:modified, :closed]}
-  end
-
-  test "file delete_self" do
-    assert line_to_event("/one/two/file DELETE_SELF") ==
-      {"/one/two/file", [:undefined]}
-  end
-end
diff --git a/test/file_system_test.exs b/test/file_system_test.exs
new file mode 100644
index 0000000000000000000000000000000000000000..9edd86bba7addd3307c4dacfd7ad95dcc41235a3
--- /dev/null
+++ b/test/file_system_test.exs
@@ -0,0 +1,12 @@
+defmodule FileSystemTest do
+  use ExUnit.Case
+
+  test "subscribe api" do
+    tmp_dir = System.cmd("mktemp", ["-d"]) |> elem(0) |> String.trim
+    {:ok, pid} = FileSystem.start_link(dirs: [tmp_dir])
+    FileSystem.subscribe(pid)
+    File.touch("#{tmp_dir}/a")
+    assert_receive {:file_event, ^pid, {_path, _events}}, 5000
+    File.rm_rf!(tmp_dir)
+  end
+end
diff --git a/test/module_api_test.exs b/test/module_api_test.exs
new file mode 100644
index 0000000000000000000000000000000000000000..67ab92137e28b771f50b4008cc2e41dde80be253
--- /dev/null
+++ b/test/module_api_test.exs
@@ -0,0 +1,22 @@
+defmodule FileSystem.ModuleApiTest do
+  use ExUnit.Case
+
+  test "module api" do
+    tmp_dir = System.cmd("mktemp", ["-d"]) |> elem(0) |> String.trim
+    Process.register(self(), :module_api_test)
+    ref = System.unique_integer
+
+    defmodule MyMonitor do
+      use FileSystem, dirs: [tmp_dir]
+      @ref ref
+
+      def callback(:stop), do: :stop
+      def callback(path, events), do: send(:module_api_test, {@ref, path, events})
+    end
+
+    MyMonitor.start
+    File.touch("#{tmp_dir}/a")
+    assert_receive {^ref, _path, _events}, 5000
+    File.rm_rf!(tmp_dir)
+  end
+end