static void inject_scheduled(arcan_evctx* ctx) { if (!playback_in.ptr) return; static arcan_event next_ev; static int32_t tickv = -1; /* -1 if no pending events */ step: if (tickv != -1){ if (tickv <= arcan_frametime()){ arcan_event_enqueue(ctx, &next_ev); tickv = -1; } else return; } ssize_t rv = unpack_rec_event(&playback_in.ptr[playback_ofs], playback_in.sz - playback_ofs, &next_ev, &tickv); if (-1 == rv){ arcan_release_map(playback_in); memset(&playback_in, '\0', sizeof(playback_in)); return; } playback_ofs += rv; goto step; }
int arcan_event_tryenqueue(arcan_evctx* ctx, const arcan_event* const src) { if (((*ctx->front + 1) % ctx->eventbuf_sz) == *ctx->back) return 0; return arcan_event_enqueue(ctx, src); }
void drop_joytbl(struct arcan_evctx* ctx) { for(int i=0; i < iodev.n_joy; i++){ if (!iodev.joys[i].tagged){ struct arcan_event remev = { .category = EVENT_IO, .io.kind = EVENT_IO_STATUS, .io.devid = iodev.joys[i].devnum, .io.devkind = EVENT_IDEVKIND_STATUS, .io.input.status.devkind = EVENT_IDEVKIND_GAMEDEV, .io.input.status.action = EVENT_IDEV_REMOVED }; snprintf((char*) &remev.io.label, sizeof(remev.io.label) / sizeof(remev.io.label[0]), "%s", iodev.joys[i].label); arcan_event_enqueue(ctx, &remev); free(iodev.joys[i].adata); free(iodev.joys[i].hattbls); } else iodev.joys[i].tagged = false; free(iodev.joys); iodev.joys = NULL; iodev.n_joy = 0; } }
static inline void process_axismotion(arcan_evctx* ctx, const SDL_JoyAxisEvent* const ev) { int devid = ev->which; if (!iodev.joys || iodev.joys[devid].axis < ev->axis) return; struct axis_opts* daxis = &iodev.joys[devid].adata[ev->axis]; int16_t dstv; if (process_axis(ctx, daxis, ev->value, &dstv)){ arcan_event newevent = { .category = EVENT_IO, .io.kind = EVENT_IO_AXIS_MOVE, .io.devid = iodev.joys[devid].devnum, .io.subid = ev->axis, .io.datatype = EVENT_IDATATYPE_ANALOG, .io.devkind = EVENT_IDEVKIND_GAMEDEV, .io.input.analog.gotrel = false, .io.input.analog.nvalues = 1, .io.input.analog.axisval[0] = dstv }; arcan_event_enqueue(ctx, &newevent); } }
static inline void process_mousemotion(arcan_evctx* ctx, int16_t xv, int16_t xrel, int16_t yv, int16_t yrel) { int16_t dstv, dstv_r; arcan_event nev = { .label = "MOUSE\0", .category = EVENT_IO, .kind = EVENT_IO_AXIS_MOVE, .data.io.datatype = EVENT_IDATATYPE_ANALOG, .data.io.devkind = EVENT_IDEVKIND_MOUSE, .data.io.input.analog.devid = ARCAN_MOUSEIDBASE, .data.io.input.analog.gotrel = true, .data.io.input.analog.nvalues = 2 }; snprintf(nev.label, sizeof(nev.label) - 1, "mouse"); if (process_axis(ctx, &iodev.mx, xv, &dstv) && process_axis(ctx, &iodev.mx_r, xrel, &dstv_r)){ nev.data.io.input.analog.subid = 0; nev.data.io.input.analog.axisval[0] = dstv; nev.data.io.input.analog.axisval[1] = dstv_r; arcan_event_enqueue(ctx, &nev); } if (process_axis(ctx, &iodev.my, yv, &dstv) && process_axis(ctx, &iodev.my_r, yrel, &dstv_r)){ nev.data.io.input.analog.subid = 1; nev.data.io.input.analog.axisval[0] = dstv; nev.data.io.input.analog.axisval[1] = dstv_r; arcan_event_enqueue(ctx, &nev); } } static void set_analogstate(struct axis_opts* dst, int lower_bound, int upper_bound, int deadzone, int kernel_size, enum ARCAN_ANALOGFILTER_KIND mode) { dst->lower = lower_bound; dst->upper = upper_bound; dst->deadzone = deadzone; dst->kernel_sz = kernel_size; dst->mode = mode; dst->kernel_ofs = 0; }
/* * enqueue to current context considering input-masking, unless label is set, * assign one based on what kind of event it is This function has a similar * prototype to the enqueue defined in the interop.h, but a different * implementation to support waking up the child, and that blocking behaviors * in the main thread is always forbidden. */ int arcan_event_enqueue(arcan_evctx* ctx, const struct arcan_event* const src) { /* early-out mask-filter, these are only ever used to silently * discard input / output (only operate on head and tail of ringbuffer) */ if (!src || (src->category & ctx->mask_cat_inp) || (ctx->state_fl & 1) > 0) return ARCAN_OK; /* One big caveat with this approach is the possibility of feedback loop with * magnification - forcing us to break ordering by directly feeding drain. * Given that we have special treatment for _EXPIRE and similar calls, * there shouldn't be any functions that has this behavior. Still, broken * ordering is better than running out of space. */ if (((*ctx->back + 1) % ctx->eventbuf_sz) == *ctx->front){ if (ctx->drain){ /* very rare / impossible, but safe-guard against future bad code */ if ((ctx->state_fl & 2) > 0){ arcan_event ev = *src; ctx->drain(&ev, 1); } /* tradeoff, can cascade to embarassing GC pause or video- stall but better * than data corruption and unpredictable states -- this can theoretically * have us return a broken 'custom' error code from some script */ else { ctx->state_fl |= 2; arcan_event_feed(ctx, ctx->drain, NULL); ctx->state_fl &= ~2; } } else return ARCAN_ERRC_OUT_OF_SPACE; } if (panic_keysym != -1 && panic_keymod != -1 && src->category == EVENT_IO && src->io.kind == EVENT_IO_BUTTON && src->io.devkind == EVENT_IDEVKIND_KEYBOARD && src->io.input.translated.modifiers == panic_keymod && src->io.input.translated.keysym == panic_keysym ){ arcan_event ev = { .category = EVENT_SYSTEM, .sys.kind = EVENT_SYSTEM_EXIT, .sys.errcode = EXIT_SUCCESS }; return arcan_event_enqueue(ctx, &ev); } if (ctx->local && src->category == EVENT_IO) pack_rec_event(src); ctx->eventbuf[(*ctx->back) % ctx->eventbuf_sz] = *src; *ctx->back = (*ctx->back + 1) % ctx->eventbuf_sz; return ARCAN_OK; }
void arcan_event_queuetransfer(arcan_evctx* dstqueue, arcan_evctx* srcqueue, enum ARCAN_EVENT_CATEGORY allowed, float saturation, arcan_vobj_id source) { if (!srcqueue || !dstqueue || (srcqueue && !srcqueue->front) || (srcqueue && !srcqueue->back)) return; bool wake = false; arcan_frameserver* tgt = arcan_video_feedstate(source) ? arcan_video_feedstate(source)->ptr : NULL; saturation = (saturation > 1.0 ? 1.0 : saturation < 0.5 ? 0.5 : saturation); while ( srcqueue->front && *srcqueue->front != *srcqueue->back && floor((float)dstqueue->eventbuf_sz * saturation) > queue_used(dstqueue)) { arcan_event inev; if (arcan_event_poll(srcqueue, &inev) == 0) break; /* * update / translate to make sure the corresponding frameserver<->lua mapping * can be found and tracked, there are also a few events that should be handled * here rather than propagated (bufferstream for instance). */ if ((inev.category & allowed) == 0 ) continue; if (inev.category == EVENT_EXTERNAL){ switch(inev.ext.kind){ /* to protect against scripts that would happily try to just allocate/respond * to what a the event says, clamp this here */ case EVENT_EXTERNAL_SEGREQ: if (inev.ext.segreq.width > PP_SHMPAGE_MAXW) inev.ext.segreq.width = PP_SHMPAGE_MAXW; if (inev.ext.segreq.height > PP_SHMPAGE_MAXH) inev.ext.segreq.height = PP_SHMPAGE_MAXH; break; case EVENT_EXTERNAL_BUFFERSTREAM: /* this assumes that we are in non-blocking state and that a single * CSMG on a socket is sufficient for a non-blocking recvmsg */ if (tgt->vstream.handle) close(tgt->vstream.handle); tgt->vstream.handle = arcan_fetchhandle(tgt->dpipe, false); tgt->vstream.stride = inev.ext.bstream.pitch; tgt->vstream.format = inev.ext.bstream.format; continue; break; /* for autoclocking, only one-fire events are forwarded if flag has been set */ case EVENT_EXTERNAL_CLOCKREQ: if (tgt->flags.autoclock && !inev.ext.clock.once){ tgt->clock.frame = inev.ext.clock.dynamic; tgt->clock.left = tgt->clock.start = inev.ext.clock.rate; continue; } break; case EVENT_EXTERNAL_REGISTER: if (tgt->segid == SEGID_UNKNOWN){ /* 0.6/CRYPTO - need actual signature authentication here */ tgt->guid[0] = inev.ext.registr.guid[0]; tgt->guid[1] = inev.ext.registr.guid[1]; } snprintf(tgt->title, COUNT_OF(tgt->title), "%s", inev.ext.registr.title); break; /* note: one could manually enable EVENT_INPUT and use separate processes * as input sources (with all the risks that comes with it security wise) * if that ever becomes a concern, here would be a good place to consider * filtering the panic_key* */ /* client may need more fine grained control for audio transfers when it * comes to synchronized A/V playback */ case EVENT_EXTERNAL_FLUSHAUD: if (tgt) arcan_frameserver_flush(tgt); continue; break; default: break; } inev.ext.source = source; } else if (inev.category == EVENT_NET){ inev.net.source = source; } wake = true; arcan_event_enqueue(dstqueue, &inev); } if (wake) arcan_sem_post(srcqueue->synch.handle); }
void platform_event_rescan_idev(arcan_evctx* ctx) { if (iodev.sticks_init) SDL_QuitSubSystem(SDL_INIT_JOYSTICK); SDL_Init(SDL_INIT_JOYSTICK); SDL_JoystickEventState(SDL_ENABLE); int n_joys = SDL_NumJoysticks(); iodev.sticks_init = true; if (n_joys == 0){ drop_joytbl(ctx); return; } /* * (Re) scan/open all joysticks, * look for matching / already present devices * and copy their settings. */ size_t jsz = sizeof(struct arcan_stick) * n_joys; struct arcan_stick* joys = malloc(jsz); memset(joys, '\0', jsz); for (int i = 0; i < n_joys; i++) { struct arcan_stick* dj = &joys[i]; struct arcan_stick* sj = NULL; unsigned long hashid = djb_hash(SDL_JoystickName(i)); /* find existing */ if (iodev.joys){ for (int j = 0; j < iodev.n_joy; j++){ if (iodev.joys[j].hashid == hashid){ sj = &iodev.joys[j]; break; } } /* if found, copy to new table */ if (sj){ memcpy(dj, sj, sizeof(struct arcan_stick)); if (dj->hats){ dj->hattbls = malloc(dj->hats * sizeof(unsigned)); memcpy(dj->hattbls, sj->hattbls, sizeof(unsigned) * sj->hats); } if (dj->axis){ dj->adata = malloc(dj->axis * sizeof(struct axis_opts)); memcpy(dj->adata, sj->adata, sizeof(struct axis_opts) * sj->axis); } dj->handle = SDL_JoystickOpen(i); continue; } } /* otherwise add as new entry */ strncpy(dj->label, SDL_JoystickName(i), 255); dj->hashid = djb_hash(SDL_JoystickName(i)); dj->handle = SDL_JoystickOpen(i); dj->devnum = gen_devid(dj->hashid); dj->axis = SDL_JoystickNumAxes(joys[i].handle); dj->buttons = SDL_JoystickNumButtons(joys[i].handle); dj->balls = SDL_JoystickNumBalls(joys[i].handle); dj->hats = SDL_JoystickNumHats(joys[i].handle); if (dj->hats > 0){ size_t dst_sz = joys[i].hats * sizeof(unsigned); dj->hattbls = malloc(dst_sz); memset(joys[i].hattbls, 0, dst_sz); } if (dj->axis > 0){ size_t ad_sz = sizeof(struct axis_opts) * dj->axis; dj->adata = malloc(ad_sz); memset(dj->adata, '\0', ad_sz); for (int i = 0; i < dj->axis; i++){ dj->adata[i].mode = ARCAN_ANALOGFILTER_AVG; /* these values are sortof set * based on the SixAxis (common enough, and noisy enough) */ dj->adata[i].lower = -32765; dj->adata[i].deadzone = 5000; dj->adata[i].upper = 32768; dj->adata[i].kernel_sz = 1; } } /* notify the rest of the system about the added device */ struct arcan_event addev = { .category = EVENT_IO, .io.kind = EVENT_IO_STATUS, .io.devkind = EVENT_IDEVKIND_STATUS, .io.devid = dj->devnum, .io.input.status.devkind = EVENT_IDEVKIND_GAMEDEV, .io.input.status.action = EVENT_IDEV_ADDED }; snprintf((char*) &addev.io.label, sizeof(addev.io.label) / sizeof(addev.io.label[0]), "%s", dj->label); arcan_event_enqueue(ctx, &addev); } iodev.n_joy = n_joys; iodev.joys = joys; } void platform_event_keyrepeat(arcan_evctx* ctx, int* rate, int* delay) { /* sdl repeat start disabled */ static int cur_rep, cur_del; bool upd = false; if (*rate < 0){ *rate = cur_rep; } else{ int tmp = *rate; *rate = cur_rep; cur_rep = tmp; upd = true; } if (*delay < 0){ *delay = cur_del; } else{ int tmp = *delay; *delay = cur_del; cur_del = tmp; upd = true; } if (upd) SDL_EnableKeyRepeat(cur_del, cur_rep); } void platform_event_deinit(arcan_evctx* ctx) { if (iodev.sticks_init){ SDL_QuitSubSystem(SDL_INIT_JOYSTICK); iodev.sticks_init = false; } } void platform_event_reset(arcan_evctx* ctx) { platform_event_deinit(ctx); }
static inline void process_hatmotion(arcan_evctx* ctx, unsigned devid, unsigned hatid, unsigned value) { if (!iodev.joys) return; static unsigned hattbl[4] = {SDL_HAT_UP, SDL_HAT_DOWN, SDL_HAT_LEFT, SDL_HAT_RIGHT}; assert(iodev.n_joy > devid); assert(iodev.joys[devid].hats > hatid); arcan_event newevent = { .category = EVENT_IO, .io.kind = EVENT_IO_BUTTON, .io.datatype = EVENT_IDATATYPE_DIGITAL, .io.devkind = EVENT_IDEVKIND_GAMEDEV, .io.devid = iodev.joys[devid].devnum, .io.subid = 128 + (hatid * 4) }; /* shouldn't really ever be the same, but not trusting SDL */ if (iodev.joys[devid].hattbls[ hatid ] != value){ unsigned oldtbl = iodev.joys[devid].hattbls[hatid]; for (int i = 0; i < 4; i++){ if ( (oldtbl & hattbl[i]) != (value & hattbl[i]) ){ newevent.io.subid = (hatid * 4) + i; newevent.io.input.digital.active = (value & hattbl[i]) > 0; arcan_event_enqueue(ctx, &newevent); } } iodev.joys[devid].hattbls[hatid] = value; } } const char* platform_event_devlabel(int devid) { if (devid == -1) return "mouse"; devid = find_devind(devid); if (devid < 0 || devid >= iodev.n_joy) return "no device"; return iodev.joys && strlen(iodev.joys[devid].label) == 0 ? "no identifier" : iodev.joys[devid].label; } void platform_event_process(arcan_evctx* ctx) { SDL_Event event; /* other fields will be set upon enqueue */ arcan_event newevent = {.category = EVENT_IO}; while (SDL_PollEvent(&event)) { switch (event.type) { case SDL_MOUSEBUTTONDOWN: newevent.io.kind = EVENT_IO_BUTTON; newevent.io.datatype = EVENT_IDATATYPE_DIGITAL; newevent.io.devkind = EVENT_IDEVKIND_MOUSE; switch(event.button.button){ case SDL_BUTTON_LEFT: newevent.io.subid = 1; break; case SDL_BUTTON_MIDDLE: newevent.io.subid = 2; break; case SDL_BUTTON_RIGHT: newevent.io.subid = 3; break; default: newevent.io.subid = event.button.button; break; } newevent.io.devid = event.button.which; newevent.io.input.digital.active = true; snprintf(newevent.io.label, sizeof(newevent.io.label) - 1, "mouse%i", event.motion.which); arcan_event_enqueue(ctx, &newevent); break; case SDL_MOUSEBUTTONUP: newevent.io.kind = EVENT_IO_BUTTON; newevent.io.datatype = EVENT_IDATATYPE_DIGITAL; newevent.io.devkind = EVENT_IDEVKIND_MOUSE; newevent.io.devid = event.button.which; newevent.io.subid = event.button.button; newevent.io.input.digital.active = false; snprintf(newevent.io.label, sizeof(newevent.io.label) - 1, "mouse%i", event.motion.which); arcan_event_enqueue(ctx, &newevent); break; case SDL_MOUSEMOTION: process_mousemotion(ctx, &event.motion); break; case SDL_JOYAXISMOTION: process_axismotion(ctx, &event.jaxis); break; case SDL_KEYDOWN: newevent.io.datatype = EVENT_IDATATYPE_TRANSLATED; newevent.io.devkind = EVENT_IDEVKIND_KEYBOARD; newevent.io.input.translated.active = true; newevent.io.input.translated.keysym = event.key.keysym.sym; newevent.io.input.translated.modifiers = event.key.keysym.mod; newevent.io.input.translated.scancode = event.key.keysym.scancode; newevent.io.subid = event.key.keysym.unicode; if (!((event.key.keysym.mod & (ARKMOD_LCTRL | ARKMOD_RCTRL)) > 0)) to_utf8(event.key.keysym.unicode, newevent.io.input.translated.utf8); arcan_event_enqueue(ctx, &newevent); break; case SDL_KEYUP: newevent.io.datatype = EVENT_IDATATYPE_TRANSLATED; newevent.io.devkind = EVENT_IDEVKIND_KEYBOARD; newevent.io.input.translated.active = false; newevent.io.input.translated.keysym = event.key.keysym.sym; newevent.io.input.translated.modifiers = event.key.keysym.mod; newevent.io.input.translated.scancode = event.key.keysym.scancode; newevent.io.subid = event.key.keysym.unicode; arcan_event_enqueue(ctx, &newevent); break; case SDL_JOYBUTTONDOWN: newevent.io.kind = EVENT_IO_BUTTON; newevent.io.datatype = EVENT_IDATATYPE_DIGITAL; newevent.io.devkind = EVENT_IDEVKIND_GAMEDEV; newevent.io.devid = iodev.joys[event.jbutton.which].devnum; newevent.io.subid = event.jbutton.button; newevent.io.input.digital.active = true; snprintf(newevent.io.label, sizeof(newevent.io.label)-1, "joystick%i", event.jbutton.which); arcan_event_enqueue(ctx, &newevent); break; case SDL_JOYBUTTONUP: newevent.io.kind = EVENT_IO_BUTTON; newevent.io.datatype = EVENT_IDATATYPE_DIGITAL; newevent.io.devkind = EVENT_IDEVKIND_GAMEDEV; newevent.io.devid = iodev.joys[event.jbutton.which].devnum; newevent.io.subid = event.jbutton.button; newevent.io.input.digital.active = false; snprintf(newevent.io.label, sizeof(newevent.io.label)-1, "joystick%i", event.jbutton.which); arcan_event_enqueue(ctx, &newevent); break; /* don't got any devices that actually use this to test with, * but should really just be translated into analog/digital events */ case SDL_JOYBALLMOTION: break; case SDL_JOYHATMOTION: process_hatmotion(ctx, event.jhat.which, event.jhat.hat, event.jhat.value); break; case SDL_ACTIVEEVENT: //newevent.io.kind = (MOUSEFOCUS, INPUTFOCUS, APPACTIVE(0 = icon, 1 = restored) //if (event->active.state & SDL_APPINPUTFOCUS){ // SDL_SetModState(KMOD_NONE); break; case SDL_QUIT: newevent.category = EVENT_SYSTEM; newevent.sys.kind = EVENT_SYSTEM_EXIT; arcan_event_enqueue(ctx, &newevent); break; case SDL_SYSWMEVENT: break; /* * currently ignoring these events (and a resizeable window frame isn't yet * supported, although the video- code is capable of handling a rebuild/reinit, * the lua- scripts themselves all depend quite a bit on VRESH/VRESW, one * option would be to just calculate a scale factor for the newvresh, newvresw * and apply that as a translation step when passing the lua<->core border. * * Recently, changes in the egl-dri and arcan_lwa platforms makes this possible * so maybe it is time to update a little here ;-) case SDL_VIDEORESIZE: break; case SDL_VIDEOEXPOSE: break; case SDL_ACTIVEEVENT: break; case SDL_ */ } } }