#ifndef HYPERDRM_HANDOVER_H #define HYPERDRM_HANDOVER_H #include "playback.h" #ifndef CALIBRATION_ACTIVE #define CALIBRATION_ACTIVE false #endif #ifndef PICCAP_BINARY #define PICCAP_BINARY "/media/developer/apps/usr/palm/services/org.webosbrew.piccap.service/hyperion-webos" #endif #define HANDOVER_PREF "/media/developer/apps/usr/palm/services/org.webosbrew.histogramhyperion.service/handover.json" static PlaybackJson playback_json; static bool handover_enabled,piccap_owned,piccap_busy,media_busy,piccap_connected; static PlaybackProtection protection; static uint64_t protection_at,next_media_probe,next_piccap_probe,handover_cooldown,piccap_action_at; typedef enum {SOURCE_HISTOGRAM,SOURCE_PICCAP_STARTING,SOURCE_PICCAP,SOURCE_PICCAP_STOPPING} CaptureSource; static CaptureSource capture_source; static void *service_handle; static bool (*call_once)(void*,const char*,const char*,Callback,void*,unsigned long*,LSError*); static bool (*cancel_call)(void*,unsigned long,LSError*); static unsigned long piccap_token,media_token; static uint64_t piccap_deadline,media_deadline; static unsigned clear_samples,piccap_fallbacks; static uint64_t clear_since,unknown_since,piccap_disconnected_since; #define CLEAR_STABLE_MS 2000 #define UNKNOWN_GRACE_MS 1500 #define PICCAP_LOSS_MS 5000 #define PICCAP_FAILURE_COOLDOWN_MS 30000 static TargetConfig piccap_target; static char piccap_error[128]; static bool piccap_request(int command); static inline bool piccap_installed(void){return access(PICCAP_BINARY,X_OK)==0;} static bool handover_save(bool enabled){ const char *data=enabled?"{\"piccapHandover\":true}\n":"{\"piccapHandover\":false}\n"; int fd=open(HANDOVER_PREF ".tmp",O_WRONLY|O_CREAT|O_EXCL|O_NOFOLLOW|O_CLOEXEC,0600);if(fd<0)return false; ssize_t n=write(fd,data,strlen(data));bool ok=n==(ssize_t)strlen(data) && fsync(fd)==0; if(close(fd)!=0)ok=false; if(ok)ok=rename(HANDOVER_PREF ".tmp",HANDOVER_PREF)==0; if(!ok)unlink(HANDOVER_PREF ".tmp"); return ok; } static void handover_load(void){ int fd=open(HANDOVER_PREF,O_RDONLY|O_NOFOLLOW|O_CLOEXEC);if(fd<0)return; char text[128];ssize_t n=read(fd,text,sizeof(text)-1);close(fd); if(n>0){text[n]=0;void *root=playback_json.parse(text);handover_enabled=playback_true(&playback_json,root,"piccapHandover");if(root)playback_json.put(root);} } static bool media_reply(void *h,void *m,void *ctx){ (void)h;(void)ctx;media_busy=false;media_token=0; protection=playback_classify(&playback_json,payload(m));protection_at=milliseconds(); if(protection==PLAYBACK_CLEAR){ if(!clear_samples)clear_since=protection_at; if(clear_samples<3)clear_samples++; unknown_since=0; }else {clear_samples=0;clear_since=0;if(protection==PLAYBACK_UNKNOWN && !unknown_since)unknown_since=protection_at;else if(protection==PLAYBACK_PROTECTED)unknown_since=0;} return true; } /* Commands: 0 status, 1 start, 2 stop, 3 getSettings, 4 setSettings. Confirm stopped status before VT reuse. */ static bool piccap_reply(void *h,void *m,void *ctx){ (void)h;int command=(int)(intptr_t)ctx;piccap_busy=false;piccap_token=0; void *root=playback_json.parse(payload(m));bool ok=playback_true(&playback_json,root,"returnValue"); if(command==0){ void *running=playback_field(&playback_json,root,"isRunning"); if(ok && running && playback_json.type(running)==1){ bool active=playback_json.boolean(running); piccap_connected=playback_true(&playback_json,root,"connected"); if(active){ if(capture_source==SOURCE_PICCAP_STARTING)capture_source=SOURCE_PICCAP; if(piccap_connected)piccap_disconnected_since=0; else if(!piccap_disconnected_since)piccap_disconnected_since=milliseconds(); } else if(capture_source!=SOURCE_PICCAP_STARTING || milliseconds()-piccap_action_at>=2000){ if(capture_source!=SOURCE_PICCAP_STOPPING)handover_cooldown=milliseconds()+10000; piccap_owned=false;capture_source=SOURCE_HISTOGRAM;piccap_connected=false;piccap_disconnected_since=0; if(handover_cooldown=3 && capture_source==SOURCE_PICCAP_STOPPING)handover_cooldown=milliseconds()+10000; if(root)playback_json.put(root); next_piccap_probe=milliseconds()+500; if(command==2)piccap_request(0); return true; } static bool piccap_request(int command){ if(piccap_busy)return false; const char *uri=command==3?"luna://org.webosbrew.piccap.service/getSettings":command==4?"luna://org.webosbrew.piccap.service/setSettings":command==1?"luna://org.webosbrew.piccap.service/start":command==2?"luna://org.webosbrew.piccap.service/stop":"luna://org.webosbrew.piccap.service/status"; LSError e;error_init(&e); char data[160];snprintf(data,sizeof(data),"{\"address\":\"%s\",\"port\":%u,\"unix-socket\":false}",target_piccap_host(&piccap_target),piccap_target.piccap_port); piccap_busy=call_once(service_handle,uri,command==4?data:"{}",piccap_reply,(void*)(intptr_t)command,&piccap_token,&e); piccap_deadline=milliseconds()+3000; error_free(&e);if(command)piccap_action_at=milliseconds();return piccap_busy; } /* Return true while PicCap owns capture or a stop confirmation is pending. */ static bool handover_tick(void){ uint64_t now=milliseconds(); if(media_busy && now>=media_deadline){LSError e;error_init(&e);cancel_call(service_handle,media_token,&e);error_free(&e);media_busy=false;media_token=0;protection=PLAYBACK_UNKNOWN;clear_samples=0;clear_since=0;if(!unknown_since)unknown_since=now;} if(piccap_busy && now>=piccap_deadline){snprintf(piccap_error,sizeof(piccap_error),"PicCap request timed out");LSError e;error_init(&e);cancel_call(service_handle,piccap_token,&e);error_free(&e);piccap_busy=false;piccap_token=0;capture_source=SOURCE_PICCAP_STOPPING;next_piccap_probe=0;} if(handover_enabled && piccap_installed() && !retries.paused && playback_json.lib && !media_busy && now>=next_media_probe){ LSError e;error_init(&e); media_busy=call_once(service_handle,"luna://com.webos.media/getActivePipelines","{}",media_reply,NULL,&media_token,&e); media_deadline=now+3000; error_free(&e);next_media_probe=now+1000; } if(protection==PLAYBACK_CLEAR && now-protection_at>=3000){protection=PLAYBACK_UNKNOWN;clear_samples=0;clear_since=0;if(!unknown_since)unknown_since=now;} bool session=!CALIBRATION_ACTIVE && handover_enabled && target_piccap_compatible(&target) && piccap_installed() && !retries.paused && !retries.blocked; bool clear=session && protection==PLAYBACK_CLEAR && now-protection_at<3000; bool stable=clear && clear_samples>=3 && now-clear_since>=CLEAR_STABLE_MS && now>=handover_cooldown; if(capture_source==SOURCE_HISTOGRAM && stable){ piccap_target=target;piccap_error[0]=0;piccap_disconnected_since=now;release_capture(false);retries.due=0;piccap_owned=true;capture_source=SOURCE_PICCAP_STARTING; if(!piccap_request(3)){capture_source=SOURCE_PICCAP_STOPPING;handover_cooldown=now+PICCAP_FAILURE_COOLDOWN_MS;} } bool grace=protection==PLAYBACK_UNKNOWN && unknown_since && now-unknown_since=PICCAP_LOSS_MS; if(lost){ snprintf(piccap_error,sizeof(piccap_error),"PicCap receiver disconnected; histogram fallback");piccap_fallbacks++;handover_cooldown=now+PICCAP_FAILURE_COOLDOWN_MS; } if(piccap_owned && (!session || (!clear && !grace) || lost || strcmp(piccap_target.host,target.host) || piccap_target.piccap_port!=target.piccap_port) && capture_source!=SOURCE_PICCAP_STOPPING){capture_source=SOURCE_PICCAP_STOPPING;next_piccap_probe=0;} if(piccap_owned && !piccap_busy && now>=next_piccap_probe){ if(capture_source==SOURCE_PICCAP_STOPPING)piccap_request(2);else piccap_request(0); next_piccap_probe=now+1000; } return piccap_owned; } #endif