diff --git a/data/gauthenticator.gresource.xml b/data/gauthenticator.gresource.xml
index b4a064f..ea6b1ee 100644
--- a/data/gauthenticator.gresource.xml
+++ b/data/gauthenticator.gresource.xml
@@ -2,5 +2,6 @@
 
 	
 		gauth-window.ui
+		otp-row.ui
 	
 
diff --git a/data/otp-row.ui b/data/otp-row.ui
new file mode 100644
index 0000000..17a3795
--- /dev/null
+++ b/data/otp-row.ui
@@ -0,0 +1,66 @@
+
+
+
+  
+  
+  
+    True
+    False
+    
+      
+      
+        0
+        1
+        2
+      
+    
+    
+      
+      
+        2
+        0
+        2
+      
+    
+    
+      
+      
+        0
+        0
+      
+    
+    
+      
+      
+        1
+        0
+      
+    
+  
+
diff --git a/src/Makefile.am b/src/Makefile.am
index 2c93349..af12b64 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -2,10 +2,16 @@ bin_PROGRAMS = gauthenticator
 
 gresource_file = $(top_srcdir)/data/gauthenticator.gresource.xml
 
-gauthenticator_SOURCES = main.vala gauth-app.vala gauth-window.vala
+BUILT_SOURCES = resources.c
+gauthenticator_SOURCES = $(BUILT_SOURCES) base32.vala otp.vala otp-row.vala main.vala gauth-app.vala gauth-window.vala
 gauthenticator_CPPFLAGS = $(GAUTHENTICATOR_CFLAGS)
 gauthenticator_VALAFLAGS = --pkg gtk+-3.0 --gresources $(gresource_file) --target-glib=2.38
 
-gauthenticator_LDADD = $(GAUTHENTICATOR_LIBS)
+gauthenticator_LDADD = $(GAUTHENTICATOR_LIBS) -lm
 
 -include $(top_srcdir)/git.mk
