diff --git a/CHANGELOG.md b/CHANGELOG.md
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,18 @@
+
+## 3.0.0.0
+
+* Remove the one-pixel white border around the `GtkNotebook` (the GTK widget thing
+  that contains the tabs). [#138](https://github.com/cdepillabout/termonad/pull/138)
+* Add a right-click menu for the terminal.  It currently allows copy and
+  paste.  [#136](https://github.com/cdepillabout/termonad/pull/136)  Thanks
+  @jecaro!
+* Add a preferences file that settings will be saved to and read from at
+  `~/.config/termonad/termonad.yaml`.  You can change settings with the
+  Preferences dialog.  **The settings will only be used from this file if you
+  do not have a `~/.config/termonad/termonad.hs` file**.
+  [#140](https://github.com/cdepillabout/termonad/pull/140) Thanks again
+  @jecaro!
+
 ## 2.1.0.0
 
 * Add a menu option to set preferences for a running Termonad session.  The preferences you have set are lost when you end the Termonad session. [#130](https://github.com/cdepillabout/termonad/pull/130)  Thanks @jecaro!
diff --git a/README.md b/README.md
--- a/README.md
+++ b/README.md
@@ -235,6 +235,32 @@
 
 ### Configuring Termonad
 
+Termonad has two different ways to be configured.
+
+The first way is to use the built-in Preferences editor.  You can find this in
+the `Preferences` menu under `Edit` in the menubar.
+
+When opening Termonad for the first time, it will create a preferences file at
+`~/.config/termonad/termonad.yaml`.  When you change a setting in the
+Preferences editor, Termonad will update the setting in the preferences file.
+
+When running Termonad, it will load settings from the preferences file.
+Do not edit the preferences file by hand, because it will be overwritten when
+updating settings in the Preferences editor.
+
+This method is perfect for users who only want to make small changes to the
+Termonad settings, like the default font size.
+
+The second way to configure Termonad is to use a Haskell-based settings file,
+called `~/.config/termonad/termonad.hs` by default.  This method allows you to
+make large, sweeping changes to Termonad.  This method is recommended for power
+users.
+
+**WARNING: If you have a `~/.config/termonad/termonad.hs` file, then all
+settings from `~/.config/termonad/termonad.yaml` will be ignored.  If you want
+to set *ANY* settings in `~/.config/termonad/termonad.hs`, then you must
+set *ALL* settings in `~/.config/termonad/termonad.hs`.**
+
 The following is an example Termonad configuration file. You should save this to
 `~/.config/termonad/termonad.hs`. You can find more information on the available
 configuration options within the
diff --git a/app/Main.hs b/app/Main.hs
--- a/app/Main.hs
+++ b/app/Main.hs
@@ -2,7 +2,7 @@
 module Main where
 
 import Termonad (defaultMain)
-import Termonad.Config (defaultTMConfig)
+import Termonad.Config (tmConfigFromPreferencesFile)
 
 main :: IO ()
-main = defaultMain defaultTMConfig
+main = defaultMain =<< tmConfigFromPreferencesFile
diff --git a/glade/preferences.glade b/glade/preferences.glade
--- a/glade/preferences.glade
+++ b/glade/preferences.glade
@@ -233,7 +233,7 @@
               </packing>
             </child>
             <child>
-              <object class="GtkLabel">
+              <object class="GtkLabel" id="warning">
                 <property name="visible">True</property>
                 <property name="can_focus">False</property>
                 <property name="margin_left">5</property>
diff --git a/src/Termonad/App.hs b/src/Termonad/App.hs
--- a/src/Termonad/App.hs
+++ b/src/Termonad/App.hs
@@ -5,7 +5,7 @@
 import Termonad.Prelude
 
 import Config.Dyre (defaultParams, projectName, realMain, showError, wrapMain)
-import Control.Lens ((&), (.~), (^.), (^..), over, set)
+import Control.Lens ((&), (.~), (^.), (^..), over, set, view)
 import Data.FocusList (focusList, moveFromToFL, updateFocusFL)
 import Data.Sequence (findIndexR)
 import GI.Gdk (castTo, managedForeignPtr, screenGetDefault)
@@ -29,6 +29,7 @@
   , Dialog(Dialog)
   , Entry(Entry)
   , FontButton(FontButton)
+  , Label(Label)
   , PolicyType(PolicyTypeAutomatic)
   , PositionType(PositionTypeRight)
   , ResponseType(ResponseTypeAccept, ResponseTypeNo, ResponseTypeYes)
@@ -68,6 +69,7 @@
   , labelNew
   , notebookGetNPages
   , notebookNew
+  , notebookSetShowBorder
   , onEntryActivate
   , onNotebookPageRemoved
   , onNotebookPageReordered
@@ -84,6 +86,7 @@
   , widgetDestroy
   , widgetGrabFocus
   , widgetSetCanFocus
+  , widgetSetVisible
   , widgetShow
   , widgetShowAll
   , windowPresent
@@ -118,6 +121,8 @@
   , terminalSetScrollbackLines
   , terminalSetWordCharExceptions
   )
+import System.Environment (getExecutablePath)
+import System.FilePath (takeFileName)
 
 import Paths_termonad (getDataFileName)
 import Termonad.Gtk (appNew, objFromBuildUnsafe)
@@ -143,6 +148,7 @@
   , lensTerm
   , lensWordCharExceptions
   )
+import Termonad.PreferencesFile (saveToPreferencesFile)
 import Termonad.Term
   ( createTerm
   , relabelTabs
@@ -365,6 +371,9 @@
   fontDesc <- createFontDescFromConfig tmConfig
   note <- notebookNew
   widgetSetCanFocus note False
+  -- If this is not set to False, then there will be a one pixel white border
+  -- shown around the notebook.
+  notebookSetShowBorder note False
   boxPackStart box note True True 0
 
   mvarTMState <- newEmptyTMState tmConfig app win note fontDesc
@@ -754,7 +763,14 @@
     objFromBuildUnsafe preferencesBuilder "scrollbackLen" SpinButton
   adjustmentNew 0 0 (fromIntegral (maxBound :: Int)) 1 10 0 >>=
     spinButtonSetAdjustment scrollbackLenSpinButton
+  warningLabel <- objFromBuildUnsafe preferencesBuilder "warning" Label
 
+  -- We show the warning label only if the user has launched termonad with a
+  -- termonad.hs file
+  executablePath <- getExecutablePath
+  let hasTermonadHs = takeFileName executablePath == "termonad-linux-x86_64"
+  widgetSetVisible warningLabel hasTermonadHs
+
   -- Make the dialog modal
   maybeWin <- applicationGetActiveWindow app
   windowSetTransientFor preferencesDialog maybeWin
@@ -808,6 +824,9 @@
         . over lensShowTabBar (`fromMaybe` maybeShowTabBar)
         . over lensCursorBlinkMode (`fromMaybe` maybeCursorBlinkMode)
         )
+
+    -- Save the changes to the preferences files
+    withMVar mvarTMState $ saveToPreferencesFile . view lensTMStateConfig
 
     -- Update the app with new settings
     applyNewPreferences mvarTMState
diff --git a/src/Termonad/Config.hs b/src/Termonad/Config.hs
--- a/src/Termonad/Config.hs
+++ b/src/Termonad/Config.hs
@@ -54,8 +54,10 @@
   , ShowScrollbar(..)
   , ShowTabBar(..)
   , CursorBlinkMode(..)
+  , tmConfigFromPreferencesFile
   ) where
 
 import GI.Vte (CursorBlinkMode(..))
 
+import Termonad.PreferencesFile (tmConfigFromPreferencesFile)
 import Termonad.Types
diff --git a/src/Termonad/PreferencesFile.hs b/src/Termonad/PreferencesFile.hs
new file mode 100644
--- /dev/null
+++ b/src/Termonad/PreferencesFile.hs
@@ -0,0 +1,70 @@
+
+module Termonad.PreferencesFile where
+
+import Termonad.Prelude
+
+import Data.Yaml (decodeFileEither, encode, prettyPrintParseException)
+import System.Directory
+  ( XdgDirectory(XdgConfig)
+  , createDirectoryIfMissing
+  , doesFileExist
+  , getXdgDirectory
+  )
+
+import Termonad.Types
+  ( ConfigOptions
+  , TMConfig(TMConfig, hooks, options)
+  , defaultConfigHooks
+  , defaultConfigOptions
+  )
+
+-- | Get the path to the preferences file @~\/.config\/termonad\/termonad.yaml@.
+getPreferencesFile :: IO FilePath
+getPreferencesFile = do
+  -- Get the termonad config directory
+  confDir <- getXdgDirectory XdgConfig "termonad"
+  createDirectoryIfMissing True confDir
+  pure $ confDir </> "termonad.yaml"
+
+-- | Read the configuration for the preferences file
+-- @~\/.config\/termonad\/termonad.yaml@. This file stores only the 'options' of
+-- 'TMConfig' so 'hooks' are initialized with 'defaultConfigHooks'.  If the
+-- file doesn't exist, create it with the default values.
+tmConfigFromPreferencesFile :: IO TMConfig
+tmConfigFromPreferencesFile = do
+  confFile <- getPreferencesFile
+  -- If there is no preferences file we create it with the default values
+  exists <- doesFileExist confFile
+  unless exists $ writePreferencesFile confFile defaultConfigOptions
+  -- Read the configuration file
+  eitherOptions <- decodeFileEither confFile
+  options <-
+    case eitherOptions of
+      Left err -> do
+        hPutStrLn stderr $ "Error parsing file " <> pack confFile
+        hPutStrLn stderr $ pack $ prettyPrintParseException err
+        pure defaultConfigOptions
+      Right options -> pure options
+  pure $ TMConfig { options = options, hooks = defaultConfigHooks }
+
+writePreferencesFile :: FilePath -> ConfigOptions -> IO ()
+writePreferencesFile confFile options = do
+  let yaml = encode options
+      yamlWithComment =
+        "# DO NOT EDIT THIS FILE BY HAND!\n" <>
+        "#\n" <>
+        "# This file is generated automatically by the Preferences dialog\n" <>
+        "# in Termonad.  Please open the Preferences dialog if you wish to\n" <>
+        "# modify this file.\n" <>
+        "#\n" <>
+        "# The settings in this file will be ignored if you have a\n" <>
+        "# termonad.hs file in this same directory.\n\n" <>
+        yaml
+  writeFile confFile yamlWithComment
+
+-- | Save the configuration to the preferences file
+-- @~\/.config\/termonad\/termonad.yaml@
+saveToPreferencesFile :: TMConfig -> IO ()
+saveToPreferencesFile TMConfig { options = options } = do
+  confFile <- getPreferencesFile
+  writePreferencesFile confFile options
diff --git a/src/Termonad/Term.hs b/src/Termonad/Term.hs
--- a/src/Termonad/Term.hs
+++ b/src/Termonad/Term.hs
@@ -8,15 +8,20 @@
 import Data.Colour.SRGB (Colour, RGB(RGB), toSRGB)
 import Data.FocusList (appendFL, deleteFL, getFocusItemFL)
 import GI.Gdk
-  ( EventKey
+  ( EventButton
+  , EventKey
   , RGBA
+  , getEventButtonButton
   , newZeroRGBA
   , setRGBABlue
   , setRGBAGreen
   , setRGBARed
   )
+import GI.Gdk.Constants (pattern BUTTON_SECONDARY)
 import GI.Gio
-  ( noCancellable
+  ( menuAppend
+  , menuNew
+  , noCancellable
   )
 import GI.GLib
   ( SpawnFlags(SpawnFlagsDefault)
@@ -47,6 +52,9 @@
   , labelSetEllipsize
   , labelSetLabel
   , labelSetMaxWidthChars
+  , menuAttachToWidget
+  , menuNewFromModel
+  , menuPopupAtPointer
   , noAdjustment
   , notebookAppendPage
   , notebookDetachTab
@@ -56,6 +64,7 @@
   , notebookSetShowTabs
   , notebookSetTabReorderable
   , onButtonClicked
+  , onWidgetButtonPressEvent
   , onWidgetKeyPressEvent
   , scrolledWindowNew
   , scrolledWindowSetPolicy
@@ -446,6 +455,7 @@
     relabelTab notebook tabLabel scrolledWin vteTerm
   void $ onWidgetKeyPressEvent vteTerm $ handleKeyPress mvarTMState
   void $ onWidgetKeyPressEvent scrolledWin $ handleKeyPress mvarTMState
+  void $ onWidgetButtonPressEvent vteTerm $ handleMousePress vteTerm
   void $ onTerminalChildExited vteTerm $ \_ -> termExit notebookTab mvarTMState
 
   -- Put the keyboard focus on the term
@@ -457,3 +467,18 @@
   -- Run user-defined hooks for modifying the newly-created VTE Terminal.
   createTermHook (hooks tmStateConfig) mvarTMState vteTerm
   pure tmTerm
+
+-- | Popup the context menu on right click
+handleMousePress :: Terminal -> EventButton -> IO Bool
+handleMousePress vteTerm event = do
+  button <- getEventButtonButton event
+  let rightClick = button == fromIntegral BUTTON_SECONDARY
+  when rightClick $ do
+    menuModel <- menuNew
+    menuAppend menuModel (Just "Copy") (Just "app.copy")
+    menuAppend menuModel (Just "Paste") (Just "app.paste")
+    menu <- menuNewFromModel menuModel
+    menuAttachToWidget menu vteTerm Nothing
+    menuPopupAtPointer menu Nothing
+  pure rightClick
+
diff --git a/src/Termonad/Types.hs b/src/Termonad/Types.hs
--- a/src/Termonad/Types.hs
+++ b/src/Termonad/Types.hs
@@ -1,4 +1,4 @@
-{-# LANGUAGE StandaloneDeriving #-}
+{-# OPTIONS_GHC -fno-warn-orphans #-}
 
 module Termonad.Types where
 
@@ -6,6 +6,12 @@
 
 import Data.FocusList (FocusList, emptyFL, singletonFL, getFocusItemFL, lengthFL)
 import Data.Unique (Unique, hashUnique, newUnique)
+import Data.Yaml
+  ( FromJSON(parseJSON)
+  , ToJSON(toJSON)
+  , Value(String)
+  , withText
+  )
 import GI.Gtk
   ( Application
   , ApplicationWindow
@@ -19,7 +25,7 @@
   , notebookGetNPages
   )
 import GI.Pango (FontDescription)
-import GI.Vte (Terminal, CursorBlinkMode(CursorBlinkModeOn))
+import GI.Vte (Terminal, CursorBlinkMode(..))
 import Text.Pretty.Simple (pPrint)
 import Text.Show (Show(showsPrec), ShowS, showParen, showString)
 
@@ -258,7 +264,7 @@
     -- can be thought of as one pixel.  The function
     -- 'GI.Pango.fontDescriptionSetAbsoluteSize' is used to set the font size.
     -- See the documentation for that function for more info.
-  deriving (Eq, Show)
+  deriving (Eq, FromJSON, Generic, Show, ToJSON)
 
 -- | The default 'FontSize' used if not specified.
 --
@@ -300,7 +306,7 @@
     -- ^ The font family to use.  Example: @"DejaVu Sans Mono"@ or @"Source Code Pro"@
   , fontSize :: !FontSize
     -- ^ The font size.
-  } deriving (Eq, Show)
+  } deriving (Eq, FromJSON, Generic, Show, ToJSON)
 
 -- | The default 'FontConfig' to use if not specified.
 --
@@ -347,7 +353,7 @@
                         -- needed.
   | ShowScrollbarIfNeeded -- ^ Only show the scrollbar if there are too many
                           -- lines on the terminal to show all at once.
-  deriving (Enum, Eq, Show)
+  deriving (Enum, Eq, Generic, FromJSON, Show, ToJSON)
 
 -- | Whether or not to show the tab bar for switching tabs.
 data ShowTabBar
@@ -355,7 +361,7 @@
                     -- open.  This may be confusing if you plan on using multiple tabs.
   | ShowTabBarAlways -- ^ Always show the tab bar, even if you only have one tab open.
   | ShowTabBarIfNeeded  -- ^ Only show the tab bar if you have multiple tabs open.
-  deriving (Enum, Eq, Show)
+  deriving (Enum, Eq, Generic, FromJSON, Show, ToJSON)
 
 -- | Configuration options for Termonad.
 --
@@ -386,7 +392,22 @@
     -- ^ When to show the tab bar.
   , cursorBlinkMode :: !CursorBlinkMode
     -- ^ How to handle cursor blink.
-  } deriving (Eq, Show)
+  } deriving (Eq, Generic, FromJSON, Show, ToJSON)
+
+instance FromJSON CursorBlinkMode where
+  parseJSON = withText "CursorBlinkMode" $ \c -> do
+    case (c :: Text) of
+      "CursorBlinkModeSystem" -> pure CursorBlinkModeSystem
+      "CursorBlinkModeOn" -> pure CursorBlinkModeOn
+      "CursorBlinkModeOff" -> pure CursorBlinkModeOff
+      _ -> fail "Wrong value for CursorBlinkMode"
+
+instance ToJSON CursorBlinkMode where
+  toJSON CursorBlinkModeSystem = String "CursorBlinkModeSystem"
+  toJSON CursorBlinkModeOn = String "CursorBlinkModeOn"
+  toJSON CursorBlinkModeOff = String "CursorBlinkModeOff"
+  -- Not supposed to happened fall back to system
+  toJSON (AnotherCursorBlinkMode _) = String "CursorBlinkModeSystem"
 
 -- | The default 'ConfigOptions'.
 --
diff --git a/termonad.cabal b/termonad.cabal
--- a/termonad.cabal
+++ b/termonad.cabal
@@ -1,5 +1,5 @@
 name:                termonad
-version:             2.1.0.0
+version:             3.0.0.0
 synopsis:            Terminal emulator configurable in Haskell
 description:         Please see <https://github.com/cdepillabout/termonad#readme README.md>.
 homepage:            https://github.com/cdepillabout/termonad
@@ -49,6 +49,7 @@
                      , Termonad.Keys
                      , Termonad.Lenses
                      , Termonad.Pcre
+                     , Termonad.PreferencesFile
                      , Termonad.Prelude
                      , Termonad.Term
                      , Termonad.Types
@@ -82,6 +83,7 @@
                      , singletons
                      , text
                      , transformers
+                     , yaml
                      , xml-conduit
                      , xml-html-qq
   default-language:    Haskell2010
@@ -91,6 +93,7 @@
                      , DeriveAnyClass
                      , DeriveFoldable
                      , DeriveFunctor
+                     , DeriveGeneric
                      , DerivingStrategies
                      , EmptyCase
                      , ExistentialQuantification
diff --git a/test/readme/README.lhs b/test/readme/README.lhs
--- a/test/readme/README.lhs
+++ b/test/readme/README.lhs
@@ -235,6 +235,32 @@
 
 ### Configuring Termonad
 
+Termonad has two different ways to be configured.
+
+The first way is to use the built-in Preferences editor.  You can find this in
+the `Preferences` menu under `Edit` in the menubar.
+
+When opening Termonad for the first time, it will create a preferences file at
+`~/.config/termonad/termonad.yaml`.  When you change a setting in the
+Preferences editor, Termonad will update the setting in the preferences file.
+
+When running Termonad, it will load settings from the preferences file.
+Do not edit the preferences file by hand, because it will be overwritten when
+updating settings in the Preferences editor.
+
+This method is perfect for users who only want to make small changes to the
+Termonad settings, like the default font size.
+
+The second way to configure Termonad is to use a Haskell-based settings file,
+called `~/.config/termonad/termonad.hs` by default.  This method allows you to
+make large, sweeping changes to Termonad.  This method is recommended for power
+users.
+
+**WARNING: If you have a `~/.config/termonad/termonad.hs` file, then all
+settings from `~/.config/termonad/termonad.yaml` will be ignored.  If you want
+to set *ANY* settings in `~/.config/termonad/termonad.hs`, then you must
+set *ALL* settings in `~/.config/termonad/termonad.hs`.**
+
 The following is an example Termonad configuration file. You should save this to
 `~/.config/termonad/termonad.hs`. You can find more information on the available
 configuration options within the
