diff --git a/LICENSE b/LICENSE
new file mode 100644
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,27 @@
+Copyright (c) 2014-2017, Mateusz Kowalczyk
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without modification,
+are permitted provided that the following conditions are met:
+
+* Redistributions of source code must retain the above copyright notice, this
+  list of conditions and the following disclaimer.
+
+* Redistributions in binary form must reproduce the above copyright notice, this
+  list of conditions and the following disclaimer in the documentation and/or
+  other materials provided with the distribution.
+
+* Neither the name of Mateusz Kowalczyk nor the names of its
+  contributors may be used to endorse or promote products derived from
+  this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
+ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/README.md b/README.md
new file mode 100644
--- /dev/null
+++ b/README.md
@@ -0,0 +1,13 @@
+Interacts with tesseract to ease reading of RAW Japanese manga.
+
+The usage is fairly simple. Start the program, passing some temporary
+filename to use. Use the provided `scrot` button to take snippets of
+parts of your screen containing Japanese text. Once scrot has take the
+image, `tesseract` will run on it and the output will be visible in
+the UI. Mind that `tesseract` is not the fastest so there may be a
+small delay depending on size of the image.
+
+The radio buttons change the modes tesseract operates in. Please read
+the tesseract manpage to find out more. Vertical text is the default.
+
+![Sample usage](http://fuuzetsu.co.uk/images/1504470733.png)
diff --git a/Setup.hs b/Setup.hs
new file mode 100644
--- /dev/null
+++ b/Setup.hs
@@ -0,0 +1,2 @@
+import Distribution.Simple
+main = defaultMain
diff --git a/res/mainwindow.glade b/res/mainwindow.glade
new file mode 100644
--- /dev/null
+++ b/res/mainwindow.glade
@@ -0,0 +1,223 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Generated with glade 3.20.0 -->
+<interface>
+  <requires lib="gtk+" version="3.12"/>
+  <object class="GtkWindow" id="mainWindow">
+    <property name="can_focus">False</property>
+    <child>
+      <object class="GtkBox" id="mainBox">
+        <property name="visible">True</property>
+        <property name="can_focus">False</property>
+        <property name="orientation">vertical</property>
+        <child>
+          <object class="GtkPaned" id="imageTextPane">
+            <property name="visible">True</property>
+            <property name="can_focus">True</property>
+            <child>
+              <object class="GtkBox" id="box2">
+                <property name="visible">True</property>
+                <property name="can_focus">False</property>
+                <property name="orientation">vertical</property>
+                <child>
+                  <object class="GtkScrolledWindow" id="scrolledwindow1">
+                    <property name="width_request">300</property>
+                    <property name="visible">True</property>
+                    <property name="can_focus">True</property>
+                    <property name="shadow_type">in</property>
+                    <child>
+                      <object class="GtkViewport" id="imageViewport">
+                        <property name="visible">True</property>
+                        <property name="can_focus">False</property>
+                        <child>
+                          <object class="GtkImage" id="imageArea">
+                            <property name="visible">True</property>
+                            <property name="can_focus">False</property>
+                            <property name="stock">gtk-missing-image</property>
+                            <property name="icon_size">0</property>
+                          </object>
+                        </child>
+                      </object>
+                    </child>
+                  </object>
+                  <packing>
+                    <property name="expand">True</property>
+                    <property name="fill">True</property>
+                    <property name="position">0</property>
+                  </packing>
+                </child>
+                <child>
+                  <object class="GtkBox" id="box5">
+                    <property name="visible">True</property>
+                    <property name="can_focus">False</property>
+                    <property name="orientation">vertical</property>
+                    <child>
+                      <object class="GtkLabel" id="ocrModeLabel">
+                        <property name="visible">True</property>
+                        <property name="can_focus">False</property>
+                        <property name="label" translatable="yes">Tesseract OCR mode</property>
+                      </object>
+                      <packing>
+                        <property name="expand">False</property>
+                        <property name="fill">True</property>
+                        <property name="position">0</property>
+                      </packing>
+                    </child>
+                    <child>
+                      <object class="GtkRadioButton" id="verticalRadio">
+                        <property name="label" translatable="yes">Uniform column</property>
+                        <property name="visible">True</property>
+                        <property name="can_focus">True</property>
+                        <property name="receives_default">False</property>
+                        <property name="active">True</property>
+                        <property name="draw_indicator">True</property>
+                      </object>
+                      <packing>
+                        <property name="expand">False</property>
+                        <property name="fill">True</property>
+                        <property name="position">1</property>
+                      </packing>
+                    </child>
+                    <child>
+                      <object class="GtkRadioButton" id="columnRadio">
+                        <property name="label" translatable="yes">Varied size column</property>
+                        <property name="visible">True</property>
+                        <property name="can_focus">True</property>
+                        <property name="receives_default">False</property>
+                        <property name="active">True</property>
+                        <property name="draw_indicator">True</property>
+                        <property name="group">verticalRadio</property>
+                      </object>
+                      <packing>
+                        <property name="expand">False</property>
+                        <property name="fill">True</property>
+                        <property name="position">2</property>
+                      </packing>
+                    </child>
+                    <child>
+                      <object class="GtkRadioButton" id="blockRadio">
+                        <property name="label" translatable="yes">Uniform block</property>
+                        <property name="visible">True</property>
+                        <property name="can_focus">True</property>
+                        <property name="receives_default">False</property>
+                        <property name="active">True</property>
+                        <property name="draw_indicator">True</property>
+                        <property name="group">verticalRadio</property>
+                      </object>
+                      <packing>
+                        <property name="expand">False</property>
+                        <property name="fill">True</property>
+                        <property name="position">3</property>
+                      </packing>
+                    </child>
+                    <child>
+                      <object class="GtkRadioButton" id="circledCharacterRadio">
+                        <property name="label" translatable="yes">Circled character</property>
+                        <property name="visible">True</property>
+                        <property name="can_focus">True</property>
+                        <property name="receives_default">False</property>
+                        <property name="active">True</property>
+                        <property name="draw_indicator">True</property>
+                        <property name="group">verticalRadio</property>
+                      </object>
+                      <packing>
+                        <property name="expand">False</property>
+                        <property name="fill">True</property>
+                        <property name="position">4</property>
+                      </packing>
+                    </child>
+                    <child>
+                      <object class="GtkRadioButton" id="characterRadio">
+                        <property name="label" translatable="yes">Single character</property>
+                        <property name="visible">True</property>
+                        <property name="can_focus">True</property>
+                        <property name="receives_default">False</property>
+                        <property name="active">True</property>
+                        <property name="draw_indicator">True</property>
+                        <property name="group">verticalRadio</property>
+                      </object>
+                      <packing>
+                        <property name="expand">False</property>
+                        <property name="fill">True</property>
+                        <property name="position">5</property>
+                      </packing>
+                    </child>
+                  </object>
+                  <packing>
+                    <property name="expand">False</property>
+                    <property name="fill">True</property>
+                    <property name="position">1</property>
+                  </packing>
+                </child>
+              </object>
+              <packing>
+                <property name="resize">False</property>
+                <property name="shrink">True</property>
+              </packing>
+            </child>
+            <child>
+              <object class="GtkScrolledWindow" id="scrolledwindow2">
+                <property name="visible">True</property>
+                <property name="can_focus">True</property>
+                <property name="shadow_type">in</property>
+                <child>
+                  <object class="GtkTextView" id="textArea">
+                    <property name="visible">True</property>
+                    <property name="can_focus">True</property>
+                  </object>
+                </child>
+              </object>
+              <packing>
+                <property name="resize">True</property>
+                <property name="shrink">True</property>
+              </packing>
+            </child>
+          </object>
+          <packing>
+            <property name="expand">True</property>
+            <property name="fill">True</property>
+            <property name="position">0</property>
+          </packing>
+        </child>
+        <child>
+          <object class="GtkBox" id="buttonBox">
+            <property name="visible">True</property>
+            <property name="can_focus">False</property>
+            <property name="homogeneous">True</property>
+            <child>
+              <object class="GtkButton" id="quitButton">
+                <property name="label" translatable="yes">Quit</property>
+                <property name="visible">True</property>
+                <property name="can_focus">True</property>
+                <property name="receives_default">True</property>
+              </object>
+              <packing>
+                <property name="expand">False</property>
+                <property name="fill">True</property>
+                <property name="position">0</property>
+              </packing>
+            </child>
+            <child>
+              <object class="GtkButton" id="scrotButton">
+                <property name="label" translatable="yes">Run scrot</property>
+                <property name="visible">True</property>
+                <property name="can_focus">True</property>
+                <property name="focus_on_click">False</property>
+                <property name="receives_default">True</property>
+              </object>
+              <packing>
+                <property name="expand">False</property>
+                <property name="fill">True</property>
+                <property name="position">2</property>
+              </packing>
+            </child>
+          </object>
+          <packing>
+            <property name="expand">False</property>
+            <property name="fill">False</property>
+            <property name="position">1</property>
+          </packing>
+        </child>
+      </object>
+    </child>
+  </object>
+</interface>
diff --git a/shell.nix b/shell.nix
new file mode 100644
--- /dev/null
+++ b/shell.nix
@@ -0,0 +1,17 @@
+{ ghc, pkgs ? import <nixpkgs> {} }:
+
+pkgs.haskell.lib.buildStackProject {
+  inherit ghc;
+  name = "tesseract-env";
+  buildInputs = with pkgs;
+    [ cairo
+      gtk3
+      pango
+      pkgconfig
+      scrot
+      tesseract
+      xlibs.libX11
+      xlibs.xproto
+      zlib
+    ];
+}
diff --git a/src/Main.hs b/src/Main.hs
new file mode 100644
--- /dev/null
+++ b/src/Main.hs
@@ -0,0 +1,129 @@
+{-# LANGUAGE BangPatterns      #-}
+{-# LANGUAGE LambdaCase        #-}
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE QuasiQuotes       #-}
+module Main (main) where
+
+import           Control.Applicative ((<*>))
+import           Control.Concurrent
+import qualified Control.Concurrent.STM as STM
+import           Control.Monad
+import           Control.Monad.IO.Class (liftIO)
+import qualified Control.Monad.Trans.Resource as R
+import qualified Data.Conduit as C
+import qualified Data.Conduit.Binary as C
+import qualified Data.Conduit.Combinators as CO
+import           Data.Function ((&))
+import           Data.Monoid ((<>), mempty)
+import           Data.Text (Text)
+import           Graphics.UI.Gtk
+import           NaturalLanguageProcessing.TsunTsun.TH (litFile)
+import qualified Options.Applicative as Opt
+import           System.Directory (createDirectoryIfMissing, doesFileExist)
+import           System.Exit (ExitCode(..))
+import           System.FilePath (takeDirectory)
+import qualified System.Process.Typed as P
+
+runWindow :: TsunConfig -> IO ()
+runWindow TsunConfig { _imagePath = iFile, _tesseractPath = tPath } = do
+  _ <- initGUI
+  -- Load the UI from glade file.
+  let gladeContent = [litFile|res/mainwindow.glade|] :: String
+  builder <- builderNew
+  builderAddFromString builder gladeContent
+  let getObj :: GObjectClass cls => (GObject -> cls) -> Text -> IO cls
+      getObj = builderGetObject builder
+
+  -- Register quit callbacks.
+  window <- getObj castToWindow "mainWindow"
+  _ <- window `on` keyPressEvent $ do
+    tryEvent $ eventKeyName >>= \"q" -> liftIO mainQuit
+  _ <- window `on` destroyEvent $ tryEvent (liftIO mainQuit)
+  _ <- window `on` deleteEvent $ tryEvent (liftIO mainQuit)
+
+
+  -- Maps radio buttons to integers accepted by tesseract's -psm flag.
+  let imageModes :: [(Text, Int)]
+      imageModes = [ ("verticalRadio", 5)
+                   , ("columnRadio", 4)
+                   , ("blockRadio", 6)
+                   , ("circledCharacterRadio", 9)
+                   , ("characterRadio", 10)
+                   ]
+
+  -- Technically we always expect one default choice but just let the
+  -- code set it depending on what's active in the UI.
+  ocrMode <- STM.newTVarIO Nothing
+  forM_ imageModes $ \(r, m) -> getObj castToRadioButton r >>= \obj -> do
+    let updateMode = toggleButtonGetActive obj >>= \case
+          True -> STM.atomically $ STM.writeTVar ocrMode (Just m)
+          False -> pure ()
+    updateMode
+    obj `on` toggled $ updateMode
+
+  _ <- getObj castToButton "quitButton" >>= \b -> b `on` buttonActivated $ do
+    mainQuit
+
+  -- TVar that's fired when scrot has finished its work which should
+  -- indicate new contents at iFile. As a bonus, if the file already
+  -- exists at program start, we'll pass the STM guard straight away
+  -- and load it.
+  imageUpdated <- doesFileExist iFile >>= STM.newTVarIO
+  _ <- getObj castToButton "scrotButton" >>= \b -> do
+    buttonSetLabel b $ "scrot -s " <> iFile
+    b `on` buttonActivated $ void . forkIO $ do
+      createDirectoryIfMissing True $ takeDirectory iFile
+      P.runProcess (P.proc "scrot" ["-s", iFile]) >>= \case
+        ExitSuccess -> STM.atomically $ STM.writeTVar imageUpdated True
+        _ -> return ()
+
+  -- When a signal comes on imageUpdated after scrot terminates,
+  -- display the new image and feed it into tesseract, updating text
+  -- area when it's done.
+  textBuffer <- getObj castToTextView "textArea" >>= textViewGetBuffer
+  runImageUpdate <- postGUIAsync . flip imageSetFromFile iFile
+                    <$> getObj castToImage "imageArea"
+  void . forkIO . forever $ do
+    -- Block until we're notified the image file has been updated.
+    STM.atomically $ do
+      STM.readTVar imageUpdated >>= STM.check
+      STM.writeTVar imageUpdated False
+
+    runImageUpdate
+    psmFlags <- STM.readTVarIO ocrMode >>= \case
+      Nothing -> pure mempty
+      Just m' -> pure ["-psm", show m']
+    let procConf = P.proc tPath (psmFlags <>  ["-l", "jpn", "stdin", "stdout"])
+                 & P.setStdout P.createSource
+                 & P.setStdin P.createSink
+    P.withProcess procConf $ \p -> do
+      R.runResourceT . C.runConduit $ do
+        C.sourceFile iFile C.$$ P.getStdin p
+        P.getStdout p
+          C..| CO.decodeUtf8
+          C..| CO.mapM_ (liftIO . postGUIAsync . textBufferSetText textBuffer)
+
+  widgetShowAll window
+  mainGUI
+
+data TsunConfig = TsunConfig
+  { _imagePath :: !FilePath
+  , _tesseractPath :: !FilePath
+  } deriving (Show, Eq, Ord)
+
+main :: IO ()
+main = Opt.execParser opts >>= runWindow
+  where
+    tc = TsunConfig
+      <$> Opt.argument Opt.str
+          ( Opt.metavar "SCROT_FILE"
+         <> Opt.help "File to save to with scrot before it's processed with tesseract." )
+      <*> Opt.argument Opt.str
+          ( Opt.metavar "TESSERACT_BIN"
+         <> Opt.help "Path to the tesseract binary."
+         <> Opt.showDefaultWith id
+         <> Opt.value "tesseract" )
+    opts = Opt.info (tc Opt.<**> Opt.helper)
+      ( Opt.fullDesc
+     <> Opt.progDesc "Run a frontend to tesseract's Japanese OCR."
+     <> Opt.header "Frontend to tesseract's Japanese OCR." )
diff --git a/src/NaturalLanguageProcessing/TsunTsun/TH.hs b/src/NaturalLanguageProcessing/TsunTsun/TH.hs
new file mode 100644
--- /dev/null
+++ b/src/NaturalLanguageProcessing/TsunTsun/TH.hs
@@ -0,0 +1,17 @@
+module NaturalLanguageProcessing.TsunTsun.TH (litFile) where
+
+import Language.Haskell.TH
+import Language.Haskell.TH.Quote
+
+literally :: String -> Q Exp
+literally = return . LitE . StringL
+
+lit :: QuasiQuoter
+lit = QuasiQuoter { quoteExp = literally
+                  , quotePat = \s -> error s $ "quotePat: " ++ s
+                  , quoteType = \s -> error $ "quoteType: " ++ s
+                  , quoteDec = \s -> error $ "quoteDec: " ++ s}
+
+-- | Read file as-is.
+litFile :: QuasiQuoter
+litFile = quoteFile lit
diff --git a/stack.yaml b/stack.yaml
new file mode 100644
--- /dev/null
+++ b/stack.yaml
@@ -0,0 +1,16 @@
+resolver: lts-9.2
+packages:
+- .
+extra-deps: []
+skip-ghc-check: true
+flags: {}
+
+extra-package-dbs: []
+
+nix:
+  enable: true
+  # If we use custom shell with buildStackProject, locale gets set
+  # properly.
+  shell-file: shell.nix
+
+system-ghc: true
diff --git a/tsuntsun.cabal b/tsuntsun.cabal
new file mode 100644
--- /dev/null
+++ b/tsuntsun.cabal
@@ -0,0 +1,37 @@
+name:                tsuntsun
+version:             0.2.0.0
+synopsis:            Interacts with tesseract to ease reading of RAW Japanese manga.
+description:         Interacts with tesseract to ease reading of RAW Japanese manga.
+homepage:            https://github.com/Fuuzetsu/tsuntsun
+license:             BSD3
+license-file:        LICENSE
+author:              Mateusz Kowalczyk
+maintainer:          fuuzetsu@fuuzetsu.co.uk
+copyright:           2014-2017 Mateusz Kowalczyk
+category:            Natural Language Processing
+build-type:          Simple
+extra-source-files:  README.md
+                   , res/mainwindow.glade
+                   , shell.nix
+                   , stack.yaml
+cabal-version:       >=1.10
+
+executable tsuntsun
+  main-is:             Main.hs
+  other-modules:       NaturalLanguageProcessing.TsunTsun.TH
+  build-depends:       base < 5
+                     , conduit
+                     , conduit-combinators
+                     , conduit-extra
+                     , directory
+                     , filepath
+                     , gtk3
+                     , optparse-applicative
+                     , resourcet
+                     , stm
+                     , template-haskell
+                     , text
+                     , typed-process
+  hs-source-dirs:      src
+  default-language:    Haskell2010
+  ghc-options:         -threaded -O2 -Wall
