diff --git a/config.go b/config.go
index 3510e4a..f8696a4 100644
--- a/config.go
+++ b/config.go
@@ -29,14 +29,23 @@ type Config struct {
 	MXID string `yaml:"mxid"`
 	HS   string `yaml:"homeserver"`
 
-	dir     string   `yaml:"-"`
-	Session *Session `yaml:"-"`
+	dir     string       `yaml:"-"`
+	gmx     Gomuks       `yaml:"-"`
+	debug   DebugPrinter `yaml:"-"`
+	Session *Session     `yaml:"-"`
 }
 
-func (config *Config) Load(dir string) {
-	config.dir = dir
-	os.MkdirAll(dir, 0700)
-	configPath := filepath.Join(dir, "config.yaml")
+func NewConfig(gmx Gomuks, dir string) *Config {
+	return &Config{
+		gmx:   gmx,
+		debug: gmx.Debug(),
+		dir:   dir,
+	}
+}
+
+func (config *Config) Load() {
+	os.MkdirAll(config.dir, 0700)
+	configPath := filepath.Join(config.dir, "config.yaml")
 	data, err := ioutil.ReadFile(configPath)
 	if err != nil {
 		if os.IsNotExist(err) {
@@ -55,16 +64,17 @@ func (config *Config) Load(dir string) {
 }
 
 func (config *Config) Save() {
+	os.MkdirAll(config.dir, 0700)
 	data, err := yaml.Marshal(&config)
 	if err != nil {
-		debug.Print("Failed to marshal config")
+		config.debug.Print("Failed to marshal config")
 		panic(err)
 	}
 
 	path := filepath.Join(config.dir, "config.yaml")
 	err = ioutil.WriteFile(path, data, 0600)
 	if err != nil {
-		debug.Print("Failed to write config to", path)
+		config.debug.Print("Failed to write config to", path)
 		panic(err)
 	}
 }
diff --git a/debug.go b/debug.go
index 2533d5c..2498f9d 100644
--- a/debug.go
+++ b/debug.go
@@ -22,35 +22,54 @@ import (
 	"github.com/rivo/tview"
 )
 
+const DebugPaneHeight = 40
+
+type DebugPrinter interface {
+	Printf(text string, args ...interface{})
+	Print(text ...interface{})
+}
+
 type DebugPane struct {
-	text string
 	pane *tview.TextView
 	num  int
+	gmx  Gomuks
+}
+
+func NewDebugPane(gmx Gomuks) *DebugPane {
+	pane := tview.NewTextView()
+	pane.
+		SetScrollable(true).
+		SetWrap(true)
+	pane.SetChangedFunc(func() {
+		gmx.App().Draw()
+	})
+	pane.SetBorder(true).SetTitle("Debug output")
+	fmt.Fprintln(pane, "[0] Debug pane initialized")
+
+	return &DebugPane{
+		pane: pane,
+		num:  0,
+		gmx:  gmx,
+	}
 }
 
 func (db *DebugPane) Printf(text string, args ...interface{}) {
-	db.num++
-	db.Write(fmt.Sprintf("[%d] %s\n", db.num, fmt.Sprintf(text, args...)))
+	db.Write(fmt.Sprintf(text, args...))
 }
 
 func (db *DebugPane) Print(text ...interface{}) {
-	db.num++
-	db.Write(fmt.Sprintf("[%d] %s", db.num, fmt.Sprintln(text...)))
+	db.Write(fmt.Sprint(text...))
 }
 
 func (db *DebugPane) Write(text string) {
 	if db.pane != nil {
-		db.text += text
-		db.pane.SetText(db.text)
+		db.num++
+		fmt.Fprintf(db.pane, "[%d] %s\n", db.num, text)
 	}
 }
 
-func (db *DebugPane) Wrap(main *tview.Pages) tview.Primitive {
-	db.pane = tview.NewTextView()
-	db.pane.SetBorder(true).SetTitle("Debug output")
-	db.text += "[0] Debug pane initialized\n"
-	db.pane.SetText(db.text)
-	return tview.NewGrid().SetRows(0, 20).SetColumns(0).
+func (db *DebugPane) Wrap(main tview.Primitive) tview.Primitive {
+	return tview.NewGrid().SetRows(0, DebugPaneHeight).SetColumns(0).
 		AddItem(main, 0, 0, 1, 1, 1, 1, true).
 		AddItem(db.pane, 1, 0, 1, 1, 1, 1, false)
 }
diff --git a/gomuks.go b/gomuks.go
index 8ffc2e9..ac7d534 100644
--- a/gomuks.go
+++ b/gomuks.go
@@ -20,94 +20,84 @@ import (
 	"os"
 	"path/filepath"
 
-	"github.com/gdamore/tcell"
+	"github.com/matrix-org/gomatrix"
 	"github.com/rivo/tview"
 )
 
-var matrix = new(MatrixContainer)
-var config = new(Config)
-var debug = new(DebugPane)
+type Gomuks interface {
+	Debug() DebugPrinter
+	Matrix() *gomatrix.Client
+	MatrixContainer() *MatrixContainer
+	App() *tview.Application
+	UI() *GomuksUI
+	Config() *Config
+}
 
-func main() {
+type gomuks struct {
+	app    *tview.Application
+	ui     *GomuksUI
+	matrix *MatrixContainer
+	debug  *DebugPane
+	config *Config
+}
+
+func NewGomuks(debug bool) *gomuks {
 	configDir := filepath.Join(os.Getenv("HOME"), ".config/gomuks")
-	os.MkdirAll(configDir, 0700)
-	config.Load(configDir)
-
-	views := tview.NewPages()
-	InitUI(views)
-
-	main := debug.Wrap(views)
-
-	if len(config.MXID) > 0 {
-		config.LoadSession(config.MXID)
+	gmx := &gomuks{
+		app: tview.NewApplication(),
 	}
-	matrix.Init(config)
+	gmx.debug = NewDebugPane(gmx)
+	gmx.config = NewConfig(gmx, configDir)
+	gmx.ui = NewGomuksUI(gmx)
+	gmx.matrix = NewMatrixContainer(gmx)
+	gmx.ui.matrix = gmx.matrix
 
-	if err := tview.NewApplication().SetRoot(main, true).Run(); err != nil {
+	gmx.config.Load()
+	if len(gmx.config.MXID) > 0 {
+		gmx.config.LoadSession(gmx.config.MXID)
+	}
+
+	gmx.matrix.InitClient()
+
+	main := gmx.ui.InitViews()
+	if debug {
+		main = gmx.debug.Wrap(main)
+	}
+	gmx.app.SetRoot(main, true)
+
+	return gmx
+}
+
+func (gmx *gomuks) Start() {
+	if err := gmx.app.Run(); err != nil {
 		panic(err)
 	}
 }
 
-func InitUI(views *tview.Pages) {
-	views.AddPage("login", InitLoginUI(), true, true)
+func (gmx *gomuks) Debug() DebugPrinter {
+	return gmx.debug
 }
 
-func Center(width, height int, p tview.Primitive) tview.Primitive {
-	return tview.NewFlex().
-		AddItem(tview.NewBox(), 0, 1, false).
-		AddItem(tview.NewFlex().
-		SetDirection(tview.FlexRow).
-		AddItem(tview.NewBox(), 0, 1, false).
-		AddItem(p, height, 1, true).
-		AddItem(tview.NewBox(), 0, 1, false), width, 1, true).
-		AddItem(tview.NewBox(), 0, 1, false)
+func (gmx *gomuks) Matrix() *gomatrix.Client {
+	return gmx.matrix.client
 }
 
-type FormTextView struct {
-	*tview.TextView
+func (gmx *gomuks) MatrixContainer() *MatrixContainer {
+	return gmx.matrix
 }
 
-func (ftv *FormTextView) GetLabel() string {
-	return ""
+func (gmx *gomuks) App() *tview.Application {
+	return gmx.app
 }
 
-func (ftv *FormTextView) SetFormAttributes(label string, labelColor, bgColor, fieldTextColor, fieldBgColor tcell.Color) tview.FormItem {
-	return ftv
+func (gmx *gomuks) Config() *Config {
+	return gmx.config
 }
 
-func (ftv *FormTextView) GetFieldWidth() int {
-	_, _, w, _ := ftv.TextView.GetRect()
-	return w
+func (gmx *gomuks) UI() *GomuksUI {
+	return gmx.ui
 }
 
-func (ftv *FormTextView) SetFinishedFunc(handler func(key tcell.Key)) tview.FormItem {
-	ftv.SetDoneFunc(handler)
-	return ftv
-}
-
-func login(form *tview.Form) func() {
-	return func() {
-		hs := form.GetFormItem(0).(*tview.InputField).GetText()
-		mxid := form.GetFormItem(1).(*tview.InputField).GetText()
-		password := form.GetFormItem(2).(*tview.InputField).GetText()
-		debug.Printf("%s %s %s", hs, mxid, password)
-		config.HS = hs
-		debug.Print(matrix.Init(config))
-		debug.Print(matrix.Login(mxid, password))
-	}
-}
-
-func InitLoginUI() tview.Primitive {
-	form := tview.NewForm().SetButtonsAlign(tview.AlignCenter)
-	hs := config.HS
-	if len(hs) == 0 {
-		hs = "https://matrix.org"
-	}
-	form.
-		AddInputField("Homeserver", hs, 30, nil, nil).
-		AddInputField("Username", config.MXID, 30, nil, nil).
-		AddPasswordField("Password", "", 30, '*', nil).
-		AddButton("Log in", login(form))
-	form.SetBorder(true).SetTitle("Log in to Matrix")
-	return Center(45, 13, form)
+func main() {
+	NewGomuks(true).Start()
 }
diff --git a/matrix.go b/matrix.go
index b559f17..17147be 100644
--- a/matrix.go
+++ b/matrix.go
@@ -23,27 +23,36 @@ import (
 )
 
 type MatrixContainer struct {
-	lient   *gomatrix.Client
+	client  *gomatrix.Client
+	gmx     Gomuks
+	ui      *GomuksUI
+	debug   DebugPrinter
 	config  *Config
 	running bool
 	stop    chan bool
 }
 
-func (c *MatrixContainer) Initialized() bool {
-	return c.lient != nil
-}
-
-func (c *MatrixContainer) Init(config *Config) error {
-	c.config = config
-
-	if c.lient != nil {
-		c.lient.StopSync()
+func NewMatrixContainer(gmx Gomuks) *MatrixContainer {
+	c := &MatrixContainer{
+		config: gmx.Config(),
+		debug:  gmx.Debug(),
+		ui:     gmx.UI(),
+		gmx:    gmx,
 	}
 
+	return c
+}
+
+func (c *MatrixContainer) InitClient() error {
 	if len(c.config.HS) == 0 {
 		return fmt.Errorf("no homeserver in config")
 	}
 
+	if c.client != nil {
+		c.Stop()
+		c.client = nil
+	}
+
 	var mxid, accessToken string
 	if c.config.Session != nil {
 		accessToken = c.config.Session.AccessToken
@@ -51,7 +60,7 @@ func (c *MatrixContainer) Init(config *Config) error {
 	}
 
 	var err error
-	c.lient, err = gomatrix.NewClient(c.config.HS, mxid, accessToken)
+	c.client, err = gomatrix.NewClient(c.config.HS, mxid, accessToken)
 	if err != nil {
 		return err
 	}
@@ -64,8 +73,12 @@ func (c *MatrixContainer) Init(config *Config) error {
 	return nil
 }
 
+func (c *MatrixContainer) Initialized() bool {
+	return c.client != nil
+}
+
 func (c *MatrixContainer) Login(user, password string) error {
-	resp, err := c.lient.Login(&gomatrix.ReqLogin{
+	resp, err := c.client.Login(&gomatrix.ReqLogin{
 		Type:     "m.login.password",
 		User:     user,
 		Password: password,
@@ -73,7 +86,7 @@ func (c *MatrixContainer) Login(user, password string) error {
 	if err != nil {
 		return err
 	}
-	c.lient.SetCredentials(resp.UserID, resp.AccessToken)
+	c.client.SetCredentials(resp.UserID, resp.AccessToken)
 	c.config.MXID = resp.UserID
 	c.config.Save()
 
@@ -88,31 +101,50 @@ func (c *MatrixContainer) Login(user, password string) error {
 
 func (c *MatrixContainer) Stop() {
 	c.stop <- true
-	c.lient.StopSync()
+	c.client.StopSync()
+}
+
+func (c *MatrixContainer) UpdateRoomList() {
+	rooms, err := c.client.JoinedRooms()
+	if err != nil {
+		c.debug.Print(err)
+	}
+
+	c.ui.SetRoomList(rooms.JoinedRooms)
 }
 
 func (c *MatrixContainer) Start() {
-	debug.Print("Starting sync...")
+	c.debug.Print("Starting sync...")
 	c.running = true
-	c.lient.Store = c.config.Session
+	c.ui.SetView(ViewMain)
+	c.client.Store = c.config.Session
 
-	syncer := c.lient.Syncer.(*gomatrix.DefaultSyncer)
+	c.UpdateRoomList()
+
+	syncer := c.client.Syncer.(*gomatrix.DefaultSyncer)
 	syncer.OnEventType("m.room.message", c.HandleMessage)
 
 	for {
 		select {
 		case <-c.stop:
-			debug.Print("Stopping sync...")
+			c.debug.Print("Stopping sync...")
 			c.running = false
 			return
 		default:
-			if err := c.lient.Sync(); err != nil {
-				debug.Print("Sync() errored", err)
+			if err := c.client.Sync(); err != nil {
+				c.debug.Print("Sync() errored", err)
+			} else {
+				c.debug.Print("Sync() returned without error")
 			}
 		}
 	}
 }
 
 func (c *MatrixContainer) HandleMessage(evt *gomatrix.Event) {
-	debug.Print("Message received")
+	message, _ := evt.Content["body"].(string)
+	c.ui.Append(evt.RoomID, evt.Sender, message)
+}
+
+func (c *MatrixContainer) SendMessage(roomID, message string) {
+	c.client.SendText(roomID, message)
 }
diff --git a/session.go b/session.go
index 8f0e5d8..77e4bfa 100644
--- a/session.go
+++ b/session.go
@@ -31,6 +31,8 @@ type Session struct {
 	NextBatch   string
 	FilterID    string
 	Rooms       map[string]*gomatrix.Room
+
+	debug DebugPrinter `json:"-"`
 }
 
 func (config *Config) LoadSession(mxid string) {
@@ -43,19 +45,20 @@ func (config *Config) NewSession(mxid string) *Session {
 		MXID:  mxid,
 		path:  filepath.Join(config.dir, mxid+".session"),
 		Rooms: make(map[string]*gomatrix.Room),
+		debug: config.debug,
 	}
 }
 
 func (s *Session) Load() {
 	data, err := ioutil.ReadFile(s.path)
 	if err != nil {
-		debug.Print("Failed to read session from", s.path)
+		s.debug.Print("Failed to read session from", s.path)
 		panic(err)
 	}
 
 	err = json.Unmarshal(data, s)
 	if err != nil {
-		debug.Print("Failed to parse session at", s.path)
+		s.debug.Print("Failed to parse session at", s.path)
 		panic(err)
 	}
 }
@@ -63,13 +66,13 @@ func (s *Session) Load() {
 func (s *Session) Save() {
 	data, err := json.Marshal(s)
 	if err != nil {
-		debug.Print("Failed to marshal session of", s.MXID)
+		s.debug.Print("Failed to marshal session of", s.MXID)
 		panic(err)
 	}
 
 	err = ioutil.WriteFile(s.path, data, 0600)
 	if err != nil {
-		debug.Print("Failed to write session to", s.path)
+		s.debug.Print("Failed to write session to", s.path)
 		panic(err)
 	}
 }
diff --git a/ui.go b/ui.go
new file mode 100644
index 0000000..99c64eb
--- /dev/null
+++ b/ui.go
@@ -0,0 +1,71 @@
+// gomuks - A terminal Matrix client written in Go.
+// Copyright (C) 2018 Tulir Asokan
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program.  If not, see .
+
+package main
+
+import (
+	"github.com/rivo/tview"
+)
+
+// Allowed views in GomuksUI
+const (
+	ViewLogin = "login"
+	ViewMain  = "main"
+)
+
+type GomuksUI struct {
+	gmx    Gomuks
+	app    *tview.Application
+	matrix *MatrixContainer
+	debug  DebugPrinter
+	config *Config
+	views  *tview.Pages
+
+	mainView *tview.Grid
+	mainViewRoomList *tview.List
+	mainViewRoomView *tview.Pages
+	mainViewInput *tview.InputField
+	mainViewRooms map[string]*RoomView
+	currentRoomIndex int
+	roomList []string
+}
+
+func NewGomuksUI(gmx Gomuks) (ui *GomuksUI) {
+	ui = &GomuksUI{
+		gmx:    gmx,
+		app:    gmx.App(),
+		matrix: gmx.MatrixContainer(),
+		debug:  gmx.Debug(),
+		config: gmx.Config(),
+		views:  tview.NewPages(),
+	}
+	ui.views.SetChangedFunc(ui.Render)
+	return
+}
+
+func (ui *GomuksUI) Render() {
+	ui.app.Draw()
+}
+
+func (ui *GomuksUI) SetView(name string) {
+	ui.views.SwitchToPage(name)
+}
+
+func (ui *GomuksUI) InitViews() tview.Primitive {
+	ui.views.AddPage(ViewLogin, ui.MakeLoginUI(), true, true)
+	ui.views.AddPage(ViewMain, ui.MakeMainUI(), true, false)
+	return ui.views
+}
diff --git a/uiutil.go b/uiutil.go
new file mode 100644
index 0000000..1b29ebf
--- /dev/null
+++ b/uiutil.go
@@ -0,0 +1,55 @@
+// gomuks - A terminal Matrix client written in Go.
+// Copyright (C) 2018 Tulir Asokan
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program.  If not, see .
+
+package main
+
+import (
+	"github.com/gdamore/tcell"
+	"github.com/rivo/tview"
+)
+
+func Center(width, height int, p tview.Primitive) tview.Primitive {
+	return tview.NewFlex().
+		AddItem(tview.NewBox(), 0, 1, false).
+		AddItem(tview.NewFlex().
+		SetDirection(tview.FlexRow).
+		AddItem(tview.NewBox(), 0, 1, false).
+		AddItem(p, height, 1, true).
+		AddItem(tview.NewBox(), 0, 1, false), width, 1, true).
+		AddItem(tview.NewBox(), 0, 1, false)
+}
+
+type FormTextView struct {
+	*tview.TextView
+}
+
+func (ftv *FormTextView) GetLabel() string {
+	return ""
+}
+
+func (ftv *FormTextView) SetFormAttributes(label string, labelColor, bgColor, fieldTextColor, fieldBgColor tcell.Color) tview.FormItem {
+	return ftv
+}
+
+func (ftv *FormTextView) GetFieldWidth() int {
+	_, _, w, _ := ftv.TextView.GetRect()
+	return w
+}
+
+func (ftv *FormTextView) SetFinishedFunc(handler func(key tcell.Key)) tview.FormItem {
+	ftv.SetDoneFunc(handler)
+	return ftv
+}
diff --git a/view-login.go b/view-login.go
new file mode 100644
index 0000000..78981a9
--- /dev/null
+++ b/view-login.go
@@ -0,0 +1,49 @@
+// gomuks - A terminal Matrix client written in Go.
+// Copyright (C) 2018 Tulir Asokan
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program.  If not, see .
+
+package main
+
+import (
+	"github.com/rivo/tview"
+)
+
+func (ui *GomuksUI) MakeLoginUI() tview.Primitive {
+	form := tview.NewForm().SetButtonsAlign(tview.AlignCenter)
+	hs := ui.config.HS
+	if len(hs) == 0 {
+		hs = "https://matrix.org"
+	}
+	form.
+		AddInputField("Homeserver", hs, 30, nil, nil).
+		AddInputField("Username", ui.config.MXID, 30, nil, nil).
+		AddPasswordField("Password", "", 30, '*', nil).
+		AddButton("Log in", ui.login(form))
+	form.SetBorder(true).SetTitle("Log in to Matrix")
+	return Center(45, 13, form)
+}
+
+func (ui *GomuksUI) login(form *tview.Form) func() {
+	return func() {
+		hs := form.GetFormItem(0).(*tview.InputField).GetText()
+		mxid := form.GetFormItem(1).(*tview.InputField).GetText()
+		password := form.GetFormItem(2).(*tview.InputField).GetText()
+
+		ui.debug.Printf("Logging into %s as %s...", hs, mxid)
+		ui.config.HS = hs
+		ui.debug.Print(ui.matrix.InitClient())
+		ui.debug.Print(ui.matrix.Login(mxid, password))
+	}
+}
diff --git a/view-main.go b/view-main.go
new file mode 100644
index 0000000..acacddb
--- /dev/null
+++ b/view-main.go
@@ -0,0 +1,164 @@
+// gomuks - A terminal Matrix client written in Go.
+// Copyright (C) 2018 Tulir Asokan
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program.  If not, see .
+
+package main
+
+import (
+	"fmt"
+	"strings"
+
+	"github.com/gdamore/tcell"
+	"github.com/rivo/tview"
+)
+
+type RoomView struct {
+	*tview.Grid
+
+	sender, message *tview.TextView
+}
+
+func NewRoomView() *RoomView {
+	view := &RoomView{
+		tview.NewGrid(),
+		tview.NewTextView(),
+		tview.NewTextView(),
+	}
+	view.SetColumns(30, 0).SetRows(0)
+
+	view.sender.SetTextAlign(tview.AlignRight)
+	view.sender.SetScrollable(true)
+	view.message.SetScrollable(true)
+
+	view.AddItem(view.sender, 0, 0, 1, 1, 1, 1, false)
+	view.AddItem(view.message, 0, 1, 1, 1, 1, 1, false)
+
+	return view
+}
+
+func (ui *GomuksUI) MakeMainUI() tview.Primitive {
+	ui.mainView = tview.NewGrid().SetColumns(40, 0).SetRows(0, 2)
+
+	ui.mainViewRoomList = tview.NewList().ShowSecondaryText(false)
+	ui.mainViewRoomList.SetBorderPadding(1, 1, 1, 1)
+	ui.mainView.AddItem(ui.mainViewRoomList, 0, 0, 2, 1, 2, 1, false)
+
+	ui.mainViewRoomView = tview.NewPages()
+	ui.mainViewRoomView.SetChangedFunc(ui.Render)
+	ui.mainView.AddItem(ui.mainViewRoomView, 0, 1, 1, 1, 1, 1, false)
+
+	ui.mainViewInput = tview.NewInputField()
+	ui.mainViewInput.SetDoneFunc(func(key tcell.Key) {
+		if key == tcell.KeyEnter {
+			room, text := ui.currentRoom(), ui.mainViewInput.GetText()
+			if len(text) == 0 {
+				return
+			} else if text[0] == '/' {
+				args := strings.SplitN(text, " ", 2)
+				command := strings.ToLower(args[0])
+				args = args[1:]
+				ui.HandleCommand(room, command, args)
+			} else {
+				ui.matrix.SendMessage(room, text)
+			}
+			ui.mainViewInput.SetText("")
+		}
+	})
+	ui.mainView.AddItem(ui.mainViewInput, 1, 1, 1, 1, 1, 1, true)
+
+	ui.debug.Print(ui.mainViewInput.SetInputCapture(ui.MainUIKeyHandler))
+
+	ui.mainViewRooms = make(map[string]*RoomView)
+
+	return ui.mainView
+}
+
+func (ui *GomuksUI) HandleCommand(room, command string, args []string) {
+	switch command {
+	case "quit":
+		ui.matrix.Stop()
+		ui.app.Stop()
+	case "part":
+	case "leave":
+		ui.matrix.client.LeaveRoom(room)
+	case "join":
+		if len(args) == 0 {
+			ui.Append(room, "*", "Usage: /join ")
+		}
+		mxid := args[0]
+		server := mxid[strings.Index(mxid, ":")+1:]
+		ui.matrix.client.JoinRoom(mxid, server, nil)
+	}
+}
+
+func (ui *GomuksUI) MainUIKeyHandler(key *tcell.EventKey) *tcell.EventKey {
+	ui.debug.Print(key)
+	if key.Modifiers() == tcell.ModCtrl {
+		if key.Key() == tcell.KeyDown {
+			ui.SwitchRoom(ui.currentRoomIndex + 1)
+			ui.mainViewRoomList.SetCurrentItem(ui.currentRoomIndex)
+		} else if key.Key() == tcell.KeyUp {
+			ui.SwitchRoom(ui.currentRoomIndex - 1)
+			ui.mainViewRoomList.SetCurrentItem(ui.currentRoomIndex)
+		}
+	} else if key.Key() == tcell.KeyPgUp || key.Key() == tcell.KeyPgDn {
+		ui.mainViewRooms[ui.currentRoom()].sender.InputHandler()(key, nil)
+		ui.mainViewRooms[ui.currentRoom()].message.InputHandler()(key, nil)
+	} else {
+		return key
+	}
+	return nil
+}
+
+func (ui *GomuksUI) SetRoomList(rooms []string) {
+	ui.roomList = rooms
+	ui.mainViewRoomList.Clear()
+	for index, room := range rooms {
+		localRoomIndex := index
+		ui.mainViewRoomList.AddItem(room, "", 0, func() {
+			ui.SwitchRoom(localRoomIndex)
+		})
+		if !ui.mainViewRoomView.HasPage(room) {
+			roomView := NewRoomView()
+			ui.mainViewRooms[room] = roomView
+			ui.mainViewRoomView.AddPage(room, roomView, true, false)
+		}
+	}
+	ui.SwitchRoom(0)
+}
+
+func (ui *GomuksUI) currentRoom() string {
+	if len(ui.roomList) == 0 {
+		return ""
+	}
+	return ui.roomList[ui.currentRoomIndex]
+}
+
+func (ui *GomuksUI) SwitchRoom(roomIndex int) {
+	if roomIndex < 0 {
+		roomIndex = len(ui.roomList) - 1
+	}
+	ui.currentRoomIndex = roomIndex % len(ui.roomList)
+	ui.mainViewRoomView.SwitchToPage(ui.roomList[ui.currentRoomIndex])
+}
+
+func (ui *GomuksUI) Append(room, sender, message string) {
+	roomView, ok := ui.mainViewRooms[room]
+	if ok {
+		fmt.Fprintf(roomView.sender, sender)
+		fmt.Fprintf(roomView.message, sender)
+		ui.Render()
+	}
+}