diff --git a/applications/gui2/src/modules/addsource.cpp b/applications/gui2/src/modules/addsource.cpp
index 2acdd22750db78902e2c4647248b48a31fa1da2b..c47599066994c4d738c8227175791a8184d772cb 100644
--- a/applications/gui2/src/modules/addsource.cpp
+++ b/applications/gui2/src/modules/addsource.cpp
@@ -25,6 +25,14 @@ ftl::Configurable *AddCtrl::add(const std::string &uri) {
 	return nullptr;
 }
 
+std::vector<std::string> AddCtrl::getHosts() {
+	return std::move(io->feed()->knownHosts());
+}
+
+std::set<ftl::stream::SourceInfo> AddCtrl::getRecent() {
+	return std::move(io->feed()->recentSources());
+}
+
 std::vector<std::string> AddCtrl::getNetSources() {
 	return std::move(io->feed()->availableNetworkSources());
 }
diff --git a/applications/gui2/src/modules/addsource.hpp b/applications/gui2/src/modules/addsource.hpp
index 51acefbf609167f8f01b32b984370a385ba63335..7e6570bb4357793281ce102bec93ad48865f1d45 100644
--- a/applications/gui2/src/modules/addsource.hpp
+++ b/applications/gui2/src/modules/addsource.hpp
@@ -21,6 +21,8 @@ public:
 
 	ftl::Configurable *add(const std::string &uri);
 
+	std::vector<std::string> getHosts();
+	std::set<ftl::stream::SourceInfo> getRecent();
 	std::vector<std::string> getNetSources();
 	std::vector<std::string> getFileSources();
 	std::vector<std::string> getDeviceSources();