+
+resource_files = $(shell $(GLIB_COMPILE_RESOURCES) --generate-dependencies --sourcedir=$(top_srcdir)/data $(gresource_file))
+resources.c: $(gresource_file) $(resource_files)
+	$(AM_V_GEN) $(GLIB_COMPILE_RESOURCES) --target=$@ --sourcedir=$(top_srcdir)/data --generate-source $<
+
diff --git a/src/base32.vala b/src/base32.vala
new file mode 100644
index 0000000..e0825a5
--- /dev/null
+++ b/src/base32.vala
@@ -0,0 +1,175 @@
+namespace Base32 {
+    public errordomain Base32Error {
+        INCORRECT_PADDING,
+        INCORRECT_MAP01;
+    }
+
+    private const string base32_alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";
+    private Array _base32_tab = null;
+    private GLib.HashTable _b32rev = null;
+
+    public GLib.Bytes b32encode(GLib.Bytes input) {
+        if (_base32_tab == null) {
+            _base32_tab = new Array();
+            var b32tab = new GLib.Bytes(base32_alphabet.data);
+
+
+            foreach (var a in b32tab.get_data()) {
+                foreach (var b in b32tab.get_data()) {
+                    _base32_tab.append_val("%c%c".printf(a, b));
+                }
+            }
+        }
+
+        var leftover = input.length % 5;
+        var s = new GLib.ByteArray();
+        s.append(input.get_data());
+
+        if (leftover > 0) {
+            var padding = new uint8[leftover + 1];
+
+            for (var i = 0; i <= leftover; i++) {
+                padding[i] = 0;
+            }
+
+            s.append(padding);
+        }
+
+        string encoded = "";
+        var length = 0;
+
+        for (var i = 0; i < s.len; i += 5) {
+            var next5 = s.data[i:i + 5];
+            uint64 c = ((uint64)next5[0] << 32) +
+                       ((uint64)next5[1] << 24) +
+                       ((uint64)next5[2] << 16) +
+                       ((uint64)next5[3] << 8) +
+                       (uint64)next5[4];
+
+            encoded += _base32_tab.index((uint)(c >> 30));
+            encoded += _base32_tab.index((uint)((c >> 20) & 0x3ff));
+            encoded += _base32_tab.index((uint)((c >> 10) & 0x3ff));
+            encoded += _base32_tab.index((uint)(c & 0x3ff));
+            length += 8;
+        }
+
+        int padder = 0;
+        if (leftover == 1) {
+            padder = -6;
+        } else if (leftover == 2) {
+            padder = -4;
+        } else if (leftover == 3) {
+            padder = -3;
+        } else if (leftover == 4) {
+            padder = -1;
+        }
+        for (var i = padder; i < 0; i++) {
+            encoded.data[encoded.length + i] = '=';
+        }
+
+        return new GLib.Bytes(encoded.data);
+    }
+
+    public GLib.Bytes b32decode(GLib.Bytes input, bool casefold, char map01) throws Base32Error {
+        if (_b32rev == null) {
+            _b32rev = new GLib.HashTable(null, null);
+
+            for (var i = 0; i < base32_alphabet.length; i++) {
+                _b32rev[base32_alphabet[i]] = i;
+            }
+        }
+
+        if (input.length % 8 != 0) {
+            throw new Base32Error.INCORRECT_PADDING("Incorrect padding");
+        }
+
+        var s = new GLib.ByteArray();
+
+        if (map01 != 0) {
+            if ((map01 != 'L') && (map01 != 'I')) {
+                throw new Base32Error.INCORRECT_MAP01("Incorrect map01 value");
+            }
+
+            var translated = new uint8[input.length];
+
+            var idata = input.get_data();
+
+            for (var i = 0; i < input.length; i++) {
+                if (idata[i] == 'O') {
+                    translated[i] = '0';
+                } else if (idata[i] == map01) {
+                    translated[i] = '1';
+                } else {
+                    translated[i] = idata[i];
+                }
+            }
+
+            s.append(translated);
+        } else {
+            s.append(input.get_data());
+        }
+
+        if (casefold) {
+            for (var i = 0; i < s.len; i++) {
+                s.data[i] = ((char)s.data[i]).toupper();
+            }
+        }
+
+        var padchars = 0;
+        while (s.data[s.len - padchars - 1] == '=') {
+            padchars++;
+        }
+
+        s.remove_range(s.len - padchars, padchars);
+
+
+        var decoded = new GLib.ByteArray();
+
+        uint64 acc = 0;
+
+        for (var i = 0; i < input.length; i += 8) {
+            var quanta = s.data[i: i + 8];
+            acc = 0;
+
+            for (var j = 0; j < 8; j++) {
+                var c = quanta[j];
+                acc = (acc << 5) + _b32rev[c];
+            }
+
+            uint8 res[5];
+
+            for (var j = 0; j < 5; j++) {
+                res[j] = (uint8)((acc >> (32 - j * 8)) & 0xff);
+            }
+
+            decoded.append(res);
+        }
+
+        if (padchars > 0) {
+            uint8 last[5];
+
+            for (var j = 0; j < 5; j++) {
+                last[j] = (uint8)((acc >> (32 - j * 8)) & 0xff);
+            }
+
+            var pad_offset = 0;
+
+            if (padchars == 1) {
+                pad_offset = -1;
+            } else if (padchars == 3) {
+                pad_offset = -2;
+            } else if (padchars == 4) {
+                pad_offset = -3;
+            } else if (padchars == 6) {
+                pad_offset = -4;
+            } else {
+                throw new Base32Error.INCORRECT_PADDING("Incorrect padding");
+            }
+
+            decoded.remove_range(decoded.len - 5, 5);
+            decoded.append(last[0:5 + pad_offset]);
+        }
+
+        return new GLib.Bytes(decoded.data);
+    }
+}
diff --git a/src/gauth-window.vala b/src/gauth-window.vala
index 9492ef5..f9c1184 100644
--- a/src/gauth-window.vala
+++ b/src/gauth-window.vala
@@ -1,11 +1,48 @@
 namespace GAuthenticator {
-	[GtkTemplate (ui = "/eu/polonkai/gergely/gauthenticator/gauth-window.ui")]
-	class Window : Gtk.ApplicationWindow {
-		[GtkChild]
-		private Gtk.ProgressBar countdown;
+    [GtkTemplate (ui = "/eu/polonkai/gergely/gauthenticator/gauth-window.ui")]
+    class Window : Gtk.ApplicationWindow {
+        [GtkChild]
+        private Gtk.ProgressBar countdown;
 
-		public Window(GAuthenticator.App app) {
-			Object(application: app);
-		}
-	}
+        [GtkChild]
+        private Gtk.ListBox auth_list;
+
+        private List rows = null;
+
+        private void update_totps(bool first = false) {
+            foreach (var row in rows) {
+                row.update(first);
+            }
+        }
+
+        private void check_update_totps() {
+            var now = new GLib.DateTime.now_utc();
+            var remaining = 30.0 - (now.get_seconds() % 30.0);
+
+            if (remaining <= 1.0 / 24.0) {
+                update_totps();
+            }
+        }
+
+        private bool update_countdown() {
+            var timestamp = new GLib.DateTime.now_local();
+            var current_seconds = timestamp.get_seconds();
+            var start_seconds = (current_seconds > 30.0) ? 30.0 : 0.0;
+
+            countdown.fraction = 1.0 - ((current_seconds - start_seconds) / 30.0);
+
+            check_update_totps();
+
+            return true;
+        }
+
+        public Window(GAuthenticator.App app) {
+            Object(application: app);
+
+            // Roughly 1/24 second, for a smooth(ish) update
+            GLib.Timeout.add(41, update_countdown);
+
+            update_totps(true);
+        }
+    }
 }
