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