Private
Public Access
Initial HyperDRM 0.6.0: webOS ambient light app and native service
This commit is contained in:
commit
376798d6ce
34 files changed
+2170
No files matched your search
@@ -0,0 +1,29 @@
|
||||
#ifndef HISTOGRAM_AUTOSTART_H
|
||||
#define HISTOGRAM_AUTOSTART_H
|
||||
#define BOOT_HOOK "/var/lib/webosbrew/init.d/60-histogramhyperion"
|
||||
static const char boot_script[] = "#!/bin/sh\n# Histogram Hyperion own boot hook v1\n(\n attempt=0\n while [ -x /var/lib/webosbrew/init.d/60-histogramhyperion ] && [ \"$attempt\" -lt 120 ]; do\n result=$(timeout 12 luna-send -n 1 luna://org.webosbrew.histogramhyperion.service/start '{}' 2>&1)\n case \"$result\" in\n *'\"running\":true'*) exit 0 ;;\n esac\n attempt=$((attempt + 1))\n sleep 5\n done\n echo 'Histogram Hyperion boot start cancelled or timed out'\n) >/var/lib/webosbrew/histogramhyperion-boot.log 2>&1 </dev/null &\nexit 0\n";
|
||||
static bool boot_enabled(void){return access(BOOT_HOOK,X_OK)==0;}
|
||||
static bool set_boot(bool enabled){
|
||||
struct stat st;
|
||||
if(lstat(BOOT_HOOK,&st)==0){
|
||||
if(!S_ISREG(st.st_mode))return false;
|
||||
char existing[sizeof(boot_script)];int fd=open(BOOT_HOOK,O_RDONLY|O_NOFOLLOW|O_CLOEXEC);if(fd<0)return false;
|
||||
ssize_t size=read(fd,existing,sizeof(existing));close(fd);
|
||||
if(size!=(ssize_t)(sizeof(boot_script)-1) || memcmp(existing,boot_script,(size_t)size)!=0)return false;
|
||||
if(!enabled)return unlink(BOOT_HOOK)==0;
|
||||
return chmod(BOOT_HOOK,0755)==0;
|
||||
}
|
||||
if(errno!=ENOENT)return false;
|
||||
if(!enabled)return true;
|
||||
if(mkdir("/var/lib/webosbrew",0755)!=0 && errno!=EEXIST)return false;
|
||||
if(mkdir("/var/lib/webosbrew/init.d",0755)!=0 && errno!=EEXIST)return false;
|
||||
int fd=open(BOOT_HOOK ".tmp",O_WRONLY|O_CREAT|O_EXCL|O_CLOEXEC|O_NOFOLLOW,0700);if(fd<0)return false;
|
||||
size_t used=0;bool ok=true;
|
||||
while(used<sizeof(boot_script)-1){ssize_t n=write(fd,boot_script+used,sizeof(boot_script)-1-used);if(n<=0){ok=false;break;}used+=(size_t)n;}
|
||||
if(ok)ok=fchmod(fd,0755)==0 && fsync(fd)==0;
|
||||
if(close(fd)!=0)ok=false;
|
||||
if(ok)ok=rename(BOOT_HOOK ".tmp",BOOT_HOOK)==0;
|
||||
if(!ok)unlink(BOOT_HOOK ".tmp");
|
||||
return ok;
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,19 @@
|
||||
#ifndef BLACKBARS_H
|
||||
#define BLACKBARS_H
|
||||
/* Calibrated preset-0 edge probes on this G4. Return -1 for an ambiguous
|
||||
* or dark frame so darkness alone cannot switch the active capture mode. */
|
||||
static inline int letterbox_candidate(const unsigned black[16],const unsigned char color[16][3]) {
|
||||
const unsigned interior[]={2,6,10,15};unsigned lit=0;
|
||||
for(unsigned i=0;i<4;i++){
|
||||
unsigned r=interior[i],peak=color[r][0];
|
||||
for(unsigned c=1;c<3;c++)if(color[r][c]>peak)peak=color[r][c];
|
||||
if(black[r]<20 && peak>35)lit++;
|
||||
}
|
||||
if(lit<2)return -1;
|
||||
unsigned top=(black[3]+black[11]+black[12])/3;
|
||||
unsigned bottom=(black[1]+black[5]+black[9])/3;
|
||||
if(top>=55 && bottom>=25)return 1;
|
||||
if(top<20 && bottom<20)return 0;
|
||||
return -1;
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,16 @@
|
||||
#ifndef COLOR_BOOST_H
|
||||
#define COLOR_BOOST_H
|
||||
/* HSV saturation x2 and value x1.33, preserving hue and clamping value. */
|
||||
static inline void boost_color(const unsigned char in[3],unsigned char out[3]){
|
||||
unsigned lo=in[0],hi=in[0];
|
||||
for(unsigned c=1;c<3;c++){if(in[c]<lo)lo=in[c];if(in[c]>hi)hi=in[c];}
|
||||
unsigned chroma=hi-lo;
|
||||
unsigned peak=(hi*133+50)/100;if(peak>255)peak=255;
|
||||
if(!chroma){for(unsigned c=0;c<3;c++)out[c]=(unsigned char)peak;return;}
|
||||
unsigned expanded=chroma*2;if(expanded>hi)expanded=hi;
|
||||
for(unsigned c=0;c<3;c++){
|
||||
unsigned value=hi-expanded+((in[c]-lo)*expanded+chroma/2)/chroma;
|
||||
out[c]=(unsigned char)((value*peak+hi/2)/hi);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,15 @@
|
||||
#ifndef LG_HISTOGRAM_H
|
||||
#define LG_HISTOGRAM_H
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
/* LG ABI: region ID followed by three planar channels, 256 uint16 bins
|
||||
* per channel. Bin index is the 8-bit intensity (0..255). */
|
||||
typedef struct { uint32_t id; uint16_t bins[3][256]; } Histogram;
|
||||
_Static_assert(sizeof(Histogram)==0x604,"LG ABI record size");
|
||||
_Static_assert(offsetof(Histogram,bins)==4,"LG ABI channel offset");
|
||||
static inline unsigned char histogram_mean(const uint16_t bins[256]) {
|
||||
uint64_t count=0,weight=0;
|
||||
for(unsigned b=0;b<256;b++){count+=bins[b];weight+=(uint64_t)bins[b]*b;}
|
||||
return count?(unsigned char)((weight+count/2)/count):0;
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,6 @@
|
||||
#ifndef SPATIAL_INTERPOLATION_H
|
||||
#define SPATIAL_INTERPOLATION_H
|
||||
static inline void blend_color(const unsigned char a[3],const unsigned char b[3],unsigned weight,unsigned char out[3]){
|
||||
for(unsigned c=0;c<3;c++)out[c]=(unsigned char)(((unsigned)a[c]*(65535-weight)+(unsigned)b[c]*weight+32767)/65535);
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,59 @@
|
||||
#ifndef HYPERION_LAYOUT_H
|
||||
#define HYPERION_LAYOUT_H
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
#include <stdlib.h>
|
||||
#include <dlfcn.h>
|
||||
#include <math.h>
|
||||
typedef struct {unsigned char a,b;unsigned weight;} LedBlend;
|
||||
/* Measured G4 rim samples, independent of LED count and wiring order. */
|
||||
static const struct {double position;unsigned char region;} rim[]={
|
||||
{0.2078431373,7},{0.3176470588,3},{0.5490196078,11},{0.7529411765,12},
|
||||
{0.8392156863,13},{1.4941176471,10},{2.1843137255,9},{2.5490196078,1},
|
||||
{2.8666666667,5},{3.4,15}
|
||||
};
|
||||
static double screen_perimeter(double x,double y){
|
||||
double distances[]={y,1-x,1-y,x},positions[]={x,1+y,3-x,4-y};unsigned edge=0;
|
||||
for(unsigned i=1;i<4;i++)if(distances[i]<distances[edge])edge=i;
|
||||
return positions[edge]>=4?positions[edge]-4:positions[edge];
|
||||
}
|
||||
static LedBlend layout_blend(double x,double y){
|
||||
double t=screen_perimeter(x,y);unsigned n=sizeof(rim)/sizeof(*rim),right=0;
|
||||
while(right<n && rim[right].position<=t)right++;
|
||||
right%=n;unsigned left=(right+n-1)%n;
|
||||
double span=rim[right].position-rim[left].position,distance=t-rim[left].position;
|
||||
if(span<=0)span+=4;
|
||||
if(distance<0)distance+=4;
|
||||
return (LedBlend){rim[left].region,rim[right].region,(unsigned)(distance/span*65535+0.5)};
|
||||
}
|
||||
/* Use the TV's JSON-C library; validate every rectangle before replacing the
|
||||
* layout. The safety limit is independent of the installed LED count. */
|
||||
static bool layout_from_json(const char *text,LedBlend **result,unsigned *count){
|
||||
void *lib=dlopen("libjson-c.so.5",RTLD_NOW);if(!lib)return false;
|
||||
void *(*parse)(const char*)=dlsym(lib,"json_tokener_parse");
|
||||
int (*get)(const void*,const char*,void**)=dlsym(lib,"json_object_object_get_ex");
|
||||
int (*type)(const void*)=dlsym(lib,"json_object_get_type");
|
||||
size_t (*length)(const void*)=dlsym(lib,"json_object_array_length");
|
||||
void *(*at)(const void*,size_t)=dlsym(lib,"json_object_array_get_idx");
|
||||
double (*number)(const void*)=dlsym(lib,"json_object_get_double");
|
||||
int (*put)(void*)=dlsym(lib,"json_object_put");
|
||||
if(!parse||!get||!type||!length||!at||!number||!put){dlclose(lib);return false;}
|
||||
void *root=parse(text),*info=NULL,*leds=NULL;bool valid=root && type(root)==4 && get(root,"info",&info) && type(info)==4 && get(info,"leds",&leds) && type(leds)==5;
|
||||
size_t n=valid?length(leds):0;valid=valid && n>0 && n<=16384;
|
||||
LedBlend *mapping=valid?calloc(n,sizeof(*mapping)):NULL;valid=valid && mapping;
|
||||
const char *keys[]={"hmin","hmax","vmin","vmax"};
|
||||
for(size_t i=0;valid && i<n;i++){
|
||||
void *led=at(leds,i);double coordinates[4];valid=led && type(led)==4;
|
||||
for(unsigned c=0;valid && c<4;c++){
|
||||
void *v=NULL;valid=get(led,keys[c],&v) && (type(v)==2||type(v)==3);
|
||||
if(valid){coordinates[c]=number(v);valid=isfinite(coordinates[c]) && coordinates[c]>=0 && coordinates[c]<=1;}
|
||||
}
|
||||
if(valid)valid=coordinates[0]<=coordinates[1] && coordinates[2]<=coordinates[3];
|
||||
if(valid)mapping[i]=layout_blend((coordinates[0]+coordinates[1])/2,(coordinates[2]+coordinates[3])/2);
|
||||
}
|
||||
if(root)put(root);
|
||||
dlclose(lib);
|
||||
if(!valid){free(mapping);return false;}
|
||||
*result=mapping;*count=(unsigned)n;return true;
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,34 @@
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <dlfcn.h>
|
||||
#include <unistd.h>
|
||||
#include <stdarg.h>
|
||||
#include "histogram.h"
|
||||
/* Process-local diagnostic sink. It changes logging only, not capture policy. */
|
||||
void printLog(const char *context,int level,const char *message_id,
|
||||
const char *file,const char *function,int line,const char *format,...) {
|
||||
(void)context;(void)level;(void)message_id;(void)file;
|
||||
fprintf(stderr,"LG %s:%d: ",function?function:"?",line);
|
||||
if(format){va_list args;va_start(args,format);vfprintf(stderr,format,args);va_end(args);}fputc('\n',stderr);
|
||||
}
|
||||
static bool policy(void) { fprintf(stderr,"Capture revoked by LG policy\n"); return true; }
|
||||
int main(void) {
|
||||
void *lib=dlopen("libvtcapture.so.1",RTLD_NOW);
|
||||
if(!lib){fprintf(stderr,"dlopen: %s\n",dlerror());return 1;}
|
||||
void *(*init)(bool(*)(void))=dlsym(lib,"vtCapture_initHistogram");
|
||||
int (*preset)(void*,int)=dlsym(lib,"vtCapture_setPresetHistogram");
|
||||
int (*get)(void*,Histogram*)=dlsym(lib,"vtCapture_getHistogram");
|
||||
int (*finish)(void*)=dlsym(lib,"vtCapture_finalizeHistogram");
|
||||
if(!init||!preset||!get||!finish)return 2;
|
||||
void *handle=init(policy); fprintf(stderr,"handle=%p\n",handle);
|
||||
if(!handle)return 3;
|
||||
int rc=preset(handle,0);fprintf(stderr,"preset=%d\n",rc);
|
||||
Histogram hist[16];
|
||||
for(int frame=0;rc==0 && frame<3;frame++) {
|
||||
usleep(100000); rc=get(handle,hist); printf("frame=%d rc=%d\n",frame,rc);
|
||||
if(rc==0)for(int i=0;i<16;i++) {printf("region=%u",hist[i].id);for(int c=0;c<3;c++){uint64_t sum=0;for(int b=0;b<256;b++)sum+=hist[i].bins[c][b];printf(" ch%d_count=%llu mean=%u",c,(unsigned long long)sum,histogram_mean(hist[i].bins[c]));} puts("");}
|
||||
}
|
||||
fprintf(stderr,"finalize=%d\n",finish(handle)); return rc?4:0;
|
||||
}
|
||||
@@ -0,0 +1,239 @@
|
||||
#define _DEFAULT_SOURCE
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <signal.h>
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <poll.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/wait.h>
|
||||
#include <unistd.h>
|
||||
#include <dlfcn.h>
|
||||
#include <sys/socket.h>
|
||||
#include <netinet/in.h>
|
||||
#include <sys/time.h>
|
||||
#include <stdarg.h>
|
||||
#include <time.h>
|
||||
#include "histogram.h"
|
||||
#include "blackbars.h"
|
||||
#include "color_boost.h"
|
||||
#include "interpolation.h"
|
||||
#include "layout.h"
|
||||
#include "autostart.h"
|
||||
#define SERVICE "org.webosbrew.histogramhyperion.service"
|
||||
#define PRIORITY 149
|
||||
typedef struct {int code;char *message;const char *file;int line;const char *func;void *padding;unsigned long magic;} LSError;
|
||||
typedef bool (*Callback)(void*,void*,void*);
|
||||
typedef struct {const char *name;Callback function;int flags;} Method;
|
||||
static bool (*reply)(void*,void*,const char*,LSError*);
|
||||
static const char *(*payload)(void*);
|
||||
static bool (*error_init)(LSError*);
|
||||
static void (*error_free)(LSError*);
|
||||
static void *(*vt_init)(bool(*)(void));
|
||||
static int (*vt_preset)(void*,int),(*vt_get)(void*,Histogram*),(*vt_finish)(void*);
|
||||
static void *capture;
|
||||
static char lg_diagnostic[256];
|
||||
/* Process-local LG diagnostics; no changes to the loaded LG library. */
|
||||
void printLog(const char *context,int level,const char *message_id,
|
||||
const char *file,const char *function,int line,const char *format,...) {
|
||||
(void)context;(void)message_id;(void)file;
|
||||
if(capture && level>3)return;
|
||||
if(format){va_list args;va_start(args,format);vsnprintf(lg_diagnostic,sizeof(lg_diagnostic),format,args);va_end(args);}
|
||||
fprintf(stderr,"LG %s:%d: ",function?function:"?",line);
|
||||
if(format){va_list args;va_start(args,format);vfprintf(stderr,format,args);va_end(args);}
|
||||
fputc('\n',stderr);
|
||||
}
|
||||
static int output=-1;
|
||||
static unsigned long frames;
|
||||
static unsigned char rgb[16][3];
|
||||
static unsigned char sent_rgb[3];
|
||||
static unsigned black_percent[16];
|
||||
static unsigned capture_preset;
|
||||
static unsigned letterbox_mode,settling;
|
||||
static int pending_letterbox=-1;
|
||||
static uint64_t candidate_since,next_border_probe;
|
||||
static bool color_valid;
|
||||
static unsigned led_count;
|
||||
static LedBlend *led_blends;
|
||||
static bool interpolation_enabled;
|
||||
static unsigned recovery_attempts;
|
||||
static uint64_t retry_at;
|
||||
static uint64_t milliseconds(void){struct timespec t;clock_gettime(CLOCK_MONOTONIC,&t);return (uint64_t)t.tv_sec*1000+t.tv_nsec/1000000;}
|
||||
static char last_error[256];
|
||||
static volatile sig_atomic_t revoked,quitting;
|
||||
static int guard_pipe=-1;
|
||||
static pid_t guard_pid=-1;
|
||||
#define DEV_MODE "/var/luna/preferences/vt_devmode"
|
||||
/* LG's developer gate is enabled only during initialization. A separate
|
||||
* guardian removes our own marker even if this process aborts or is killed.
|
||||
* An existing marker is preserved, as are files replaced by another owner. */
|
||||
static bool enter_developer_mode(void){
|
||||
int marker=open(DEV_MODE,O_CREAT|O_EXCL|O_WRONLY|O_CLOEXEC,0600);
|
||||
if(marker<0)return errno==EEXIST;
|
||||
struct stat owned;
|
||||
if(fstat(marker,&owned)!=0){close(marker);unlink(DEV_MODE);return false;}
|
||||
close(marker);int p[2];
|
||||
if(pipe(p)!=0){unlink(DEV_MODE);return false;}
|
||||
pid_t child=fork();
|
||||
if(child<0){close(p[0]);close(p[1]);unlink(DEV_MODE);return false;}
|
||||
if(child==0){
|
||||
close(p[1]);struct pollfd fd={p[0],POLLIN|POLLHUP,0};
|
||||
/* Bounded cleanup also covers an indefinitely blocked initialization. */
|
||||
poll(&fd,1,10000);close(p[0]);struct stat current;
|
||||
int rc=0;
|
||||
if(lstat(DEV_MODE,¤t)==0 && current.st_dev==owned.st_dev && current.st_ino==owned.st_ino)rc=unlink(DEV_MODE);
|
||||
_exit(rc==0?0:1);
|
||||
}
|
||||
close(p[0]);guard_pipe=p[1];guard_pid=child;return true;
|
||||
}
|
||||
static bool leave_developer_mode(void){
|
||||
if(guard_pipe<0)return true;
|
||||
close(guard_pipe);guard_pipe=-1;int status;
|
||||
while(waitpid(guard_pid,&status,0)<0){if(errno!=EINTR)return false;}
|
||||
guard_pid=-1;return WIFEXITED(status)&&WEXITSTATUS(status)==0;
|
||||
}
|
||||
static void quit_signal(int s){(void)s;quitting=1;}
|
||||
static bool policy(void){revoked=1;return true;}
|
||||
static void set_error(const char *s){snprintf(last_error,sizeof(last_error),"%s",s);fprintf(stderr,"%s\n",s);}
|
||||
static int send_line(const char *line){
|
||||
size_t left=strlen(line);const char *p=line;
|
||||
while(left){ssize_t n=send(output,p,left,MSG_NOSIGNAL);if(n<=0)return -1;p+=n;left-=n;}
|
||||
return 0;
|
||||
}
|
||||
static bool read_layout(void){
|
||||
const size_t capacity=2*1024*1024;char *data=malloc(capacity+1);if(!data)return false;
|
||||
bool complete=false;size_t used=0;
|
||||
if(send_line("{\"command\":\"serverinfo\"}\n")==0){
|
||||
while(used<capacity){ssize_t n=recv(output,data+used,capacity-used,0);if(n<=0)break;used+=(size_t)n;data[used]=0;char *end=strchr(data,'\n');if(end){*end=0;complete=true;break;}}
|
||||
}
|
||||
bool ok=complete && layout_from_json(data,&led_blends,&led_count);free(data);interpolation_enabled=ok;return ok;
|
||||
}
|
||||
static void stop_capture(void){
|
||||
free(led_blends);led_blends=NULL;led_count=0;interpolation_enabled=false;
|
||||
color_valid=false;memset(rgb,0,sizeof(rgb));memset(sent_rgb,0,sizeof(sent_rgb));
|
||||
if(output>=0){if(frames){char line[80];snprintf(line,sizeof(line),"{\"command\":\"clear\",\"priority\":%d}\n",PRIORITY);send_line(line);}close(output);output=-1;}
|
||||
if(capture){vt_finish(capture);capture=NULL;}
|
||||
}
|
||||
static bool respond(void *h,void *m,const char *s){if(!h||!m)return true;LSError e;error_init(&e);bool ok=reply(h,m,s,&e);error_free(&e);return ok;}
|
||||
static bool start(void *h,void *m,void *ctx){
|
||||
(void)ctx;const char *p=m?payload(m):NULL;
|
||||
if(p && strcmp(p,"{}")!=0)return respond(h,m,"{\"returnValue\":false,\"errorText\":\"Start expects an empty object; endpoint is localhost:19444\"}");
|
||||
if(capture)return respond(h,m,"{\"returnValue\":true,\"running\":true}");
|
||||
if(m){retry_at=0;recovery_attempts=0;}
|
||||
frames=0;revoked=0;last_error[0]=0;lg_diagnostic[0]=0;
|
||||
if(!enter_developer_mode()){set_error("Developer initialization needs root. Elevate this own service through Homebrew Channel.");return respond(h,m,"{\"returnValue\":false,\"errorText\":\"Root access is required for temporary LG developer initialization\"}");}
|
||||
capture=vt_init(policy);
|
||||
if(!leave_developer_mode()){stop_capture();set_error("Could not restore LG developer marker; check vt_devmode before retrying");return respond(h,m,"{\"returnValue\":false,\"errorText\":\"Developer marker cleanup failed\"}");}
|
||||
if(!capture){set_error("LG could not acquire the histogram capture resource. Check for another active capture source; no colors are being sent.");return respond(h,m,"{\"returnValue\":false,\"errorCode\":1,\"errorText\":\"LG refused histogram initialization. Another capture source may hold VT_CAPTURE. No colors are being sent.\"}");}
|
||||
if(vt_preset(capture,0)!=0){stop_capture();set_error("LG histogram preset initialization failed");return respond(h,m,"{\"returnValue\":false,\"errorText\":\"Histogram preset failed\"}");}
|
||||
capture_preset=letterbox_mode=settling=0;pending_letterbox=-1;next_border_probe=0;
|
||||
output=socket(AF_INET,SOCK_STREAM,0);
|
||||
struct timeval timeout={1,0};
|
||||
if(output>=0){setsockopt(output,SOL_SOCKET,SO_SNDTIMEO,&timeout,sizeof(timeout));setsockopt(output,SOL_SOCKET,SO_RCVTIMEO,&timeout,sizeof(timeout));}
|
||||
struct sockaddr_in a={.sin_family=AF_INET,.sin_port=htons(19444),.sin_addr={htonl(INADDR_LOOPBACK)}};
|
||||
if(output<0||connect(output,(void*)&a,sizeof(a))!=0){stop_capture();set_error("Hyperion JSON server is unavailable on localhost:19444");return respond(h,m,"{\"returnValue\":false,\"errorText\":\"Cannot connect to local Hyperion\"}");}
|
||||
if(!read_layout()){stop_capture();set_error("Cannot read a valid LED layout from Hyperion");return respond(h,m,"{\"returnValue\":false,\"errorText\":\"Cannot read Hyperion LED layout\"}");}
|
||||
return respond(h,m,"{\"returnValue\":true,\"running\":true}");
|
||||
}
|
||||
static bool stop(void *h,void *m,void *ctx){(void)ctx;retry_at=0;recovery_attempts=0;stop_capture();return respond(h,m,"{\"returnValue\":true,\"running\":false}");}
|
||||
static bool autostart(void *h,void *m,void *ctx){
|
||||
(void)ctx;const char *p=m?payload(m):NULL;bool enabled;
|
||||
if(p && strcmp(p,"{\"enabled\":true}")==0)enabled=true;
|
||||
else if(p && strcmp(p,"{\"enabled\":false}")==0)enabled=false;
|
||||
else return respond(h,m,"{\"returnValue\":false,\"errorText\":\"Expected enabled boolean\"}");
|
||||
if(geteuid()!=0 || !set_boot(enabled))return respond(h,m,"{\"returnValue\":false,\"errorText\":\"Autostart could not be saved. Check own service root elevation and boot hook.\"}");
|
||||
return respond(h,m,enabled?"{\"returnValue\":true,\"autostart\":true}":"{\"returnValue\":true,\"autostart\":false}");
|
||||
}
|
||||
static bool status(void *h,void *m,void *ctx){
|
||||
(void)ctx;char json[4096],safe[512];size_t n=0;
|
||||
for(size_t i=0;last_error[i] && n+2<sizeof(safe);i++){char c=last_error[i];if(c=='"'||c=='\\')safe[n++]='\\';if((unsigned char)c>=32)safe[n++]=c;}safe[n]=0;
|
||||
int used=snprintf(json,sizeof(json),"{\"returnValue\":true,\"version\":\"0.6.0\",\"running\":%s,\"colorValid\":%s,\"frames\":%lu,\"endpoint\":\"127.0.0.1:19444\",\"priority\":%d,\"uid\":%u,\"drmVerified\":false,\"mappingVerified\":false,\"outputColor\":[%u,%u,%u],\"error\":\"%s\",\"regions\":[",capture?"true":"false",color_valid?"true":"false",frames,PRIORITY,(unsigned)geteuid(),sent_rgb[0],sent_rgb[1],sent_rgb[2],safe);
|
||||
for(int i=0;i<16;i++)used+=snprintf(json+used,sizeof(json)-used,"%s[%u,%u,%u]",i?",":"",rgb[i][0],rgb[i][1],rgb[i][2]);
|
||||
used+=snprintf(json+used,sizeof(json)-used,"],\"ledCount\":%u,\"spatialOutput\":%s,\"blackPercent\":[",led_count,led_count?"true":"false");
|
||||
for(int i=0;i<16;i++)used+=snprintf(json+used,sizeof(json)-used,"%s%u",i?",":"",black_percent[i]);
|
||||
snprintf(json+used,sizeof(json)-used,"],\"preset\":%u,\"saturationGain\":2.0,\"brightnessGain\":1.33,\"spatialInterpolation\":%s,\"blackBarDetection\":true,\"autostart\":%s,\"letterboxDetected\":%s}",capture_preset,interpolation_enabled?"true":"false",boot_enabled()?"true":"false",capture&&letterbox_mode?"true":"false");return respond(h,m,json);
|
||||
}
|
||||
static int tick(void *unused){
|
||||
(void)unused;
|
||||
if(!capture){
|
||||
if(retry_at && milliseconds()>=retry_at){
|
||||
retry_at=0;recovery_attempts++;start(NULL,NULL,NULL);
|
||||
if(!capture && recovery_attempts<6)retry_at=milliseconds()+5000;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
if(revoked){set_error("LG revoked capture; retrying initialization after source transition");stop_capture();recovery_attempts=0;retry_at=milliseconds()+5000;return 1;}
|
||||
if(settling){settling--;return 1;}
|
||||
if(letterbox_mode && capture_preset==1 && milliseconds()>=next_border_probe){
|
||||
if(vt_preset(capture,0)!=0){set_error("Border probe preset failed");stop_capture();return 1;}
|
||||
capture_preset=0;settling=2;return 1;
|
||||
}
|
||||
Histogram hist[16]={0};int rc=vt_get(capture,hist);
|
||||
if(rc){set_error("LG histogram read failed; streaming stopped");stop_capture();return 1;}
|
||||
unsigned aggregate[3]={0};
|
||||
for(int i=0;i<16;i++){
|
||||
black_percent[i]=100;
|
||||
for(int c=0;c<3;c++){
|
||||
uint64_t total=0,black=0;
|
||||
for(int b=0;b<256;b++){total+=hist[i].bins[c][b];if(b<=12)black+=hist[i].bins[c][b];}
|
||||
unsigned fraction=total?(unsigned)(100*black/total):0;
|
||||
if(fraction<black_percent[i])black_percent[i]=fraction;
|
||||
}
|
||||
}
|
||||
for(int i=0;i<16;i++)for(int c=0;c<3;c++){
|
||||
rgb[i][c]=histogram_mean(hist[i].bins[c]);aggregate[c]+=rgb[i][c];
|
||||
}
|
||||
if(capture_preset==0){
|
||||
int candidate=letterbox_candidate(black_percent,rgb);uint64_t now=milliseconds();
|
||||
if(candidate<0)pending_letterbox=-1;
|
||||
else if(candidate!=pending_letterbox){pending_letterbox=candidate;candidate_since=now;}
|
||||
else if((unsigned)candidate!=letterbox_mode && now-candidate_since>=2000)letterbox_mode=(unsigned)candidate;
|
||||
if(letterbox_mode){
|
||||
if(vt_preset(capture,1)!=0){set_error("Letterbox preset failed");stop_capture();return 1;}
|
||||
capture_preset=1;settling=2;next_border_probe=now+2000;return 1;
|
||||
}
|
||||
}
|
||||
for(int c=0;c<3;c++)sent_rgb[c]=(unsigned char)(aggregate[c]/16);
|
||||
unsigned char boosted_mean[3];boost_color(sent_rgb,boosted_mean);memcpy(sent_rgb,boosted_mean,3);
|
||||
size_t capacity=256+(size_t)led_count*12;char *line=malloc(capacity);
|
||||
if(!line){set_error("Color buffer allocation failed");stop_capture();return 1;}
|
||||
size_t written=(size_t)snprintf(line,capacity,"{\"command\":\"color\",\"priority\":%d,\"duration\":1500,\"origin\":\"HyperDRM\",\"color\":[",PRIORITY);
|
||||
for(unsigned i=0;i<led_count;i++){
|
||||
unsigned char interpolated[3],boosted[3];LedBlend blend=led_blends[i];
|
||||
blend_color(rgb[blend.a],rgb[blend.b],blend.weight,interpolated);boost_color(interpolated,boosted);
|
||||
written+=(size_t)snprintf(line+written,capacity-written,"%s%u,%u,%u",i?",":"",boosted[0],boosted[1],boosted[2]);
|
||||
}
|
||||
snprintf(line+written,capacity-written,"]}\n");
|
||||
int failed=send_line(line);free(line);
|
||||
if(failed){set_error("Hyperion connection lost");stop_capture();return 1;}
|
||||
char answer[4096];size_t used=0;bool complete=false;
|
||||
while(used+1<sizeof(answer)){ssize_t len=recv(output,answer+used,sizeof(answer)-used-1,0);if(len<=0)break;used+=(size_t)len;answer[used]=0;if(strchr(answer,'\n')){complete=true;break;}}
|
||||
if(!complete){set_error("Hyperion did not acknowledge color data");stop_capture();return 1;}
|
||||
if(!strstr(answer,"\"success\":true")&&!strstr(answer,"\"success\": true")){set_error("Hyperion rejected the color command");stop_capture();return 1;}
|
||||
color_valid=true;frames++;return 1;
|
||||
}
|
||||
static void *symbol(void *lib,const char *name){void *s=dlsym(lib,name);if(!s){fprintf(stderr,"Missing symbol %s\n",name);exit(2);}return s;}
|
||||
int main(void){
|
||||
signal(SIGTERM,quit_signal);signal(SIGINT,quit_signal);
|
||||
void *vt=dlopen("libvtcapture.so.1",RTLD_NOW),*ls=dlopen("libluna-service2.so.3",RTLD_NOW),*glib=dlopen("libglib-2.0.so.0",RTLD_NOW);
|
||||
if(!vt||!ls||!glib){fprintf(stderr,"Missing LG library: %s\n",dlerror());return 2;}
|
||||
vt_init=symbol(vt,"vtCapture_initHistogram");vt_preset=symbol(vt,"vtCapture_setPresetHistogram");vt_get=symbol(vt,"vtCapture_getHistogram");vt_finish=symbol(vt,"vtCapture_finalizeHistogram");
|
||||
reply=symbol(ls,"LSMessageReply");payload=symbol(ls,"LSMessageGetPayload");error_init=symbol(ls,"LSErrorInit");error_free=symbol(ls,"LSErrorFree");
|
||||
bool (*reg)(const char*,void**,LSError*)=symbol(ls,"LSRegister");
|
||||
bool (*category)(void*,const char*,Method*,void*,void*,LSError*)=symbol(ls,"LSRegisterCategory");
|
||||
bool (*attach)(void*,void*,LSError*)=symbol(ls,"LSGmainAttach");
|
||||
bool (*unreg)(void*,LSError*)=symbol(ls,"LSUnregister");
|
||||
void *(*loop_new)(void*,int)=symbol(glib,"g_main_loop_new");
|
||||
int (*iterate)(void*,int)=symbol(glib,"g_main_context_iteration");
|
||||
unsigned (*timer)(unsigned,int(*)(void*),void*)=symbol(glib,"g_timeout_add");
|
||||
void (*loop_unref)(void*)=symbol(glib,"g_main_loop_unref");
|
||||
void *handle=NULL,*loop=loop_new(NULL,0);LSError e;error_init(&e);
|
||||
Method methods[]={{"start",start,0},{"stop",stop,0},{"status",status,0},{"autostart",autostart,0},{NULL,NULL,0}};
|
||||
if(!reg(SERVICE,&handle,&e)||!category(handle,"/",methods,NULL,NULL,&e)||!attach(handle,loop,&e)){fprintf(stderr,"Luna registration failed: %s\n",e.message?e.message:"unknown");error_free(&e);return 3;}
|
||||
timer(100,tick,NULL);
|
||||
while(!quitting){iterate(NULL,0);usleep(10000);}
|
||||
stop_capture();unreg(handle,&e);error_free(&e);loop_unref(loop);return 0;
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
#include <assert.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include "blackbars.h"
|
||||
int main(void){
|
||||
unsigned black[16]={0};unsigned char color[16][3];memset(color,100,sizeof(color));
|
||||
assert(letterbox_candidate(black,color)==0);
|
||||
black[3]=black[11]=black[12]=66;black[1]=55;black[5]=black[9]=33;
|
||||
assert(letterbox_candidate(black,color)==1);
|
||||
black[1]=black[5]=black[9]=0;assert(letterbox_candidate(black,color)==-1);
|
||||
for(unsigned i=0;i<16;i++)black[i]=100;
|
||||
memset(color,0,sizeof(color));assert(letterbox_candidate(black,color)==-1);
|
||||
memset(black,0,sizeof(black));assert(letterbox_candidate(black,color)==-1);
|
||||
puts("PASS: full frame, measured letterbox, one edge only, black frame, dark frame");
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
#include <assert.h>
|
||||
#include <stdio.h>
|
||||
#include "color_boost.h"
|
||||
int main(void){
|
||||
unsigned char out[3];
|
||||
for(unsigned v=0;v<256;v++){
|
||||
unsigned char gray[3]={v,v,v};boost_color(gray,out);
|
||||
unsigned expected=(v*133+50)/100;if(expected>255)expected=255;
|
||||
assert(out[0]==expected && out[1]==expected && out[2]==expected);
|
||||
}
|
||||
unsigned char cyan[3]={93,224,242};boost_color(cyan,out);
|
||||
assert(out[0]<93 && out[1]>out[0] && out[1]<out[2] && out[2]==255);
|
||||
for(unsigned c=0;c<3;c++){
|
||||
unsigned char primary[3]={0};primary[c]=255;boost_color(primary,out);
|
||||
for(unsigned j=0;j<3;j++)assert(out[j]==primary[j]);
|
||||
}
|
||||
unsigned char warm[3]={171,82,0};boost_color(warm,out);
|
||||
assert(out[0]==227 && out[1]>82 && out[2]==0);
|
||||
puts("PASS: neutral grays, stronger cyan, brighter warm color, clamped primaries");
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
#include <assert.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include "histogram.h"
|
||||
int main(void) {
|
||||
Histogram h={0};
|
||||
assert(histogram_mean(h.bins[0])==0);
|
||||
for(unsigned intensity=0;intensity<256;intensity++) {
|
||||
memset(&h,0,sizeof(h));h.bins[0][intensity]=65535;
|
||||
assert(histogram_mean(h.bins[0])==intensity);
|
||||
}
|
||||
/* Adjacent halfwords must stay independent, including the final bin. */
|
||||
memset(&h,0,sizeof(h));h.bins[0][254]=65535;h.bins[0][255]=1;
|
||||
assert(histogram_mean(h.bins[0])==254);
|
||||
/* Weighted sum exceeds uint32 for full uint16 counts in every bin. */
|
||||
for(unsigned b=0;b<256;b++)h.bins[0][b]=65535;
|
||||
assert(histogram_mean(h.bins[0])==128);
|
||||
for(unsigned primary=0;primary<3;primary++) {
|
||||
memset(&h,0,sizeof(h));
|
||||
for(unsigned c=0;c<3;c++)h.bins[c][c==primary?255:0]=100;
|
||||
for(unsigned c=0;c<3;c++)assert(histogram_mean(h.bins[c])==(c==primary?255:0));
|
||||
}
|
||||
puts("PASS: ABI, all 256 intensities, adjacent bins, overflow, RGB primaries");
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
#include <assert.h>
|
||||
#include <stdio.h>
|
||||
#include "interpolation.h"
|
||||
int main(void){
|
||||
const unsigned char a[3]={255,0,0},b[3]={0,255,0};unsigned char out[3];
|
||||
blend_color(a,b,0,out);assert(out[0]==255 && out[1]==0 && out[2]==0);
|
||||
blend_color(a,b,65535,out);assert(out[0]==0 && out[1]==255 && out[2]==0);
|
||||
blend_color(a,b,32768,out);assert(out[0]==127 && out[1]==128 && out[2]==0);
|
||||
unsigned previous=0;
|
||||
for(unsigned w=0;w<=65535;w++){
|
||||
blend_color(a,b,w,out);assert(out[1]>=previous && out[2]==0);previous=out[1];
|
||||
unsigned char gray[3]={173,173,173};blend_color(gray,gray,w,out);
|
||||
assert(out[0]==173 && out[1]==173 && out[2]==173);
|
||||
}
|
||||
puts("PASS: endpoints, midpoint, all weights monotonic, constant gray preserved");
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
#include <assert.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include "layout.h"
|
||||
int main(void){
|
||||
unsigned sizes[]={50,500};
|
||||
for(unsigned k=0;k<2;k++){
|
||||
unsigned n=sizes[k];size_t capacity=150*n+100;char *text=malloc(capacity);size_t used=snprintf(text,capacity,"{\"info\":{\"leds\":[");
|
||||
for(unsigned i=0;i<n;i++)used+=snprintf(text+used,capacity-used,"%s{\"hmin\":%.8f,\"hmax\":%.8f,\"vmin\":0,\"vmax\":0.05}",i?",":"",(double)i/n,(double)(i+1)/n);
|
||||
snprintf(text+used,capacity-used,"]}}");LedBlend *mapping=NULL;unsigned count=0;
|
||||
assert(layout_from_json(text,&mapping,&count));assert(count==n);
|
||||
for(unsigned i=0;i<n;i++)assert(mapping[i].a<16 && mapping[i].b<16 && mapping[i].weight<=65535);
|
||||
assert(mapping[0].a!=mapping[n-1].a);free(mapping);free(text);printf("PASS: %u LEDs, positions and order preserved\n",n);
|
||||
}
|
||||
const char *bad[]={"{}","{\"info\":{\"leds\":[]}}","{\"info\":{\"leds\":[42]}}","{\"info\":{\"leds\":[{\"hmin\":2,\"hmax\":1,\"vmin\":0,\"vmax\":1}]}}"};
|
||||
for(unsigned i=0;i<sizeof(bad)/sizeof(*bad);i++){LedBlend *mapping=NULL;unsigned count=0;assert(!layout_from_json(bad[i],&mapping,&count));assert(!mapping && !count);}
|
||||
puts("PASS: malformed layouts rejected without replacing output");
|
||||
}
|
||||
Reference in new issue
Block a user