Switch to using std::wstring, it supports UCS-4 already, just with a different type :-)
[ccbs] / bigscreen / ccbs_bigscreen.cpp
1 #include <cstdio>
2 #include <cstring>
3 #include <iconv.h>
4 #include <unistd.h>
5 #include <pqxx/pqxx>
6 #include <ft2build.h>
7 #include FT_FREETYPE_H
8 #include <tinyptc.h>
9
10 iconv_t ucs4_iconv;
11
12 // UCS-4 string with support for getting from UTF-8
13 class widestring : public std::wstring
14 {
15 public:
16         void operator= (const char *from)
17         {
18                 unsigned bytes = std::strlen(from);
19                 char *from_buf = strdup(from);
20                 wchar_t *to_buf = new wchar_t[bytes + 1];
21
22                 char *inptr = from_buf, *outptr = reinterpret_cast<char *> (to_buf);
23
24                 size_t in_left = bytes;
25                 size_t out_left = bytes * sizeof(wchar_t);
26
27                 size_t ret = iconv(ucs4_iconv, NULL, NULL, &outptr, &out_left);
28                 if (ret == (size_t)(-1)) {
29                         throw std::runtime_error("Error in iconv during initialization");
30                 }
31
32                 ret = iconv(ucs4_iconv, &inptr, &in_left, &outptr, &out_left);
33                 if (ret == (size_t)(-1)) {
34                         perror("iconv");
35                         throw std::runtime_error("Error in iconv during conversion");
36                 }
37
38                 erase(begin(), end());
39                 std::copy(to_buf, reinterpret_cast<wchar_t *> (outptr), std::back_inserter(*this));
40
41                 free(from_buf);
42                 delete[] to_buf;
43         }
44 };
45
46 template<>
47 void pqxx::from_string<widestring>(const char *from, widestring &to)
48 {
49         to = from;
50 }
51
52 int my_draw_text(const widestring &str, unsigned char *buf, int xpos, int ypos, bool real_render, int r, int g, int b, FT_Face face);
53
54 class Tournament {
55 public:
56         int id;
57         widestring name;
58 };
59
60 Tournament active_tournament;
61 FT_Face font;
62
63 /* A trigger that sets a flag whenever it's trigged. */
64 class FlagTrigger : pqxx::trigger {
65 private:
66         bool flag;
67         
68 public:
69         FlagTrigger(pqxx::connection_base &conn, const PGSTD::string &name)
70                 : pqxx::trigger(conn, name), flag(false) {}
71         virtual ~FlagTrigger() throw () {}
72         
73         virtual void operator() (int pid)
74         {
75                 flag = true;
76                 std::fprintf(stderr, "Received a flag trigger from pid %u\n", pid);
77         }
78
79         bool get_flag() const
80         {
81                 return flag;
82         }
83
84         void reset_flag()
85         {
86                 flag = false;
87         }
88 };
89
90 /* A transactor that fetches the current tournament and some information about it. */
91 class FetchCurrentTournament : public pqxx::transactor<> {
92 private:
93         Tournament *tourn;
94
95 public:
96         FetchCurrentTournament(Tournament *tourn) : tourn(tourn) {}
97         void operator() (pqxx::transaction<> &t)
98         {
99                 pqxx::result res( t.exec("SELECT * FROM bigscreen.active_tournament NATURAL JOIN tournaments") );
100                 try {
101                         pqxx::result::tuple tournament = res.at(0);
102
103                         tourn->id = tournament["tournament"].as(tourn->id);
104                         tourn->name = tournament["tournamentname"].as(tourn->name);
105                 } catch (PGSTD::out_of_range &e) {
106                         tourn->id = -1;
107                         tourn->name = "";
108                 }
109         }
110 };
111
112 void init(pqxx::connection &conn)
113 {
114         conn.perform(FetchCurrentTournament(&active_tournament));
115
116         if (active_tournament.id == -1) {
117                 std::fprintf(stderr, "No active tournament\n");
118         } else {
119                 std::fprintf(stderr, "Current tournament is %d (name: '%s')\n",
120                         active_tournament.id, active_tournament.name.c_str());
121         }
122 }
123
124 unsigned char framebuf[800 * 600 * 4];
125
126 void main_loop(pqxx::connection &conn)
127 {
128         if (active_tournament.id == -1) {
129                 // No active tournament, sleep a second or so and exit
130                 sleep(1);
131                 return;
132         }
133
134         memset(framebuf, 0, 800*600*4);
135         
136         pqxx::work t(conn, "trx");
137
138         // fetch all songs
139         pqxx::result res( t.exec("SELECT * FROM songs WHERE title LIKE 'M%'") );
140         unsigned y = 0;
141         for (pqxx::result::const_iterator i = res.begin(); i != res.end(); ++i) {
142                 my_draw_text(i["title"].as(widestring()), framebuf, 0, y, 1, 255, 255, 255, font);
143                 y += 20;
144 //              std::fprintf(stderr, "%s\n", i["title"].c_str());
145         }
146         t.commit();
147
148         ptc_update(framebuf);
149         sleep(1);
150 }
151
152 void init_freetype()
153 {
154         FT_Library library;
155         if (FT_Init_FreeType(&library))
156                 throw std::logic_error("FreeType init failed.");
157         if (FT_New_Face(library, "/usr/share/fonts/truetype/freefont/FreeSerif.ttf", 0, &font))
158                 throw std::logic_error("Face opening failed.");
159         if (FT_Set_Char_Size(font, 0, 12 * 64, 96, 96))
160                 throw std::logic_error("Size set failed.");
161 }
162
163 int my_draw_text(const widestring &str, unsigned char *buf, int xpos, int ypos, bool real_render, int r, int g, int b, FT_Face face)
164 {
165         FT_GlyphSlot slot = face->glyph;
166         int x = 0;
167
168         for (widestring::const_iterator i = str.begin(); i != str.end(); ++i) { 
169                 int glyph_index = FT_Get_Char_Index(face, *i);
170                 if (*i > 128) {
171                         printf("Loading U+%x\n", *i);
172                 }
173                 if (FT_Load_Glyph(face, glyph_index, FT_LOAD_RENDER))
174                         continue;
175
176                 if (real_render) {
177                         int y;
178                         FT_Bitmap *bm = &(slot->bitmap);
179                         for (y = 0; y < bm->rows; y++) {
180                                 int xx;
181                                 int dsty = ypos - slot->bitmap_top + y;
182                                 if (dsty < 0 || dsty > 599) continue;
183
184                                 unsigned char *dst = buf + dsty * 800*4 + (x + xpos + slot->bitmap_left)*4;
185                                 unsigned char *src = bm->buffer + y * bm->width;
186                                 for (xx = 0; xx < bm->width; xx++) {
187                                         *dst = (*dst * (256-*src) + r * *src) >> 8;
188                                         *dst++;
189                                         *dst = (*dst * (256-*src) + g * *src) >> 8;
190                                         *dst++;
191                                         *dst = (*dst * (256-*src) + b * *src) >> 8;
192                                         *dst++;
193                                         *dst++ = 0;
194                                         src++;
195                                 }
196                         }
197                 }
198
199                 x += slot->advance.x >> 6;
200         }
201
202         return x;
203 }
204
205
206 int main(int argc, char **argv)
207 {
208         ucs4_iconv = iconv_open("ucs-4le", "utf-8");   // FIXME: will be broken for big endian!
209         
210         ptc_open("CCBS bigscreen", 800, 600);
211         
212         try {
213                 init_freetype();
214                 pqxx::connection conn("dbname=ccbs host=altersex.samfundet.no user=ccbs password=GeT|>>B_");
215                 FlagTrigger tournament_changed(conn, "active_tournament");
216                 
217                 // when active_tournament is changed, we destroy everything and start from scratch
218                 for ( ;; ) {
219                         tournament_changed.reset_flag();
220                         init(conn);
221                         do {
222                                 main_loop(conn);
223                                 conn.get_notifs();
224                         } while (!tournament_changed.get_flag());
225                         std::fprintf(stderr, "active_tournament changed, resetting...\n");
226                 }
227         } catch (const std::exception &e) {
228                 std::fprintf(stderr, "Exception: %s\n", e.what());
229                 exit(1);
230         }
231         
232         return 0;
233 }