diff --git a/src/otp-row.vala b/src/otp-row.vala
new file mode 100644
index 0000000..1a9a463
--- /dev/null
+++ b/src/otp-row.vala
@@ -0,0 +1,42 @@
+namespace GAuthenticator {
+    [GtkTemplate (ui = "/eu/polonkai/gergely/gauthenticator/otp-row.ui")]
+    class OTPRow : Gtk.Grid {
+        [GtkChild]
+        private Gtk.Label code;
+
+        [GtkChild]
+        private Gtk.Label provider_name;
+
+        [GtkChild]
+        private Gtk.Label account_name;
+
+        public string secret {get; set;}
+        public string provider {get; set;}
+        public string account {get; set;}
+
+        private OTP.TOTP generator;
+
+        public OTPRow(string secret, string provider, string account) {
+            Object(secret: secret, provider: provider, account: account);
+
+            generator = new OTP.TOTP(secret, 6, GLib.ChecksumType.SHA1, 30);
+
+            provider_name.set_text(provider);
+            account_name.set_text(account);
+        }
+
+        public void update(bool first = false) {
+            uint64 val;
+
+            // FIXME: The first run is OK, but the rest is behind one cycle
+            if (first) {
+                val = generator.now();
+            } else {
+                var dt = new GLib.DateTime.now_utc();
+                val = generator.at(dt, 1);
+            }
+
+            code.set_text("%06lu".printf((ulong)val));
+        }
+    }
+}
diff --git a/src/otp.vala b/src/otp.vala
new file mode 100644
index 0000000..8efc842
--- /dev/null
+++ b/src/otp.vala
@@ -0,0 +1,72 @@
+namespace OTP {
+    abstract class Base : Object {
+        private GLib.Bytes _secret;
+
+        public string secret {
+            get {
+                // TODO: Terminate it with a zero!
+                return (string)_secret.get_data();
+            }
+
+            set {
+                _secret = Base32.b32decode(new GLib.Bytes(value.data), true, 0);
+            }
+        }
+
+        public int digits {get; set; default = 6;}
+        public GLib.ChecksumType digest {get; set; default = GLib.ChecksumType.SHA1;}
+
+        public Base(string secret_key, int digits, GLib.ChecksumType digest) {
+            Object(secret: secret_key, digits: digits, digest: digest);
+        }
+
+        private static GLib.Bytes int_to_bytestring(uint64 val) {
+            var size = (int) sizeof(uint64);
+            var result = new uint8[size];
+            for (int i = size - 1; i >= 0; i--) {
+                result[i] = (uint8)(val & 0xFF);
+                val >>= 8;
+            }
+
+            return new GLib.Bytes(result);
+        }
+
+        public uint64 generate_otp(uint64 input) {
+            // TODO: Make this dynamic based on the digest
+            uint8[256] result = {0};
+            size_t result_len = 256;
+
+            var hasher = new GLib.Hmac(digest, _secret.get_data());
+            hasher.update(int_to_bytestring(input).get_data());
+            hasher.get_digest(result, ref result_len);
+
+            var offset = result[result_len - 1] & 0xf;
+            uint64 code = ((uint64)(result[offset] & 0x7f) << 24 |
+                           (uint64)(result[offset + 1] & 0xff) << 16 |
+                           (uint64)(result[offset + 2] & 0xff) << 8 |
+                           (uint64)(result[offset + 3] & 0xff));
+            return Math.llrint(code % GLib.Math.pow(10, digits));
+        }
+    }
+
+    class TOTP : Base {
+        public int interval {get; set; default = 30;}
+
+        public TOTP(string secret_key, int digits, GLib.ChecksumType digest, int interval) {
+            Object(secret: secret_key, digits: digits, digest: digest);
+        }
+
+        public uint64 at(GLib.DateTime for_time, int counter_offset) {
+            return generate_otp(timecode(for_time) + counter_offset);
+        }
+
+        public uint64 now() {
+            return generate_otp(timecode(new GLib.DateTime.now_utc()));
+        }
+
+        public uint64 timecode(GLib.DateTime for_time) {
+            return Math.llrint(Math.ceil(for_time.to_unix() / interval));
+        }
+    }
+
+}