]> git.sesse.net Git - nageru/blob - theme.cpp
Add a static image input (fixed to 1280x720 for now, and uploaded rather inefficiently).
[nageru] / theme.cpp
1 #include "theme.h"
2
3 #include <assert.h>
4 #include <lauxlib.h>
5 #include <lua.h>
6 #include <lualib.h>
7 #include <movit/effect.h>
8 #include <movit/effect_chain.h>
9 #include <movit/image_format.h>
10 #include <movit/mix_effect.h>
11 #include <movit/overlay_effect.h>
12 #include <movit/padding_effect.h>
13 #include <movit/resample_effect.h>
14 #include <movit/resize_effect.h>
15 #include <movit/white_balance_effect.h>
16 #include <movit/ycbcr.h>
17 #include <movit/ycbcr_input.h>
18 #include <stdio.h>
19 #include <stdlib.h>
20 #include <cstddef>
21 #include <new>
22 #include <utility>
23
24 #include "image_input.h"
25
26 namespace movit {
27 class ResourcePool;
28 }  // namespace movit
29
30 #define WIDTH 1280  // FIXME
31 #define HEIGHT 720  // FIXME
32
33 using namespace std;
34 using namespace movit;
35
36 namespace {
37
38 vector<LiveInputWrapper *> live_inputs;
39
40 template<class T, class... Args>
41 int wrap_lua_object(lua_State* L, const char *class_name, Args&&... args)
42 {
43         // Construct the C++ object and put it on the stack.
44         void *mem = lua_newuserdata(L, sizeof(T));
45         new(mem) T(std::forward<Args>(args)...);
46
47         // Look up the metatable named <class_name>, and set it on the new object.
48         luaL_getmetatable(L, class_name);
49         lua_setmetatable(L, -2);
50
51         return 1;
52 }
53
54 Theme *get_theme_updata(lua_State* L)
55 {       
56         luaL_checktype(L, lua_upvalueindex(1), LUA_TLIGHTUSERDATA);
57         return (Theme *)lua_touserdata(L, lua_upvalueindex(1));
58 }
59
60 Effect *get_effect(lua_State *L, int idx)
61 {
62         if (luaL_testudata(L, idx, "WhiteBalanceEffect") ||
63             luaL_testudata(L, idx, "ResampleEffect") ||
64             luaL_testudata(L, idx, "PaddingEffect") ||
65             luaL_testudata(L, idx, "IntegralPaddingEffect") ||
66             luaL_testudata(L, idx, "OverlayEffect") ||
67             luaL_testudata(L, idx, "ResizeEffect") ||
68             luaL_testudata(L, idx, "MixEffect") ||
69             luaL_testudata(L, idx, "ImageInput")) {
70                 return (Effect *)lua_touserdata(L, idx);
71         }
72         luaL_error(L, "Error: Index #%d was not an Effect type\n", idx);
73         return nullptr;
74 }
75
76 bool checkbool(lua_State* L, int idx)
77 {
78         luaL_checktype(L, idx, LUA_TBOOLEAN);
79         return lua_toboolean(L, idx);
80 }
81
82 std::string checkstdstring(lua_State *L, int index)
83 {
84         size_t len;
85         const char* cstr = lua_tolstring(L, index, &len);
86         return std::string(cstr, len);
87 }
88
89 int EffectChain_new(lua_State* L)
90 {
91         assert(lua_gettop(L) == 2);
92         int aspect_w = luaL_checknumber(L, 1);
93         int aspect_h = luaL_checknumber(L, 2);
94
95         return wrap_lua_object<EffectChain>(L, "EffectChain", aspect_w, aspect_h);
96 }
97
98 int EffectChain_add_live_input(lua_State* L)
99 {
100         assert(lua_gettop(L) == 2);
101         Theme *theme = get_theme_updata(L);
102         EffectChain *chain = (EffectChain *)luaL_checkudata(L, 1, "EffectChain");
103         bool override_bounce = checkbool(L, 2);
104         return wrap_lua_object<LiveInputWrapper>(L, "LiveInputWrapper", theme, chain, override_bounce);
105 }
106
107 int EffectChain_add_effect(lua_State* L)
108 {
109         assert(lua_gettop(L) >= 2);
110         EffectChain *chain = (EffectChain *)luaL_checkudata(L, 1, "EffectChain");
111
112         // TODO: Better error reporting.
113         Effect *effect = get_effect(L, 2);
114         if (lua_gettop(L) == 2) {
115                 if (effect->num_inputs() == 0) {
116                         chain->add_input((Input *)effect);
117                 } else {
118                         chain->add_effect(effect);
119                 }
120         } else {
121                 vector<Effect *> inputs;
122                 for (int idx = 3; idx <= lua_gettop(L); ++idx) {
123                         if (luaL_testudata(L, idx, "LiveInputWrapper")) {
124                                 LiveInputWrapper *input = (LiveInputWrapper *)lua_touserdata(L, idx);
125                                 inputs.push_back(input->get_input());
126                         } else {
127                                 inputs.push_back(get_effect(L, idx));
128                         }
129                 }
130                 chain->add_effect(effect, inputs);
131         }
132
133         lua_settop(L, 2);  // Return the effect itself.
134
135         // Make sure Lua doesn't garbage-collect it away.
136         lua_pushvalue(L, -1);
137         luaL_ref(L, LUA_REGISTRYINDEX);  // TODO: leak?
138
139         return 1;
140 }
141
142 int EffectChain_finalize(lua_State* L)
143 {
144         assert(lua_gettop(L) == 2);
145         EffectChain *chain = (EffectChain *)luaL_checkudata(L, 1, "EffectChain");
146         bool is_main_chain = checkbool(L, 2);
147
148         // Add outputs as needed.
149         // NOTE: If you change any details about the output format, you will need to
150         // also update what's given to the muxer (HTTPD::Mux constructor) and
151         // what's put in the H.264 stream (sps_rbsp()).
152         ImageFormat inout_format;
153         inout_format.color_space = COLORSPACE_REC_709;
154
155         // Output gamma is tricky. We should output Rec. 709 for TV, except that
156         // we expect to run with web players and others that don't really care and
157         // just output with no conversion. So that means we'll need to output sRGB,
158         // even though H.264 has no setting for that (we use “unspecified”).
159         inout_format.gamma_curve = GAMMA_sRGB;
160
161         if (is_main_chain) {
162                 YCbCrFormat output_ycbcr_format;
163                 // We actually output 4:2:0 in the end, but chroma subsampling
164                 // happens in a pass not run by Movit (see Mixer::subsample_chroma()).
165                 output_ycbcr_format.chroma_subsampling_x = 1;
166                 output_ycbcr_format.chroma_subsampling_y = 1;
167
168                 // Rec. 709 would be the sane thing to do, but it seems many players
169                 // (e.g. MPlayer and VLC) just default to BT.601 coefficients no matter
170                 // what (see discussions in e.g. https://trac.ffmpeg.org/ticket/4978).
171                 // We _do_ set the right flags, though, so that a player that works
172                 // properly doesn't have to guess.
173                 output_ycbcr_format.luma_coefficients = YCBCR_REC_601;
174                 output_ycbcr_format.full_range = false;
175                 output_ycbcr_format.num_levels = 256;
176
177                 chain->add_ycbcr_output(inout_format, OUTPUT_ALPHA_FORMAT_POSTMULTIPLIED, output_ycbcr_format, YCBCR_OUTPUT_SPLIT_Y_AND_CBCR);
178                 chain->set_dither_bits(8);
179                 chain->set_output_origin(OUTPUT_ORIGIN_TOP_LEFT);
180         }
181         chain->add_output(inout_format, OUTPUT_ALPHA_FORMAT_POSTMULTIPLIED);
182
183         chain->finalize();
184         return 0;
185 }
186
187 int LiveInputWrapper_connect_signal(lua_State* L)
188 {
189         assert(lua_gettop(L) == 2);
190         LiveInputWrapper *input = (LiveInputWrapper *)luaL_checkudata(L, 1, "LiveInputWrapper");
191         int signal_num = luaL_checknumber(L, 2);
192         input->connect_signal(signal_num);
193         return 0;
194 }
195
196 int ImageInput_new(lua_State* L)
197 {
198         assert(lua_gettop(L) == 1);
199         std::string filename = checkstdstring(L, 1);
200         return wrap_lua_object<ImageInput>(L, "ImageInput", filename);
201 }
202
203 int WhiteBalanceEffect_new(lua_State* L)
204 {
205         assert(lua_gettop(L) == 0);
206         return wrap_lua_object<WhiteBalanceEffect>(L, "WhiteBalanceEffect");
207 }
208
209 int ResampleEffect_new(lua_State* L)
210 {
211         assert(lua_gettop(L) == 0);
212         return wrap_lua_object<ResampleEffect>(L, "ResampleEffect");
213 }
214
215 int PaddingEffect_new(lua_State* L)
216 {
217         assert(lua_gettop(L) == 0);
218         return wrap_lua_object<PaddingEffect>(L, "PaddingEffect");
219 }
220
221 int IntegralPaddingEffect_new(lua_State* L)
222 {
223         assert(lua_gettop(L) == 0);
224         return wrap_lua_object<IntegralPaddingEffect>(L, "IntegralPaddingEffect");
225 }
226
227 int OverlayEffect_new(lua_State* L)
228 {
229         assert(lua_gettop(L) == 0);
230         return wrap_lua_object<OverlayEffect>(L, "OverlayEffect");
231 }
232
233 int ResizeEffect_new(lua_State* L)
234 {
235         assert(lua_gettop(L) == 0);
236         return wrap_lua_object<ResizeEffect>(L, "ResizeEffect");
237 }
238
239 int MixEffect_new(lua_State* L)
240 {
241         assert(lua_gettop(L) == 0);
242         return wrap_lua_object<MixEffect>(L, "MixEffect");
243 }
244
245 int Effect_set_float(lua_State *L)
246 {
247         assert(lua_gettop(L) == 3);
248         Effect *effect = (Effect *)get_effect(L, 1);
249         std::string key = checkstdstring(L, 2);
250         float value = luaL_checknumber(L, 3);
251         if (!effect->set_float(key, value)) {
252                 luaL_error(L, "Effect refused set_float(\"%s\", %d) (invalid key?)", key.c_str(), int(value));
253         }
254         return 0;
255 }
256
257 int Effect_set_int(lua_State *L)
258 {
259         assert(lua_gettop(L) == 3);
260         Effect *effect = (Effect *)get_effect(L, 1);
261         std::string key = checkstdstring(L, 2);
262         float value = luaL_checknumber(L, 3);
263         if (!effect->set_int(key, value)) {
264                 luaL_error(L, "Effect refused set_int(\"%s\", %d) (invalid key?)", key.c_str(), int(value));
265         }
266         return 0;
267 }
268
269 int Effect_set_vec3(lua_State *L)
270 {
271         assert(lua_gettop(L) == 5);
272         Effect *effect = (Effect *)get_effect(L, 1);
273         std::string key = checkstdstring(L, 2);
274         float v[3];
275         v[0] = luaL_checknumber(L, 3);
276         v[1] = luaL_checknumber(L, 4);
277         v[2] = luaL_checknumber(L, 5);
278         if (!effect->set_vec3(key, v)) {
279                 luaL_error(L, "Effect refused set_vec3(\"%s\", %f, %f, %f) (invalid key?)", key.c_str(),
280                         v[0], v[1], v[2]);
281         }
282         return 0;
283 }
284
285 int Effect_set_vec4(lua_State *L)
286 {
287         assert(lua_gettop(L) == 6);
288         Effect *effect = (Effect *)get_effect(L, 1);
289         std::string key = checkstdstring(L, 2);
290         float v[4];
291         v[0] = luaL_checknumber(L, 3);
292         v[1] = luaL_checknumber(L, 4);
293         v[2] = luaL_checknumber(L, 5);
294         v[3] = luaL_checknumber(L, 6);
295         if (!effect->set_vec4(key, v)) {
296                 luaL_error(L, "Effect refused set_vec4(\"%s\", %f, %f, %f, %f) (invalid key?)", key.c_str(),
297                         v[0], v[1], v[2], v[3]);
298         }
299         return 0;
300 }
301
302 const luaL_Reg EffectChain_funcs[] = {
303         { "new", EffectChain_new },
304         { "add_live_input", EffectChain_add_live_input },
305         { "add_effect", EffectChain_add_effect },
306         { "finalize", EffectChain_finalize },
307         { NULL, NULL }
308 };
309
310 const luaL_Reg LiveInputWrapper_funcs[] = {
311         { "connect_signal", LiveInputWrapper_connect_signal },
312         { NULL, NULL }
313 };
314
315 const luaL_Reg ImageInput_funcs[] = {
316         { "new", ImageInput_new },
317         { "set_float", Effect_set_float },
318         { "set_int", Effect_set_int },
319         { "set_vec3", Effect_set_vec3 },
320         { "set_vec4", Effect_set_vec4 },
321         { NULL, NULL }
322 };
323
324 const luaL_Reg WhiteBalanceEffect_funcs[] = {
325         { "new", WhiteBalanceEffect_new },
326         { "set_float", Effect_set_float },
327         { "set_int", Effect_set_int },
328         { "set_vec3", Effect_set_vec3 },
329         { "set_vec4", Effect_set_vec4 },
330         { NULL, NULL }
331 };
332
333 const luaL_Reg ResampleEffect_funcs[] = {
334         { "new", ResampleEffect_new },
335         { "set_float", Effect_set_float },
336         { "set_int", Effect_set_int },
337         { "set_vec3", Effect_set_vec3 },
338         { "set_vec4", Effect_set_vec4 },
339         { NULL, NULL }
340 };
341
342 const luaL_Reg PaddingEffect_funcs[] = {
343         { "new", PaddingEffect_new },
344         { "set_float", Effect_set_float },
345         { "set_int", Effect_set_int },
346         { "set_vec3", Effect_set_vec3 },
347         { "set_vec4", Effect_set_vec4 },
348         { NULL, NULL }
349 };
350
351 const luaL_Reg IntegralPaddingEffect_funcs[] = {
352         { "new", IntegralPaddingEffect_new },
353         { "set_float", Effect_set_float },
354         { "set_int", Effect_set_int },
355         { "set_vec3", Effect_set_vec3 },
356         { "set_vec4", Effect_set_vec4 },
357         { NULL, NULL }
358 };
359
360 const luaL_Reg OverlayEffect_funcs[] = {
361         { "new", OverlayEffect_new },
362         { "set_float", Effect_set_float },
363         { "set_int", Effect_set_int },
364         { "set_vec3", Effect_set_vec3 },
365         { "set_vec4", Effect_set_vec4 },
366         { NULL, NULL }
367 };
368
369 const luaL_Reg ResizeEffect_funcs[] = {
370         { "new", ResizeEffect_new },
371         { "set_float", Effect_set_float },
372         { "set_int", Effect_set_int },
373         { "set_vec3", Effect_set_vec3 },
374         { "set_vec4", Effect_set_vec4 },
375         { NULL, NULL }
376 };
377
378 const luaL_Reg MixEffect_funcs[] = {
379         { "new", MixEffect_new },
380         { "set_float", Effect_set_float },
381         { "set_int", Effect_set_int },
382         { "set_vec3", Effect_set_vec3 },
383         { "set_vec4", Effect_set_vec4 },
384         { NULL, NULL }
385 };
386
387 }  // namespace
388
389 LiveInputWrapper::LiveInputWrapper(Theme *theme, EffectChain *chain, bool override_bounce)
390         : theme(theme)
391 {
392         ImageFormat inout_format;
393         inout_format.color_space = COLORSPACE_sRGB;
394
395         // Gamma curve depends on the input signal, and we don't really get any
396         // indications. A camera would be expected to do Rec. 709, but
397         // I haven't checked if any do in practice. However, computers _do_ output
398         // in sRGB gamma (ie., they don't convert from sRGB to Rec. 709), and
399         // I wouldn't really be surprised if most non-professional cameras do, too.
400         // So we pick sRGB as the least evil here.
401         inout_format.gamma_curve = GAMMA_sRGB;
402
403         // The Blackmagic driver docs claim that the device outputs Y'CbCr
404         // according to Rec. 601, but practical testing indicates it definitely
405         // is Rec. 709 (at least up to errors attributable to rounding errors).
406         // Perhaps 601 was only to indicate the subsampling positions, not the
407         // colorspace itself? Tested with a Lenovo X1 gen 3 as input.
408         YCbCrFormat input_ycbcr_format;
409         input_ycbcr_format.chroma_subsampling_x = 2;
410         input_ycbcr_format.chroma_subsampling_y = 1;
411         input_ycbcr_format.cb_x_position = 0.0;
412         input_ycbcr_format.cr_x_position = 0.0;
413         input_ycbcr_format.cb_y_position = 0.5;
414         input_ycbcr_format.cr_y_position = 0.5;
415         input_ycbcr_format.luma_coefficients = YCBCR_REC_709;
416         input_ycbcr_format.full_range = false;
417
418         if (override_bounce) {
419                 input = new NonBouncingYCbCrInput(inout_format, input_ycbcr_format, WIDTH, HEIGHT, YCBCR_INPUT_SPLIT_Y_AND_CBCR);
420         } else {
421                 input = new YCbCrInput(inout_format, input_ycbcr_format, WIDTH, HEIGHT, YCBCR_INPUT_SPLIT_Y_AND_CBCR);
422         }
423         chain->add_input(input);
424 }
425
426 void LiveInputWrapper::connect_signal(int signal_num)
427 {
428         theme->connect_signal(input, signal_num);
429 }
430
431 Theme::Theme(const char *filename, ResourcePool *resource_pool, unsigned num_cards)
432         : resource_pool(resource_pool), num_cards(num_cards)
433 {
434         L = luaL_newstate();
435         luaL_openlibs(L);
436
437         register_class("EffectChain", EffectChain_funcs); 
438         register_class("LiveInputWrapper", LiveInputWrapper_funcs); 
439         register_class("ImageInput", ImageInput_funcs);
440         register_class("WhiteBalanceEffect", WhiteBalanceEffect_funcs);
441         register_class("ResampleEffect", ResampleEffect_funcs);
442         register_class("PaddingEffect", PaddingEffect_funcs);
443         register_class("IntegralPaddingEffect", IntegralPaddingEffect_funcs);
444         register_class("OverlayEffect", OverlayEffect_funcs);
445         register_class("ResizeEffect", ResizeEffect_funcs);
446         register_class("MixEffect", MixEffect_funcs);
447
448         // Run script.
449         lua_settop(L, 0);
450         if (luaL_dofile(L, filename)) {
451                 fprintf(stderr, "error: %s\n", lua_tostring(L, -1));
452                 lua_pop(L, 1);
453                 exit(1);
454         }
455         assert(lua_gettop(L) == 0);
456
457         // Ask it for the number of channels.
458         lua_getglobal(L, "num_channels");
459
460         if (lua_pcall(L, 0, 1, 0) != 0) {
461                 fprintf(stderr, "error running function `num_channels': %s\n", lua_tostring(L, -1));
462                 exit(1);
463         }
464
465         num_channels = luaL_checknumber(L, 1);
466         lua_pop(L, 1);
467         assert(lua_gettop(L) == 0);
468 }
469
470 void Theme::register_class(const char *class_name, const luaL_Reg *funcs)
471 {
472         assert(lua_gettop(L) == 0);
473         luaL_newmetatable(L, class_name);
474         lua_pushlightuserdata(L, this);
475         luaL_setfuncs(L, funcs, 1);
476         lua_pushvalue(L, -1);
477         lua_setfield(L, -2, "__index");
478         lua_setglobal(L, class_name);
479         assert(lua_gettop(L) == 0);
480 }
481
482 pair<EffectChain *, function<void()>>
483 Theme::get_chain(unsigned num, float t, unsigned width, unsigned height)
484 {
485         unique_lock<mutex> lock(m);
486         assert(lua_gettop(L) == 0);
487         lua_getglobal(L, "get_chain");  /* function to be called */
488         lua_pushnumber(L, num);
489         lua_pushnumber(L, t);
490         lua_pushnumber(L, width);
491         lua_pushnumber(L, height);
492
493         if (lua_pcall(L, 4, 2, 0) != 0) {
494                 fprintf(stderr, "error running function `get_chain': %s\n", lua_tostring(L, -1));
495                 exit(1);
496         }
497
498         EffectChain *chain = (EffectChain *)luaL_checkudata(L, -2, "EffectChain");
499         if (!lua_isfunction(L, -1)) {
500                 fprintf(stderr, "Argument #-1 should be a function\n");
501                 exit(1);
502         }
503         lua_pushvalue(L, -1);
504         int funcref = luaL_ref(L, LUA_REGISTRYINDEX);  // TODO: leak!
505         lua_pop(L, 2);
506         assert(lua_gettop(L) == 0);
507         return make_pair(chain, [this, funcref]{
508                 unique_lock<mutex> lock(m);
509
510                 // Set up state, including connecting signals.
511                 lua_rawgeti(L, LUA_REGISTRYINDEX, funcref);
512                 if (lua_pcall(L, 0, 0, 0) != 0) {
513                         fprintf(stderr, "error running chain setup callback: %s\n", lua_tostring(L, -1));
514                         exit(1);
515                 }
516                 assert(lua_gettop(L) == 0);
517         });
518 }
519
520 std::string Theme::get_channel_name(unsigned channel)
521 {
522         unique_lock<mutex> lock(m);
523         lua_getglobal(L, "channel_name");
524         lua_pushnumber(L, channel);
525         if (lua_pcall(L, 1, 1, 0) != 0) {
526                 fprintf(stderr, "error running function `channel_nam': %s\n", lua_tostring(L, -1));
527                 exit(1);
528         }
529
530         std::string ret = lua_tostring(L, -1);
531         lua_pop(L, 1);
532         assert(lua_gettop(L) == 0);
533         return ret;
534 }
535
536 bool Theme::get_supports_set_wb(unsigned channel)
537 {
538         unique_lock<mutex> lock(m);
539         lua_getglobal(L, "supports_set_wb");
540         lua_pushnumber(L, channel);
541         if (lua_pcall(L, 1, 1, 0) != 0) {
542                 fprintf(stderr, "error running function `supports_set_wb': %s\n", lua_tostring(L, -1));
543                 exit(1);
544         }
545
546         bool ret = checkbool(L, -1);
547         lua_pop(L, 1);
548         assert(lua_gettop(L) == 0);
549         return ret;
550 }
551
552 void Theme::set_wb(unsigned channel, double r, double g, double b)
553 {
554         unique_lock<mutex> lock(m);
555         lua_getglobal(L, "set_wb");
556         lua_pushnumber(L, channel);
557         lua_pushnumber(L, r);
558         lua_pushnumber(L, g);
559         lua_pushnumber(L, b);
560         if (lua_pcall(L, 4, 0, 0) != 0) {
561                 fprintf(stderr, "error running function `set_wb': %s\n", lua_tostring(L, -1));
562                 exit(1);
563         }
564
565         assert(lua_gettop(L) == 0);
566 }
567
568 std::vector<std::string> Theme::get_transition_names(float t)
569 {
570         unique_lock<mutex> lock(m);
571         lua_getglobal(L, "get_transitions");
572         lua_pushnumber(L, t);
573         if (lua_pcall(L, 1, 1, 0) != 0) {
574                 fprintf(stderr, "error running function `get_transitions': %s\n", lua_tostring(L, -1));
575                 exit(1);
576         }
577
578         std::vector<std::string> ret;
579         lua_pushnil(L);
580         while (lua_next(L, -2) != 0) {
581                 ret.push_back(lua_tostring(L, -1));
582                 lua_pop(L, 1);
583         }
584         lua_pop(L, 1);
585         assert(lua_gettop(L) == 0);
586         return ret;
587 }       
588
589 void Theme::connect_signal(YCbCrInput *input, int signal_num)
590 {
591         if (signal_num >= int(num_cards)) {
592                 if (signals_warned_about.insert(signal_num).second) {
593                         fprintf(stderr, "WARNING: Theme asked for input %d, but we only have %u card(s).\n", signal_num, num_cards);
594                         fprintf(stderr, "Mapping to card %d instead.\n", signal_num % num_cards);
595                 }
596                 signal_num %= num_cards;
597         }
598         input->set_texture_num(0, input_textures[signal_num].tex_y);
599         input->set_texture_num(1, input_textures[signal_num].tex_cbcr);
600 }
601
602 void Theme::transition_clicked(int transition_num, float t)
603 {
604         unique_lock<mutex> lock(m);
605         lua_getglobal(L, "transition_clicked");
606         lua_pushnumber(L, transition_num);
607         lua_pushnumber(L, t);
608
609         if (lua_pcall(L, 2, 0, 0) != 0) {
610                 fprintf(stderr, "error running function `transition_clicked': %s\n", lua_tostring(L, -1));
611                 exit(1);
612         }
613         assert(lua_gettop(L) == 0);
614 }
615
616 void Theme::channel_clicked(int preview_num)
617 {
618         unique_lock<mutex> lock(m);
619         lua_getglobal(L, "channel_clicked");
620         lua_pushnumber(L, preview_num);
621
622         if (lua_pcall(L, 1, 0, 0) != 0) {
623                 fprintf(stderr, "error running function `channel_clicked': %s\n", lua_tostring(L, -1));
624                 exit(1);
625         }
626         assert(lua_gettop(L) == 0);
627 }