diff --git a/applications/gui2/src/modules/themes.cpp b/applications/gui2/src/modules/themes.cpp
index 4576687c66c47b18b0da331dff4c97f15ae1ae3b..ee711a68657f051c012ee8bdb1c9af328c67a642 100644
--- a/applications/gui2/src/modules/themes.cpp
+++ b/applications/gui2/src/modules/themes.cpp
@@ -57,15 +57,24 @@ void Themes::init() {
 	viewtheme->mWindowDropShadowSize = 0;
 
 	auto* windowtheme_dark = screen->getTheme("window_dark");
-	windowtheme_dark->mWindowCornerRadius = 2;
-	windowtheme_dark->mButtonGradientBotFocused = nanogui::Color(90,255);
+	windowtheme_dark->mWindowCornerRadius = 5;
+	/*windowtheme_dark->mButtonGradientBotFocused = nanogui::Color(90,255);
 	windowtheme_dark->mButtonGradientBotUnfocused = nanogui::Color(70,255);
 	windowtheme_dark->mButtonGradientTopFocused = nanogui::Color(110,255);
 	windowtheme_dark->mButtonGradientTopUnfocused = nanogui::Color(110,255);
 	windowtheme_dark->mButtonGradientTopPushed = nanogui::Color(50,255);
-	windowtheme_dark->mButtonGradientBotPushed = nanogui::Color(90,255);
+	windowtheme_dark->mButtonGradientBotPushed = nanogui::Color(90,255);*/
+	windowtheme_dark->mButtonGradientBotFocused = nanogui::Color(60,255);
+	windowtheme_dark->mButtonGradientBotUnfocused = nanogui::Color(30,30,40,180);
+	windowtheme_dark->mButtonGradientTopFocused = nanogui::Color(60,255);
+	windowtheme_dark->mButtonGradientTopUnfocused = nanogui::Color(30,30,40,180);
+	windowtheme_dark->mButtonGradientTopPushed = nanogui::Color(60,180);
+	windowtheme_dark->mButtonGradientBotPushed = nanogui::Color(60,180);
 	windowtheme_dark->mButtonFontSize = 16;
 	windowtheme_dark->mIconScale = 0.85f;
+	windowtheme_dark->mBorderDark = nanogui::Color(90,255);
+	windowtheme_dark->mBorderMedium = nanogui::Color(90,255);
+	windowtheme_dark->mBorderLight = nanogui::Color(90,255);
 
 	auto* mediatheme = screen->getTheme("media");
 	mediatheme->mIconScale = 1.2f;
diff --git a/applications/gui2/src/views/addsource.cpp b/applications/gui2/src/views/addsource.cpp
index 1f962b4970b0cea566bc1d557188175fd7cd5ae1..321a1ccf153f95d2df0ecc6a99613c8e9dc38311 100644
--- a/applications/gui2/src/views/addsource.cpp
+++ b/applications/gui2/src/views/addsource.cpp
@@ -6,6 +6,8 @@
 #include <nanogui/layout.h>
 #include <nanogui/label.h>
 #include <nanogui/button.h>
+#include <nanogui/vscrollpanel.h>
+#include <nanogui/tabwidget.h>
 
 #include <loguru.hpp>
 
@@ -13,13 +15,19 @@
 using ftl::gui2::AddSourceWindow;
 
 AddSourceWindow::AddSourceWindow(nanogui::Widget* parent, AddCtrl *ctrl) :
-		nanogui::Window(parent, "Add Source"), ctrl_(ctrl) {
+		nanogui::Window(parent, ""), ctrl_(ctrl) {
 
 	using namespace nanogui;
 
-	//setFixedWidth(300);
-	setLayout(new GroupLayout(15, 6, 14, 10));
-	setPosition(Vector2i(parent->width()/2.0f - 100.0f, parent->height()/2.0f - 100.0f));
+	auto t = dynamic_cast<ftl::gui2::Screen*>(screen())->getTheme("window_dark");
+	setTheme(t);
+
+	//setFixedWidth(500);
+	setFixedSize(Vector2i(500,300));
+	setLayout(new nanogui::BoxLayout
+				(nanogui::Orientation::Vertical, nanogui::Alignment::Fill, 20, 10));
+
+	setPosition(Vector2i(parent->width()/2.0f - fixedWidth()/2.0f, parent->height()/2.0f - fixedHeight()/2.0f));
 
 	auto close = new nanogui::Button(buttonPanel(), "", ENTYPO_ICON_CROSS);
 	close->setTheme(dynamic_cast<ftl::gui2::Screen*>(screen())->getTheme("window_dark"));
@@ -40,28 +48,149 @@ AddSourceWindow::~AddSourceWindow() {
 
 }
 
+nanogui::Button *AddSourceWindow::_addButton(const std::string &s, nanogui::Widget *parent) {
+	using namespace nanogui;
+
+	ftl::URI uri(s);
+	int icon = 0;
+	switch (uri.getScheme()) {
+	case ftl::URI::SCHEME_DEVICE		: icon = ENTYPO_ICON_CAMERA; break;
+	case ftl::URI::SCHEME_FILE			: icon = ENTYPO_ICON_FOLDER_VIDEO; break;
+	case ftl::URI::SCHEME_FTL			: icon = ENTYPO_ICON_HOME; break;
+	case ftl::URI::SCHEME_WS			:
+	case ftl::URI::SCHEME_TCP			: icon = ENTYPO_ICON_CLASSIC_COMPUTER; break;
+	default: break;
+	}
+
+	auto *button = new Button(parent, ctrl_->getSourceName(s), icon);
+	if (ctrl_->isSourceActive(s)) {
+		button->setBackgroundColor(Color(0, 255, 0, 25));
+	}
+
+	button->setIconPosition(Button::IconPosition::Left);
+	button->setIconExtraScale(1.2);
+	button->setFontSize(18);
+	button->setTooltip(s);
+
+	button->setCallback([this, uri = s]() {
+		ctrl_->add(uri);
+		close();
+	});
+
+	return button;
+}
+
 void AddSourceWindow::rebuild() {
 	using namespace nanogui;
 	
-	new Label(this, "Device Sources", "sans-bold");
+	auto *title = new Label(this, "Add Source", "sans-bold");
+	title->setFontSize(28);
+
+	auto *tabs = new TabWidget(this);
+
+	auto *recent_tab = tabs->createTab("Recent");
+	recent_tab->setLayout(new nanogui::BoxLayout
+				(nanogui::Orientation::Vertical, nanogui::Alignment::Fill, 0, 0));
+	VScrollPanel *vscroll = new VScrollPanel(recent_tab);
+	vscroll->setFixedHeight(200);
+	Widget *recentscroll = new Widget(vscroll);
+	recentscroll->setLayout(new BoxLayout(Orientation::Vertical, Alignment::Fill, 10, 4));
 
-	auto *devicebuttons = new Widget(this);
-	devicebuttons->setLayout(new BoxLayout(Orientation::Horizontal, Alignment::Middle, 0, 4));
 	Button *button;
 
+	auto srcs = ctrl_->getRecent();
+
+	for (auto &s : srcs) {
+		_addButton(s.uri, recentscroll);
+	}
+
+	auto *dev_tab = tabs->createTab("Devices");
+	dev_tab->setLayout(new nanogui::BoxLayout
+				(nanogui::Orientation::Vertical, nanogui::Alignment::Fill, 0, 0));
+	vscroll = new VScrollPanel(dev_tab);
+	vscroll->setFixedHeight(150);
+	Widget *devscroll = new Widget(vscroll);
+	devscroll->setLayout(new BoxLayout(Orientation::Vertical, Alignment::Fill, 10, 4));
+
 	auto devsrcs = ctrl_->getDeviceSources();
 
 	for (auto &s : devsrcs) {
-		button = new Button(devicebuttons, ctrl_->getSourceName(s));
-		if (ctrl_->isSourceActive(s)) {
-			button->setBackgroundColor(Color(0, 255, 0, 25));
+		_addButton(s, devscroll);
+	}
+
+	auto *host_tab = tabs->createTab("Hosts");
+	host_tab->setLayout(new nanogui::BoxLayout
+				(nanogui::Orientation::Vertical, nanogui::Alignment::Fill, 0, 0));
+	vscroll = new VScrollPanel(host_tab);
+	vscroll->setFixedHeight(150);
+	Widget *hostscroll = new Widget(vscroll);
+	hostscroll->setLayout(new BoxLayout(Orientation::Vertical, Alignment::Fill, 10, 4));
+
+	auto hostsrcs = ctrl_->getHosts();
+
+	for (auto &s : hostsrcs) {
+		_addButton(s, hostscroll);
+	}
+
+	auto *stream_tab = tabs->createTab("Streams");
+	stream_tab->setLayout(new nanogui::BoxLayout
+				(nanogui::Orientation::Vertical, nanogui::Alignment::Fill, 0, 0));
+	vscroll = new VScrollPanel(stream_tab);
+	vscroll->setFixedHeight(150);
+	Widget *streamscroll = new Widget(vscroll);
+	streamscroll->setLayout(new BoxLayout(Orientation::Vertical, Alignment::Fill, 10, 4));
+
+	auto streamsrcs = ctrl_->getNetSources();
+
+	for (auto &s : streamsrcs) {
+		_addButton(s, streamscroll);
+	}
+
+	auto *file_tab = tabs->createTab("Files");
+	file_tab->setLayout(new nanogui::BoxLayout
+				(nanogui::Orientation::Vertical, nanogui::Alignment::Fill, 0, 0));
+	vscroll = new VScrollPanel(file_tab);
+	vscroll->setFixedHeight(150);
+	Widget *filescroll = new Widget(vscroll);
+	filescroll->setLayout(new BoxLayout(Orientation::Vertical, Alignment::Fill, 10, 4));
+
+	button = new Button(filescroll, "Open", ENTYPO_ICON_FOLDER);
+	button->setIconPosition(Button::IconPosition::Left);
+	button->setIconExtraScale(1.2);
+	button->setFontSize(18);
+	button->setTooltip("Open FTL File");
+	button->setCallback([this]() {
+		try {
+			std::string filename = file_dialog({ {"ftl", "FTL Captures"} }, false);
+			if (filename.size() > 0 && filename[0] == '/') {
+				filename = std::string("file://") + filename;
+			} else {
+				filename = std::string("file:///") + filename;
+			}
+#ifdef WIN32
+			auto p = filename.find_first_of('\\');
+			while (p != std::string::npos) {
+				filename[p] = '/';
+				p = filename.find_first_of('\\');
+			}
+#endif
+			ctrl_->add(filename);
+		} catch (const std::exception &e) {
+			LOG(ERROR) << "File load exception: " << e.what();
 		}
+		close();
+	});
 
-		button->setCallback([this, s]() {
-			ctrl_->add(s);
-			close();
-		});
+	auto filesrcs = ctrl_->getFileSources();
+
+	for (auto &s : filesrcs) {
+		_addButton(s, filescroll);
 	}
+
+
+	tabs->setActiveTab(0);
+
+
 	/*button = new Button(devicebuttons, "Camera(s)");
 	button->setCallback([this]() { ctrl_->add("device:camera"); close(); });
 	button = new Button(devicebuttons, "Realsense");
@@ -73,7 +202,7 @@ void AddSourceWindow::rebuild() {
 	button = new Button(devicebuttons, "OpenVR");
 	button->setCallback([this]() { ctrl_->add("device:openvr"); close(); });*/
 
-	new Label(this, "Network Sources", "sans-bold");
+	/*new Label(this, "Network Sources", "sans-bold");
 
 	auto *netbuttons = new Widget(this);
 	netbuttons->setLayout(new BoxLayout(Orientation::Horizontal, Alignment::Middle, 0, 4));
@@ -89,36 +218,15 @@ void AddSourceWindow::rebuild() {
 			ctrl_->add(s);
 			close();
 		});
-	}
+	}*/
 
-	new Label(this, "Files", "sans-bold");
+	//new Label(this, "Files", "sans-bold");
 
-	auto *filebuttons = new Widget(this);
-	filebuttons->setLayout(new BoxLayout(Orientation::Horizontal, Alignment::Middle, 0, 4));
-	button = new Button(filebuttons, "", ENTYPO_ICON_FOLDER);
-	button->setCallback([this]() {
-		try {
-			std::string filename = file_dialog({ {"ftl", "FTL Captures"} }, false);
-			if (filename.size() > 0 && filename[0] == '/') {
-				filename = std::string("file://") + filename;
-			} else {
-				filename = std::string("file:///") + filename;
-			}
-#ifdef WIN32
-			auto p = filename.find_first_of('\\');
-			while (p != std::string::npos) {
-				filename[p] = '/';
-				p = filename.find_first_of('\\');
-			}
-#endif
-			ctrl_->add(filename);
-		} catch (const std::exception &e) {
-			LOG(ERROR) << "File load exception: " << e.what();
-		}
-		close();
-	});
+	//auto *filebuttons = new Widget(this);
+	//filebuttons->setLayout(new BoxLayout(Orientation::Horizontal, Alignment::Middle, 0, 4));
+	
 
-	auto filesrcs = ctrl_->getFileSources();
+	/*auto filesrcs = ctrl_->getFileSources();
 
 	for (auto &s : filesrcs) {
 		button = new Button(filebuttons, ctrl_->getSourceName(s));
@@ -130,7 +238,7 @@ void AddSourceWindow::rebuild() {
 			ctrl_->add(s);
 			close();
 		});
-	}
+	}*/
 }
 
 void AddSourceWindow::close() {
diff --git a/applications/gui2/src/views/addsource.hpp b/applications/gui2/src/views/addsource.hpp
index 4d340f856c832fd28450adf94c43907c423b1b87..938e1e3dde73d87b484814d8b98f731394045a0f 100644
--- a/applications/gui2/src/views/addsource.hpp
+++ b/applications/gui2/src/views/addsource.hpp
@@ -25,6 +25,8 @@ private:
 	void close();
 	void rebuild();
 
+	nanogui::Button *_addButton(const std::string &s, nanogui::Widget *parent);
+
 	ftl::Handle new_source_handle_;
 	MUTEX mutex_;
 	std::atomic_bool do_rebuild_=false;
diff --git a/components/common/cpp/include/ftl/uri.hpp b/components/common/cpp/include/ftl/uri.hpp
index 65ac79a97d1f0336f4132e54095a8cc726736b93..3a80439fca816ace6ecec81cfc80af921c3b848a 100644
--- a/components/common/cpp/include/ftl/uri.hpp
+++ b/components/common/cpp/include/ftl/uri.hpp
@@ -51,9 +51,9 @@ namespace ftl {
 		 * Get the URI without query parameters, and limit path to length N.
 		 * If N is negative then it is taken from full path length.
 		 */
-		std::string getBaseURI(int n);
+		std::string getBaseURI(int n) const;
 
-		std::string getBaseURIWithUser();
+		std::string getBaseURIWithUser() const;
 
 		std::string getPathSegment(int n) const;
 
@@ -61,11 +61,13 @@ namespace ftl {
 		void setAttribute(const std::string &key, int value);
 
 		template <typename T>
-		T getAttribute(const std::string &key) {
+		T getAttribute(const std::string &key) const {
 			auto i = m_qmap.find(key);
 			return (i != m_qmap.end()) ? T(i->second) : T();
 		}
 
+		bool hasAttribute(const std::string &a) const { return m_qmap.count(a) > 0; }
+
 		std::string to_string() const;
 
 		void to_json(nlohmann::json &);
@@ -89,13 +91,13 @@ namespace ftl {
 	};
 
 	template <>
-	inline int URI::getAttribute<int>(const std::string &key) {
+	inline int URI::getAttribute<int>(const std::string &key) const {
 		auto i = m_qmap.find(key);
 		return (i != m_qmap.end()) ? std::stoi(i->second) : 0;
 	}
 
 	template <>
-	inline std::string URI::getAttribute<std::string>(const std::string &key) {
+	inline std::string URI::getAttribute<std::string>(const std::string &key) const {
 		auto i = m_qmap.find(key);
 		return (i != m_qmap.end()) ? i->second : "";
 	}
diff --git a/components/common/cpp/src/uri.cpp b/components/common/cpp/src/uri.cpp
index 37a5d579215faecbb3fa0be36391ed986e6e52f1..9311b841b2fd227d16f63c572b0ba5dcb80a454c 100644
--- a/components/common/cpp/src/uri.cpp
+++ b/components/common/cpp/src/uri.cpp
@@ -160,7 +160,7 @@ string URI::getPathSegment(int n) const {
 	else return m_pathseg[N];
 }
 
-string URI::getBaseURI(int n) {
+string URI::getBaseURI(int n) const {
 	if (n >= (int)m_pathseg.size()) return m_base;
 	if (n >= 0) {
 		string r = m_protostr + string("://") + m_host + ((m_port != 0) ? string(":") + std::to_string(m_port) : "");
@@ -182,7 +182,7 @@ string URI::getBaseURI(int n) {
 	} else return "";
 }
 
-std::string URI::getBaseURIWithUser() {
+std::string URI::getBaseURIWithUser() const {
 	std::string result;
 
 	result += m_protostr + "://";
diff --git a/components/streams/include/ftl/streams/feed.hpp b/components/streams/include/ftl/streams/feed.hpp
index 95642070546cdd185565055df87cf8d2518d42cf..23d500b61547643e930f2e5eea340d5dd2366060 100644
--- a/components/streams/include/ftl/streams/feed.hpp
+++ b/components/streams/include/ftl/streams/feed.hpp
@@ -21,6 +21,13 @@ namespace ftl {
 namespace render { class Source; }
 namespace stream {
 
+struct SourceInfo {
+	std::string uri;
+	int64_t last_used;
+
+	inline bool operator<(const SourceInfo &o) const { return last_used > o.last_used; }
+};
+
 class Feed : public ftl::Configurable {
 public:
 	/**
@@ -66,69 +73,6 @@ public:
 		std::vector<ftl::Handle> handles_;
 	};
 
-private:
-	// public methods acquire lock if necessary, private methods assume locking
-	// managed by caller
-	SHARED_MUTEX mtx_;
-	std::condition_variable cv_net_connect_;
-
-	ftl::net::Universe* const net_;
-	std::unique_ptr<ftl::data::Pool> pool_;
-	std::unique_ptr<ftl::stream::Intercept> interceptor_;
-	 // multiple streams to single fs
-	std::unique_ptr<ftl::stream::Muxer> stream_;
-
-	 // streams to fs
-	std::unique_ptr<ftl::stream::Receiver> receiver_;
-	ftl::Handle handle_receiver_;
-
-	// framesets to stream
-	std::unique_ptr<ftl::stream::Sender> sender_;
-	ftl::Handle handle_sender_;
-
-	std::unique_ptr<ftl::stream::Sender> recorder_;
-	std::unique_ptr<ftl::stream::Broadcast> record_stream_;
-	ftl::Handle handle_record_;
-	ftl::Handle record_recv_handle_;
-	Filter *record_filter_;
-
-	//ftl::Handler<const ftl::data::FrameSetPtr&> frameset_cb_;
-	std::unordered_map<std::string, uint32_t> fsid_lookup_;
-	std::map<uint32_t, ftl::data::FrameSetPtr> latest_;
-	std::unordered_map<std::string, uint32_t> groups_;
-
-	std::unordered_map<uint32_t, std::list<ftl::stream::Stream*>> streams_;
-	std::unordered_map<uint32_t, ftl::rgbd::Source*> devices_;
-	std::unordered_map<uint32_t, ftl::render::Source*> renderers_;
-	std::unordered_map<uint32_t, ftl::operators::Graph*> pre_pipelines_;
-	std::list<ftl::streams::ManualSourceBuilder*> render_builders_;
-
-	std::vector<std::string> netcams_;
-	ftl::Handler<uint32_t> new_sources_cb_;
-	ftl::Handler<uint32_t> add_src_cb_;
-	ftl::Handler<uint32_t> remove_sources_cb_;
-
-	std::vector<Filter*> filters_;
-
-	uint32_t fs_counter_ = 0;
-	uint32_t file_counter_ = 0;
-
-	uint32_t allocateFrameSetId(const std::string &group);
-
-	void add(uint32_t fsid, const std::string &uri, ftl::stream::Stream *s);
-
-	/** callback for network (adds new sorces on connect/...) */
-	void updateNetSources();
-	/** select channels and sources based on current filters_; */
-	void select();
-
-	void _createPipeline(uint32_t fsid);
-	ftl::operators::Graph* _addPipeline(uint32_t fsid);
-
-	void _beginRecord(Filter *f);
-	void _stopRecording();
-	bool _isRecording();
-
 public:
 
 	Feed(nlohmann::json &config, ftl::net::Universe *net);
@@ -158,6 +102,8 @@ public:
 	std::vector<ftl::data::FrameID> listFrames();
 	std::vector<unsigned int> listFrameSets();
 
+	std::set<ftl::stream::SourceInfo> recentSources();
+	std::vector<std::string> knownHosts();
 	std::vector<std::string> availableNetworkSources();
 	std::vector<std::string> availableFileSources();
 	std::vector<std::string> availableDeviceSources();
@@ -198,6 +144,71 @@ public:
 	void removeFilter(Filter* filter);
 
 	void autoConnect();
+
+private:
+	// public methods acquire lock if necessary, private methods assume locking
+	// managed by caller
+	SHARED_MUTEX mtx_;
+	std::condition_variable cv_net_connect_;
+
+	ftl::net::Universe* const net_;
+	std::unique_ptr<ftl::data::Pool> pool_;
+	std::unique_ptr<ftl::stream::Intercept> interceptor_;
+	 // multiple streams to single fs
+	std::unique_ptr<ftl::stream::Muxer> stream_;
+
+	 // streams to fs
+	std::unique_ptr<ftl::stream::Receiver> receiver_;
+	ftl::Handle handle_receiver_;
+
+	// framesets to stream
+	std::unique_ptr<ftl::stream::Sender> sender_;
+	ftl::Handle handle_sender_;
+
+	std::unique_ptr<ftl::stream::Sender> recorder_;
+	std::unique_ptr<ftl::stream::Broadcast> record_stream_;
+	ftl::Handle handle_record_;
+	ftl::Handle record_recv_handle_;
+	Filter *record_filter_;
+
+	//ftl::Handler<const ftl::data::FrameSetPtr&> frameset_cb_;
+	std::unordered_map<std::string, uint32_t> fsid_lookup_;
+	std::map<uint32_t, ftl::data::FrameSetPtr> latest_;
+	std::unordered_map<std::string, uint32_t> groups_;
+
+	std::unordered_map<uint32_t, std::list<ftl::stream::Stream*>> streams_;
+	std::unordered_map<uint32_t, ftl::rgbd::Source*> devices_;
+	std::unordered_map<uint32_t, ftl::render::Source*> renderers_;
+	std::unordered_map<uint32_t, ftl::operators::Graph*> pre_pipelines_;
+	std::list<ftl::streams::ManualSourceBuilder*> render_builders_;
+
+	std::vector<std::string> netcams_;
+	ftl::Handler<uint32_t> new_sources_cb_;
+	ftl::Handler<uint32_t> add_src_cb_;
+	ftl::Handler<uint32_t> remove_sources_cb_;
+
+	std::vector<Filter*> filters_;
+
+	uint32_t fs_counter_ = 0;
+	uint32_t file_counter_ = 0;
+
+	uint32_t allocateFrameSetId(const std::string &group);
+
+	void add(uint32_t fsid, const std::string &uri, ftl::stream::Stream *s);
+	nlohmann::json &_add_recent_source(const ftl::URI &uri);
+
+	/** callback for network (adds new sorces on connect/...) */
+	void _updateNetSources(ftl::net::Peer *p);
+	void _updateNetSources(ftl::net::Peer *p, const std::string &uri);
+	/** select channels and sources based on current filters_; */
+	void select();
+
+	void _createPipeline(uint32_t fsid);
+	ftl::operators::Graph* _addPipeline(uint32_t fsid);
+
+	void _beginRecord(Filter *f);
+	void _stopRecording();
+	bool _isRecording();
 };
 
 }
diff --git a/components/streams/src/feed.cpp b/components/streams/src/feed.cpp
index 8a2eb4a2840a95f726b0b6db77d2534da943cb90..47c388ca3d64ad4e14e620da7ef4fa3a6eb5c745 100644
--- a/components/streams/src/feed.cpp
+++ b/components/streams/src/feed.cpp
@@ -93,6 +93,7 @@ Feed::Feed(nlohmann::json &config, ftl::net::Universe*net) :
 	//feed_config = ftl::loadJSON(FTL_LOCAL_CONFIG_ROOT "/feed.json");
 	restore(ftl::Configurable::getID(), {
 		"recent_files",
+		"recent_sources",
 		"known_hosts",
 		"auto_host_connect",
 		"auto_host_sources",
@@ -147,18 +148,14 @@ Feed::Feed(nlohmann::json &config, ftl::net::Universe*net) :
 	});
 
 	net_->onConnect([this](ftl::net::Peer *p) {
-		ftl::pool.push([this](int id) {
-			// FIXME: Find better option that waiting here.
-			// Wait to make sure streams have started properly.
-			std::this_thread::sleep_for(std::chrono::milliseconds(100));
-			//UNIQUE_LOCK(mtx_, lk);
-			updateNetSources();
+		ftl::pool.push([this,p](int id) {
+			_updateNetSources(p);
 		});
 	});
 
-	net_->bind("add_stream", [this](std::string uri){
+	net_->bind("add_stream", [this](ftl::net::Peer &p, std::string uri){
 		//UNIQUE_LOCK(mtx_, lk);
-		updateNetSources();
+		_updateNetSources(&p, uri);
 	});
 
 	net_->onDisconnect([this](ftl::net::Peer *) {
@@ -215,7 +212,7 @@ Feed::Feed(nlohmann::json &config, ftl::net::Universe*net) :
 
 	stream_->begin();
 
-	if (value("auto_host_connect", true)) autoConnect();
+	//if (value("auto_host_connect", true)) autoConnect();
 }
 
 Feed::~Feed() {
@@ -466,16 +463,37 @@ void Feed::removeFilter(Feed::Filter* filter) {
 	}
 }
 
-void Feed::updateNetSources() {
-	auto netcams =
-		net_->findAll<std::string>("list_streams");
+void Feed::_updateNetSources(ftl::net::Peer *p, const std::string &s) {
+	UNIQUE_LOCK(mtx_, lk);
+	netcams_.push_back(s);
+
+	// TODO: Auto add source
+
+	ftl::URI uri(s);
+	_add_recent_source(uri)["host"] = p->getURI();
+
+	ftl::pool.push([this](int id) {
+		new_sources_cb_.trigger(0);
+	});
+}
+
+void Feed::_updateNetSources(ftl::net::Peer *p) {
+	//auto netcams =
+	//	net_->findAll<std::string>("list_streams");
+
+	auto peerstreams = p->call<std::vector<std::string>>("list_streams");
 
 	UNIQUE_LOCK(mtx_, lk);
-	netcams_ = std::move(netcams);
+	//netcams_ = std::move(netcams);
+	netcams_.insert(netcams_.end(), peerstreams.begin(), peerstreams.end());
 
-	if (value("auto_host_sources", false)) {
+	for (const auto &s : peerstreams) {
+		ftl::URI uri(s);
+		_add_recent_source(uri)["host"] = p->getURI();
+	}
+
+	/*if (value("auto_host_sources", false)) {
 		for (auto s : netcams_) {
-			ftl::URI uri(s);
 			const std::string group = uri.getAttribute<std::string>("group");
 
 			if (fsid_lookup_.count(uri.getBaseURI()) == 0) {
@@ -498,7 +516,7 @@ void Feed::updateNetSources() {
 				LOG(INFO) << "Stream exists: " << s;
 			}
 		}
-	}
+	}*/
 
 	ftl::pool.push([this](int id) {
 		new_sources_cb_.trigger(0);
@@ -526,6 +544,32 @@ std::vector<std::string> Feed::availableFileSources() {
 	return files;
 }
 
+std::vector<std::string> Feed::knownHosts() {
+	std::vector<std::string> hosts;
+	auto &known = getConfig()["known_hosts"];
+
+	for (auto &f : known.items()) {
+		hosts.push_back(f.key());
+	}
+
+	return hosts;
+}
+
+std::set<ftl::stream::SourceInfo> Feed::recentSources() {
+	std::set<ftl::stream::SourceInfo> result;
+
+	auto &recent = getConfig()["recent_sources"];
+
+	for (auto &f : recent.items()) {
+		ftl::stream::SourceInfo info;
+		info.uri = f.key();
+		info.last_used = f.value()["last_open"].get<int64_t>();
+		result.insert(info);
+	}
+
+	return result;
+}
+
 std::vector<std::string> Feed::availableDeviceSources() {
 	std::vector<std::string> results;
 
@@ -596,11 +640,29 @@ std::string Feed::getName(const std::string &puri) {
 		return "Unknown Device";
 	} else if (uri.getScheme() == ftl::URI::SCHEME_FILE) {
 		return getConfig()["recent_files"][uri.getBaseURI()].value("name", "FTLFile");
+	} else if (uri.getScheme() == ftl::URI::SCHEME_TCP || uri.getScheme() == ftl::URI::SCHEME_WS) {
+		return uri.getBaseURI();
 	}
 
 	return uri.getPathSegment(-1);
 }
 
+nlohmann::json &Feed::_add_recent_source(const ftl::URI &uri) {
+	auto &known = getConfig()["recent_sources"];
+	auto &details = known[uri.getBaseURI()];
+	std::string name = uri.getPathSegment(-1);
+
+	if (uri.hasAttribute("name")) {
+		name = uri.getAttribute<std::string>("name");
+	} else if (uri.getScheme() == ftl::URI::SCHEME_FILE) {
+		name = name.substr(0, name.find_last_of('.'));
+	}
+
+	details["name"] = name;
+	details["last_open"] = ftl::timer::get_time();
+	return details;
+}
+
 void Feed::add(uint32_t fsid, const std::string &uri, ftl::stream::Stream* stream) {
 	fsid_lookup_[uri] = fsid;
 	latest_[fsid] = nullptr;
@@ -660,6 +722,8 @@ uint32_t Feed::add(const std::string &path) {
 		file_details["name"] = fname.substr(0, fname.find_last_of('.'));
 		file_details["last_open"] = ftl::timer::get_time();
 
+		_add_recent_source(uri);
+
 		// TODO: URI normalization; should happen in add(,,) or add(,,,) take
 		// ftl::URI instead of std::string as argument. Note the bug above.
 		// TODO: write unit test for uri parsing
@@ -713,6 +777,8 @@ uint32_t Feed::add(const std::string &path) {
 			creator->start();
 		}
 
+		_add_recent_source(uri);
+
 		add_src_cb_.trigger(fsid);
 		return fsid;
 	}
@@ -732,6 +798,15 @@ uint32_t Feed::add(const std::string &path) {
 
 	}
 	else if (scheme == ftl::URI::SCHEME_FTL) {
+		// Attempt to ensure connection first
+		auto &known = getConfig()["recent_sources"];
+		auto &details = known[uri.getBaseURI()];
+		if (details.contains("host")) {
+			net_->connect(details["host"].get<std::string>())->waitConnection();
+		} else {
+			// See if it can otherwise be found?
+		}
+
 		auto *stream = ftl::create<ftl::stream::Net>
 			(this, std::string("netstream")
 			+std::to_string(fsid_lookup_.size()), net_);