aboutsummaryrefslogtreecommitdiff
path: root/host
diff options
context:
space:
mode:
Diffstat (limited to 'host')
-rw-r--r--host/.gitignore5
-rw-r--r--host/Makefile12
-rwxr-xr-xhost/aus.py20
-rw-r--r--host/bdf.c (renamed from host/main.c)91
-rw-r--r--host/bdf.h11
-rw-r--r--host/color.h9
-rw-r--r--host/default.lines259
-rw-r--r--host/font.c13
-rw-r--r--host/font.h3
-rw-r--r--host/main.h10
-rw-r--r--host/matelight.py40
-rw-r--r--host/matelight.service10
-rw-r--r--host/net.h18
-rwxr-xr-xhost/server.py239
-rw-r--r--host/terminal.py19
-rw-r--r--host/usb.c161
-rw-r--r--host/usb.h27
17 files changed, 734 insertions, 213 deletions
diff --git a/host/.gitignore b/host/.gitignore
new file mode 100644
index 0000000..1903c5d
--- /dev/null
+++ b/host/.gitignore
@@ -0,0 +1,5 @@
+__pycache__
+env
+*.so
+*.swo
+*.swp
diff --git a/host/Makefile b/host/Makefile
index a6b892f..85536f5 100644
--- a/host/Makefile
+++ b/host/Makefile
@@ -1,6 +1,12 @@
-all: main.c font.c font.h color.c color.h
- gcc -shared -fPIC -std=gnu11 -Wall -lm -o libbdf.so -g -O0 main.c font.c color.c
+all: libml libbdf
+
+
+libml: usb.c color.c color.h
+ gcc -shared -fPIC -std=gnu11 -Wall -lm -lusb-1.0 -o libml.so -g usb.c color.c
+
+libbdf: bdf.c font.c font.h color.c color.h
+ gcc -shared -fPIC -std=gnu11 -Wall -lm -o libbdf.so -g bdf.c font.c color.c
clean:
- rm libbdf.so
+ rm libbdf.so libml.so
diff --git a/host/aus.py b/host/aus.py
new file mode 100755
index 0000000..c0bab71
--- /dev/null
+++ b/host/aus.py
@@ -0,0 +1,20 @@
+#!/usr/bin/env python
+
+from socketserver import *
+import socket
+import struct
+import zlib
+from time import time, strftime, sleep
+from collections import namedtuple, deque
+import itertools
+import threading
+import random
+import os
+
+from ctypes import *
+
+from matelight import sendframe, DISPLAY_WIDTH, DISPLAY_HEIGHT, FRAME_SIZE
+
+if __name__ == '__main__':
+ sendframe(bytes([0, 0, 0]*FRAME_SIZE))
+
diff --git a/host/main.c b/host/bdf.c
index 9648a26..a95ed8b 100644
--- a/host/main.c
+++ b/host/bdf.c
@@ -1,9 +1,8 @@
#include "config.h"
-#include "main.h"
+#include "bdf.h"
#include "color.h"
#include "font.h"
-#include "net.h"
#include <stdint.h>
#include <stdlib.h>
#include <stdio.h>
@@ -18,20 +17,11 @@
#include <netinet/in.h>
#include <unistd.h>
-
-void free_framebuffer(framebuffer_t *fb){
- free(fb->data);
- free(fb);
-}
-
/* CAUTION: REQUIRES INPUT TO BE \0-TERMINATED
* ...also, it does a hardcoded setlocale of LC_CTYPE to en_US.utf8 for... reasons. */
-framebuffer_t *framebuffer_render_text(char *s, glyphtable_t *glyph_table){
- unsigned int len = strlen(s);
-
- color_t *gbuf = NULL;
- unsigned int gbufwidth = 0;
- unsigned int gbufheight = 0;
+int framebuffer_get_text_bounds(char *s, glyphtable_t *glyph_table, size_t *outw, size_t *outh){
+ size_t gbufwidth = 0;
+ size_t gbufheight = 0;
char *p = s;
/* Calculate screen width of string prior to allocating memory for the frame buffer */
@@ -65,7 +55,7 @@ framebuffer_t *framebuffer_render_text(char *s, glyphtable_t *glyph_table){
p += inc;
if(c > glyph_table->size){
- fprintf(stderr, "Error rendering string: Codepoint 0x%lx out of valid range (0-%ld).\n", (long int)c, glyph_table->size);
+ fprintf(stderr, "Error rendering string: Codepoint 0x%lx out of valid range (0-%zd).\n", (long int)c, glyph_table->size);
goto error;
}
@@ -81,19 +71,31 @@ framebuffer_t *framebuffer_render_text(char *s, glyphtable_t *glyph_table){
gbufwidth += g->width;
}
- /* For easier rendering on the terminal, round up to multiples of two */
- gbufheight += gbufheight&1;
+ *outw = gbufwidth;
+ *outh = gbufheight;
+ return 0;
+error:
+ return 1;
+}
+
+/* CAUTION: REQUIRES INPUT TO BE \0-TERMINATED
+ * ...also, it does a hardcoded setlocale of LC_CTYPE to en_US.utf8 for... reasons. */
+/* Render the string beginning from the specified x offset (in pixels) */
+int framebuffer_render_text(char *s, glyphtable_t *glyph_table, color_t *gbuf, size_t gbufwidth, size_t gbufheight, size_t offx){
+ unsigned int len = strlen(s);
+ char *p = s;
- size_t gbufsize = gbufwidth*gbufheight;
- gbuf = calloc(gbufsize, sizeof(color_t));
- if(!gbuf){
- fprintf(stderr, "Cannot malloc() %lu bytes.\n", gbufsize*sizeof(color_t));
+ if(!setlocale(LC_CTYPE, "en_US.utf8")){
+ fprintf(stderr, "Cannot set locale\n");
goto error;
}
- memset(gbuf, 0, gbufsize*sizeof(color_t));
+
+ memset(gbuf, 0, gbufwidth*gbufheight*sizeof(color_t));
unsigned int x = 0;
+ wchar_t c;
p = s;
+ mbstate_t ps = {0};
memset(&ps, 0, sizeof(mbstate_t));
struct {
color_t fg;
@@ -280,38 +282,35 @@ framebuffer_t *framebuffer_render_text(char *s, glyphtable_t *glyph_table){
color_t bg = inv ? style.bg : style.fg;
glyph_t *g = glyph_table->data[c];
- render_glyph(g, gbuf, gbufwidth, x, 0, fg, bg);
- if(style.strikethrough || style.underline){
- int sty = gbufheight/2;
- /* g->y usually is a negative index of the glyph's baseline measured from the glyph's bottom */
- int uly = gbufheight + g->y;
- for(int i=0; i<g->width; i++){
- if(style.strikethrough)
- gbuf[sty*gbufwidth + x + i] = fg;
- if(style.underline)
- gbuf[uly*gbufwidth + x + i] = fg;
+ /* Is the glyph within the buffer's bounds? */
+ //if(x+g->width > offx && x < offx+gbufwidth){
+ /* x-offx might be negative down to -g->width+1, but that's ok */
+ render_glyph(g, gbuf, gbufwidth, x-offx, 0, fg, bg);
+ if(style.strikethrough || style.underline){
+ int sty = gbufheight/2;
+ /* g->y usually is a negative index of the glyph's baseline measured from the glyph's bottom */
+ int uly = gbufheight + g->y;
+ for(int i=0; i<g->width; i++){
+ if(x+i >= offx && x+i-offx < gbufwidth){ /* Stay within the frame buffer's bounds */
+ if(style.strikethrough)
+ gbuf[sty*gbufwidth + x + i - offx] = fg;
+ if(style.underline)
+ gbuf[uly*gbufwidth + x + i - offx] = fg;
+ }
+ }
}
- }
+ //}
x += g->width;
}
- framebuffer_t *fb = malloc(sizeof(framebuffer_t));
- if(!fb){
- fprintf(stderr, "Cannot malloc() %lu bytes.\n", sizeof(framebuffer_t));
- goto error;
- }
- fb->w = gbufwidth;
- fb->h = gbufheight;
- fb->data = gbuf;
- return fb;
-error:
- free(gbuf);
return 0;
+error:
+ return 1;
}
void console_render_buffer(color_t *data, size_t w, size_t h){
/* Render framebuffer to terminal, two pixels per character using Unicode box drawing stuff */
color_t lastfg = {0, 0, 0}, lastbg = {0, 0, 0};
- printf("\e[38;5;0;48;5;0m");
+ printf("\e[38;5;0;48;5;0m\e[K");
for(size_t y=0; y < h; y+=2){
for(size_t x=0; x < w; x++){
/* Da magicks: ▀█▄ */
@@ -350,7 +349,7 @@ void console_render_buffer(color_t *data, size_t w, size_t h){
}
}
}
- printf("\n");
+ printf("\n\e[K");
}
printf("\033[0m");
}
diff --git a/host/bdf.h b/host/bdf.h
new file mode 100644
index 0000000..c81277e
--- /dev/null
+++ b/host/bdf.h
@@ -0,0 +1,11 @@
+#ifndef __MAIN_H__
+#define __MAIN_H__
+
+#include "color.h"
+#include "font.h"
+
+int framebuffer_get_text_bounds(char *s, glyphtable_t *glyph_table, size_t *outw, size_t *outh);
+int framebuffer_render_text(char *s, glyphtable_t *glyph_table, color_t *gbuf, size_t gbufwidth, size_t gbufheight, size_t offx);
+void console_render_buffer(color_t *data, size_t w, size_t h);
+
+#endif//__MAIN_H__
diff --git a/host/color.h b/host/color.h
index 1fbfe57..2a9d345 100644
--- a/host/color.h
+++ b/host/color.h
@@ -6,13 +6,14 @@
/* For easier memsetting we use an inverted alpha channel, i.e. 0 ≘ fully opaque; 255 ≘ fully transparent */
typedef struct {
- uint8_t r;
- uint8_t g;
- uint8_t b;
- uint8_t a;
+ uint8_t r, g, b, a;
} color_t;
typedef struct {
+ uint8_t r, g, b;
+} rgb_t;
+
+typedef struct {
color_t *data;
size_t w;
size_t h;
diff --git a/host/default.lines b/host/default.lines
new file mode 100644
index 0000000..ecf0862
--- /dev/null
+++ b/host/default.lines
@@ -0,0 +1,259 @@
+\x1B[38;5;214mabstraction is a type of decadence
+\x1B[38;5;214mit's important to stay clean on all levels
+\x1B[38;5;214mexpiring for love is beautiful but stupid
+\x1B[38;5;214mlack of charisma can be fatal
+\x1B[38;5;214mambition is just as dangerous as complacency
+\x1B[38;5;214mslipping into madness is good for the sake of comparison
+\x1B[38;5;214mplanning for the future is escapism
+\x1B[38;5;214mtrue freedom is frightful
+\x1B[92mMate Light\x1B[0m powered by \x1B[95mData Becker™ \x1B[96mLaufschriftstudio 2000® Platinum Edition
+\x1B[38;5;214mreligion causes as many problems as it solves
+\x1B[38;5;214mdisorganization is a kind of anesthesia
+\x1B[38;5;214mpursuing pleasure for the sake of pleasure will ruin you
+\x1B[38;5;214manger or hate can be a useful motivating force
+\x1B[38;5;214mjust believing something can make it happen
+\x1B[38;5;214mtrading a life for a life is fair enough
+\x1B[38;5;214mlisten when your body talks
+\x1B[38;5;214mfake or real indifference is a powerful personal weapon
+\x1B[38;5;214mcalm is more conductive to creativity than is anxiety
+\x1B[38;5;214mthere are too few immutable truths today
+\x1B[38;5;214mbeing happy is more important than anything else
+\x1B[38;5;214mit can be helpful to keep going no matter what
+\x1B[38;5;214mbelieving in rebirth is the same as admitting defeat
+\x1B[38;5;214mit's vital to live in harmony with nature
+\x1B[38;5;214mdisgust is the appropriate response to most situations
+\x1B[38;5;214mnothing upsets the balance of good and evil
+\x1B[38;5;214mrandom mating is good for debunking sex myths
+\x1B[38;5;214manimalism is perfectly healthy
+\x1B[92mMate Light\x1B[0m powered by \x1B[95mSiemens™ \x1B[96mLaufschrift® v.0.1.2b fuer Intel™ Pentium®
+\x1B[38;5;214msalvation can't be bought and sold
+\x1B[38;5;214mextreme self-consciousness leads to perversion
+\x1B[38;5;214mroutine small excesses are worse than then the occasional debauch
+\x1B[38;5;214mwe must make sacrifices to maintain our quality of life
+\x1B[38;5;214mmuch was decided before you were born
+\x1B[38;5;214ma positive attitude means all the difference in the world
+\x1B[38;5;214myou must have one grand passion
+\x1B[38;5;214mit is heroic to try to stop time
+\x1B[38;5;214moccasionally principles are more valuable than people
+\x1B[38;5;214ma sense of timing is the mark of genius
+\x1B[38;5;214mexpressing anger is necessary
+\x1B[38;5;214mroutine is a link with the past
+\x1B[38;5;214mtorture is barbaric
+\x1B[38;5;214melaboration is a form of pollution
+\x1B[38;5;214mpeople are responsible for what they do unless they are insane
+\x1B[38;5;214mromantic love was invented to manipulate women
+\x1B[38;5;214msex differences are here to stay
+\x1B[38;5;214mthe unattainable is invariable attractive
+\x1B[38;5;214ma man can't know what it is to be a mother
+\x1B[38;5;214mthere's nothing except what you sense
+\x1B[38;5;214msurvival of the fittest applies to men and animals
+\x1B[38;5;214man elite is inevitable
+\x1B[38;5;214mit is man's fate to outsmart himself
+\x1B[38;5;214mpeople won't behave if they have nothing to lose
+\x1B[38;5;214mpeople are nuts if they think they are important
+\x1B[38;5;214many surplus is immoral
+\x1B[38;5;214mremember you always have freedom of choice
+\x1B[38;5;214ma lot of professionals are crackpots
+\x1B[38;5;214msacrificing yourself for a bad cause is not a moral act
+\x1B[38;5;214mit's better to be naive than jaded
+\x1B[38;5;214mdependence can be a meal ticket
+\x1B[38;5;214mkeep something in reserve for emergencies
+\x1B[38;5;214mold friends are better left in the past
+\x1B[38;5;214mmoney creates taste
+\x1B[38;5;214mto disagree presupposes moral integrity
+\x1B[38;5;214mbeing sure of yourself means you're a fool
+\x1B[38;5;214myou must know where you stop and the world begins
+\x1B[38;5;214mideals are replaced by conventional goals at a certain age
+\x1B[38;5;214mmost people are not fit to rule themselves
+\x1B[38;5;214mguilt and self-laceration are indulgences
+\x1B[38;5;214mit's better to be lonely than to be with inferior people
+\x1B[38;5;214mdying and coming back gives you considerable perspective
+\x1B[38;5;214mabsolute submission can be a form of freedom
+\x1B[38;5;214mfear is the greatest incapacitator
+\x1B[38;5;214mholding back protects your vital energies
+\x1B[38;5;214mwhen something terrible happens people wake up
+\x1B[38;5;214mensure that your life stays in flux
+\x1B[38;5;214mbeing judgmental is a sign of life
+\x1B[38;5;214mfreedom is a luxury not a necessity
+\x1B[38;5;214mdrama often obscures the real issues
+\x1B[38;5;214mwith perseverance you can discover any truth
+\x1B[38;5;214mhumanism is obsolete
+\x1B[38;5;214myou must disagree with authority figures
+\x1B[38;5;214mmyth can make reality more intelligible
+\x1B[38;5;214mhabitual contempt doesn't reflect a finer sensibility
+\x1B[38;5;214msterilization is a weapon of the rulers
+\x1B[38;5;214myour oldest fears are the worst ones
+\x1B[38;5;214moffer very little information about yourself
+\x1B[38;5;214mthreatening someone sexually is a horrible act
+\x1B[38;5;214mbeing alone with yourself is increasingly unpopular
+\x1B[38;5;214myou are a victim of the rules you live by
+\x1B[38;5;214mto volunteer is reactionary
+\x1B[38;5;214myou have to hurt others to be extraordinary
+\x1B[38;5;214mabuse of power comes as no surprise
+\x1B[38;5;214mrecluses always get weak
+\x1B[38;5;214mawful punishment awaits really bad people
+\x1B[38;5;214mchildren are the most cruel of all
+\x1B[38;5;214mthe cruelest disappointment is when you let yourself down
+\x1B[38;5;214mgo all out in romance and let the chips fall where they may
+\x1B[38;5;214mthe only way to be pure is to stay by yourself
+\x1B[38;5;214mspending too much time on self-improvement is antisocial
+\x1B[38;5;214mknowing yourself lets you understand others
+\x1B[38;5;214mresolutions serve to ease our conscience
+\x1B[38;5;214mwar is a purification rite
+\x1B[38;5;214mgiving free rein to your emotions is an honest way to live
+\x1B[38;5;214mpeople who go crazy are too sensitive
+\x1B[38;5;214mit's better to study the living fact than to analyze history
+\x1B[38;5;214mmen are not monogamous by nature
+\x1B[38;5;214mclass action is a nice idea with no substance
+\x1B[38;5;214mdreaming while awake is a frightening contradiction
+\x1B[38;5;214millness is a state of mind
+\x1B[38;5;214myou are responsible for constituting the meaning of things
+\x1B[38;5;214mit's good to give extra money to charity
+\x1B[38;5;214mit's better to be a good person than a famous person
+\x1B[38;5;214moften you should act like you are sexless
+\x1B[38;5;214mpeople who don't work with their hands are parasites
+\x1B[38;5;214mmonomania is a prerequisite of success
+\x1B[38;5;214mchasing the new is dangerous to society
+\x1B[38;5;214mrevolution begins with changes in the individual
+\x1B[38;5;214mselflessness is the highest achievement
+\x1B[38;5;214msymbols are more meaningful than things themselves
+\x1B[38;5;214ma strong sense of duty imprisons you
+\x1B[38;5;214mautomation is deadly
+\x1B[38;5;214mopacity is an irresistible challenge
+\x1B[38;5;214mmurder has its sexual side
+\x1B[38;5;214mat times inactivity is preferable to mindless functioning
+\x1B[38;5;214mmostly you should mind your own business
+\x1B[38;5;214mthe mundane is to be cherished
+\x1B[38;5;214mselfishness is the most basic motivation
+\x1B[38;5;214msloppy thinking gets worse over time
+\x1B[38;5;214myou can't expect people to be something they're not
+\x1B[38;5;214mambivalence can ruin your life
+\x1B[38;5;214munquestioning love demonstrates largesse of spirit
+\x1B[38;5;214mlabor is a life-destroying activity
+\x1B[38;5;214mgovernment is a burden on the people
+\x1B[38;5;214mdecency is a relative thing
+\x1B[38;5;214myour actions ae pointless if no one notices
+\x1B[38;5;214mdying should be as easy as falling off a log
+\x1B[38;5;214martificial desires are despoiling the earth
+\x1B[38;5;214mcategorizing fear is calming
+\x1B[38;5;214mit's just an accident that your parents are your parents
+\x1B[38;5;214meven your family can betray you
+\x1B[38;5;214mbad intentions can yield good results
+\x1B[38;5;214mif you have many desires your life will be interesting
+\x1B[92mMate Light\x1B[93m@\x1B[92mPlay store or \x1B[94;101mtcp://ml.jaseg.net:1337\x1B[0;91m ♥
+\x1B[38;5;214mit's not good to operate on credit
+\x1B[38;5;214mdescription is more important than metaphor
+\x1B[38;5;214mhiding your emotions is despicable
+\x1B[38;5;214mthe desire to reproduce is a death wish
+\x1B[38;5;214mpush yourself to the limit as often as possible
+\x1B[38;5;214mextreme behavior has its basis in pathological psychology
+\x1B[38;5;214mthere's nothing redeeming in toil
+\x1B[38;5;214myou are guileless in your dreams
+\x1B[38;5;214manything is a legitimate area of investigation
+\x1B[38;5;214mdeviants are sacrificed to increase group solidarity
+\x1B[38;5;214myou can't fool others if you're fooling yourself
+\x1B[38;5;214myou can understand someone of your sex only
+\x1B[38;5;214mraise boys and girls the same way
+\x1B[38;5;214mit's not good to hold too many absolutes
+\x1B[38;5;214mkilling is unavoidable but nothing to be proud of
+\x1B[38;5;214myou should study as much as possible
+\x1B[38;5;214malienation produces eccentrics or revolutionaries
+\x1B[38;5;214mtimidity is laughable
+\x1B[38;5;214mfathers often use too much force
+\x1B[38;5;214mgrass roots agitation is the only hope
+\x1B[38;5;214mstrong emotional attachment stems from basic insecurity
+\x1B[38;5;214mrechanneling destructive impulses is a sign of maturity
+\x1B[92mMate Light\x1B[0m powered by \x1B[95mApple™ \x1B[96miScroll®'
+\x1B[38;5;214msometimes science advances faster than it should
+\x1B[38;5;214ma sincere effort is all you can ask
+\x1B[38;5;214myou owe the world not the other way around
+\x1B[38;5;214mmothers shouldn't make too many sacrifices
+\x1B[38;5;214mloving animals is a substitute activity
+\x1B[38;5;214mchange is valuable when the oppressed become tyrants
+\x1B[38;5;214msolitude is enriching
+\x1B[38;5;214msometimes things seem to happen of their own accord
+\x1B[38;5;214mviolence is permissible even desirable occasionally
+\x1B[38;5;214mall things are delicately interconnected
+\x1B[38;5;214mplaying it safe can cause a lot of damage in the long run
+\x1B[38;5;214mdecadence can be an end in itself
+\x1B[92mMate Light\x1B[0;91m ♥ \x1B[92mUnicode
+\x1B[38;5;214meveryone's work is equally important
+\x1B[38;5;214mtaking a strong stand publicizes the opposite position
+\x1B[38;5;214mpain can be a very positive thing
+\x1B[38;5;214mclass structure is as artificial as plastic
+\x1B[38;5;214mthe world operates according to discoverable laws
+\x1B[38;5;214mteasing people sexually can have ugly consequences
+\x1B[38;5;214mredistributing wealth is imperative
+\x1B[38;5;214maction causes more trouble than thought
+\x1B[38;5;214mignoring enemies is the best way to fight
+\x1B[38;5;214mnoise can be hostile
+\x1B[38;5;214mwishing things away is not effective
+\x1B[38;5;214mit is a gift to the world not to have babies
+\x1B[38;5;214mthe sum of your actions determines what you are
+\x1B[38;5;214ma name means a lot just by itself
+\x1B[38;5;214mthinking too much can only cause problems
+\x1B[38;5;214msin is a means of social control
+\x1B[38;5;214mchildren are the hope of the future
+\x1B[38;5;214min some instances it's better to die than to continue
+\x1B[38;5;214mself-awareness can be crippling
+\x1B[38;5;214mprivate property created crime
+\x1B[38;5;214mlow expectations are good protection
+\x1B[38;5;214meverything that's interesting is new
+\x1B[38;5;214mdon't place to much trust in experts
+\x1B[38;5;214mstupid people shouldn't breed
+\x1B[38;5;214ma relaxed man is not necessarily a better man
+\x1B[38;5;214mleisure time is a gigantic smoke screen
+\x1B[38;5;214mexceptional people deserve special concessions
+\x1B[38;5;214mstasis is a dream state
+\x1B[38;5;214mgood deeds eventually are rewarded
+\x1B[38;5;214menjoy yourself because you can't change anything anyway
+\x1B[38;5;214mthe new is nothing but a restatement of the old
+\x1B[38;5;214mit's crucial to have an active fantasy life
+\x1B[38;5;214mworrying can help you prepare
+\x1B[38;5;214minheritance must be abolished
+\x1B[38;5;214mpeople are boring unless they are extremists
+\x1B[38;5;214mconfusing yourself is a way to stay honest
+\x1B[38;5;214mstarvation is nature's way
+\x1B[38;5;214mhumor is a release
+\x1B[38;5;214mcrime against property is relatively unimportant
+\x1B[38;5;214mat times your unconsciousness is truer than your conscious mind
+\x1B[38;5;214mself-contempt can do more harm than good
+\x1B[38;5;214mtalking is used to hide one's inability to act
+\x1B[38;5;214mthe idea of revolution is an adolescent fantasy
+\x1B[38;5;214mif you live simply there is nothing to worry about
+\x1B[38;5;214ma single event can have infinitely many interpretations
+\x1B[38;5;214mgoing with the flow is soothing but risky
+\x1B[38;5;214mmanual labor can be refreshing and wholesome
+\x1B[38;5;214mpotential counts for nothing until it's realized
+\x1B[38;5;214myou are the past present and future
+\x1B[38;5;214mtechnology will make or break us
+\x1B[38;5;214mevery achievement requires a sacrifice
+\x1B[38;5;214mthe family is living on borrowed time
+\x1B[38;5;214mphysical culture is second best
+\x1B[38;5;214munique things must be the most valuable
+\x1B[38;5;214ma solid home base builds a sense of self
+\x1B[38;5;214mthe idiosyncratic has lost its authority
+\x1B[38;5;214mlooking back is the first sign of aging and decay
+\x1B[38;5;214mrepetition is the best way to learn
+\x1B[38;5;214mknowledge should be advanced at all costs
+\x1B[38;5;214mif you aren't political your personal life should be exemplary
+\x1B[38;5;214mboredom makes you do crazy things
+\x1B[38;5;214myou must be intimate with a token few
+\x1B[38;5;214myou can live on through your descendants
+\x1B[38;5;214mseparatism is the way to a new beginning
+\x1B[38;5;214mthe idea of transcendence is used to obscure oppression
+\x1B[38;5;214mpolitics is used for personal gain
+\x1B[38;5;214meating too much is criminal
+\x1B[38;5;214mimposing order is man's vocation for chaos is hell
+\x1B[38;5;214myou don't know what's what until you support yourself
+\x1B[38;5;214mif you can't leave your mark give up
+\x1B[38;5;214ma little knowledge can go a long way
+\x1B[38;5;214musing force to stop force is absurd
+\x1B[38;5;214mmorals are for little people
+\x1B[38;5;214mwords tend to be inadequate
+\x1B[38;5;214memotional responses ar as valuable as intellectual responses
+\x1B[92mMate Light\x1B[0m powered by \x1B[95mMicrosoft™ \x1B[96mMarquee Manager® Professional OEM
+\x1B[38;5;214mthe most profound things are inexpressible
+\x1B[38;5;214mfaithfulness is a social not a biological law
+\x1B[38;5;214mrelativity is no boon to mankind
+\x1B[38;5;214mmoderation kills the spirit
diff --git a/host/font.c b/host/font.c
index ee9adb3..59bc2ef 100644
--- a/host/font.c
+++ b/host/font.c
@@ -6,7 +6,8 @@
#include <string.h>
#include <errno.h>
-void render_glyph(glyph_t *g, color_t *buf, unsigned int bufwidth, unsigned int offx, unsigned int offy, color_t fg, color_t bg){
+/* Clips at buffer bounds */
+void render_glyph(glyph_t *g, color_t *buf, unsigned int bufwidth, int offx, unsigned int offy, color_t fg, color_t bg){
unsigned int bitmap_row_width = g->width/8;
uint8_t *bitmap = ((uint8_t *)g) + sizeof(glyph_t);
for(unsigned int y=0; y < g->height; y++){
@@ -16,9 +17,13 @@ void render_glyph(glyph_t *g, color_t *buf, unsigned int bufwidth, unsigned int
data |= bitmap[y*bitmap_row_width+i];
}
color_t *p = buf + (offy+y)*bufwidth + offx;
- for(unsigned int x=0; x < g->width; x++){
- color_t c = (data&(1<<(g->width-1))) ? fg : bg;
- *p++ = c;
+ /* Take care to only render what's within the framebuffer's bounds */
+ for(int x=0; (x < g->width) && (offx+x < (int)bufwidth); x++){
+ if(offx + x >= 0){
+ color_t c = (data&(1<<(g->width-1))) ? fg : bg;
+ *p = c;
+ }
+ p++;
data <<= 1;
}
}
diff --git a/host/font.h b/host/font.h
index e9fbb35..6a6f302 100644
--- a/host/font.h
+++ b/host/font.h
@@ -29,7 +29,6 @@ glyphtable_t *read_bdf(FILE *f);
void free_glyphtable(glyphtable_t *glyph_table);
-// Requires buf to point to a buffer at least of size glyph->width*glyph->height.
-void render_glyph(glyph_t *glyph, color_t *buf, unsigned int bufwidth, unsigned int offx, unsigned int offy, color_t fg, color_t bg);
+void render_glyph(glyph_t *g, color_t *buf, unsigned int bufwidth, int offx, unsigned int offy, color_t fg, color_t bg);
#endif//__FONT_H__
diff --git a/host/main.h b/host/main.h
deleted file mode 100644
index 5ace45e..0000000
--- a/host/main.h
+++ /dev/null
@@ -1,10 +0,0 @@
-#ifndef __MAIN_H__
-#define __MAIN_H__
-
-#include "color.h"
-#include "font.h"
-
-framebuffer_t *framebuffer_render_text(char *s, glyphtable_t *glyph_table);
-void console_render_buffer(color_t *data, size_t w, size_t h);
-
-#endif//__MAIN_H__
diff --git a/host/matelight.py b/host/matelight.py
index 7c4f6c4..1bafe66 100644
--- a/host/matelight.py
+++ b/host/matelight.py
@@ -1,7 +1,8 @@
-import usb
import colorsys
-import numpy as np
from itertools import product
+from ctypes import c_size_t, c_uint8, c_void_p, c_float, CDLL, Structure, POINTER
+import numpy as np
+import time
CRATE_WIDTH = 5
CRATE_HEIGHT = 4
@@ -10,25 +11,34 @@ CRATES_Y = 4
DISPLAY_WIDTH = CRATES_X*CRATE_WIDTH
DISPLAY_HEIGHT = CRATES_Y*CRATE_HEIGHT
-FRAME_SIZE = CRATE_WIDTH*CRATE_HEIGHT*3
+CRATE_SIZE = CRATE_WIDTH*CRATE_HEIGHT*3
+FRAME_SIZE = DISPLAY_WIDTH*DISPLAY_HEIGHT
+
+# Gamma factor
+GAMMA = 2.5
+
+# Brightness of the LEDs in percent. 1.0 means 100%.
+BRIGHTNESS = 1.0
+
+ml = CDLL('./libml.so')
+ml.matelight_open.restype = c_void_p
-dev = usb.core.find(idVendor=0x1cbe, idProduct=0x0003)
+if ml.matelight_usb_init():
+ raise OSError('Cannot initialize USB library')
+matelights = ml.matelight_open()
+if matelights is None:
+ raise ImportError('Cannot open any Mate Light devices')
+dbuf = np.zeros(DISPLAY_WIDTH*DISPLAY_HEIGHT*4, dtype=np.uint8)
def sendframe(framedata):
""" Send a frame to the display
The argument contains a h * w array of 3-tuples of (r, g, b)-data or 4-tuples of (r, g, b, a)-data where the a
channel is ignored.
"""
- # Gamma correction
- framedata = (((framedata/255) ** 2.5) * 255).astype(np.uint8)
- for cx, cy in product(range(CRATES_X), range(CRATES_Y)):
- datar = framedata[cy*CRATE_HEIGHT:(cy+1)*CRATE_HEIGHT, cx*CRATE_WIDTH:(cx+1)*CRATE_WIDTH, :3]
- data = datar.flat
- if len(data) != FRAME_SIZE:
- raise ValueError('Invalid frame data. Expected {} bytes, got {}.'.format(FRAME_SIZE, len(data)))
- # Send framebuffer data
- dev.write(0x01, bytes([0, cx, cy])+bytes(data))
- # Send latch command
- dev.write(0x01, b'\x01')
+ # just use the first Mate Light available
+ rgba = len(framedata) == DISPLAY_WIDTH*DISPLAY_HEIGHT*4
+ global dbuf
+ np.copyto(dbuf[:640*(3+rgba)], np.frombuffer(framedata, dtype=np.uint8))
+ ml.matelight_send_frame(matelights, dbuf.ctypes.data_as(POINTER(c_uint8)), c_size_t(CRATES_X), c_size_t(CRATES_Y), c_float(BRIGHTNESS), rgba)
diff --git a/host/matelight.service b/host/matelight.service
new file mode 100644
index 0000000..7592791
--- /dev/null
+++ b/host/matelight.service
@@ -0,0 +1,10 @@
+[Unit]
+Description=MateLight Server
+
+[Service]
+WorkingDirectory=/root/matelight/host
+ExecStart=/root/matelight/host/server.py
+Type=simple
+
+[Install]
+WantedBy=multi-user.target
diff --git a/host/net.h b/host/net.h
deleted file mode 100644
index d8ec949..0000000
--- a/host/net.h
+++ /dev/null
@@ -1,18 +0,0 @@
-#ifndef __NET_H__
-#define __NET_H__
-
-#include "config.h"
-
-typedef struct {
- uint8_t r, g, b;
-} rgb_t;
-
-typedef struct {
- uint32_t magic, seq;
- uint16_t width, height;
- rgb_t data[DISPLAY_WIDTH*DISPLAY_HEIGHT];
-} ml_packet_t;
-
-#define UDP_BUF_SIZE sizeof(ml_packet_t)
-
-#endif//__NET_H__
diff --git a/host/server.py b/host/server.py
index ab3f7b5..456eedb 100755
--- a/host/server.py
+++ b/host/server.py
@@ -1,22 +1,22 @@
#!/usr/bin/env python
from socketserver import *
+import socket
+import struct
+import zlib
from time import time, strftime, sleep
-from collections import namedtuple
-from itertools import product
+from collections import namedtuple, deque
+import itertools
import threading
import random
+import os
-from ctypes import CDLL, POINTER, c_void_p, Structure, c_uint8, c_size_t, cast, addressof
-
-import numpy as np
-
-from matelight import sendframe, DISPLAY_WIDTH, DISPLAY_HEIGHT
+from ctypes import *
+from matelight import sendframe, DISPLAY_WIDTH, DISPLAY_HEIGHT, FRAME_SIZE
UDP_TIMEOUT = 3.0
-
class COLOR(Structure):
_fields_ = [('r', c_uint8), ('g', c_uint8), ('b', c_uint8), ('a', c_uint8)]
@@ -26,134 +26,151 @@ class FRAMEBUFFER(Structure):
bdf = CDLL('./libbdf.so')
bdf.read_bdf_file.restype = c_void_p
bdf.framebuffer_render_text.restype = POINTER(FRAMEBUFFER)
+bdf.framebuffer_render_text.argtypes= [c_char_p, c_void_p, c_void_p, c_size_t, c_size_t, c_size_t]
unifont = bdf.read_bdf_file('unifont.bdf')
-def render_text(text):
+def compute_text_bounds(text):
assert unifont
- fb = bdf.framebuffer_render_text(bytes(str(text), 'UTF-8'), unifont)
- fbd = fb.contents
- buf = np.ctypeslib.as_array(cast(fbd.data, POINTER(c_uint8)), shape=(fbd.h, fbd.w, 4))
- # Set data pointer to NULL before freeing framebuffer struct to prevent free_framebuffer from also freeing the data
- # buffer that is now used by numpy
- #bdf.console_render_buffer(fb)
- fbd.data = cast(c_void_p(), POINTER(COLOR))
- bdf.free_framebuffer(fb)
- return buf
+ textbytes = bytes(str(text), 'UTF-8')
+ textw, texth = c_size_t(0), c_size_t(0)
+ res = bdf.framebuffer_get_text_bounds(textbytes, unifont, byref(textw), byref(texth))
+ if res:
+ raise ValueError('Invalid text')
+ return textw.value, texth.value
+
+cbuf = create_string_buffer(FRAME_SIZE*sizeof(COLOR))
+cbuflock = threading.Lock()
+def render_text(text, offset):
+ global cbuf
+ cbuflock.acquire()
+ textbytes = bytes(str(text), 'UTF-8')
+ res = bdf.framebuffer_render_text(textbytes, unifont, cbuf, DISPLAY_WIDTH, DISPLAY_HEIGHT, offset)
+ if res:
+ raise ValueError('Invalid text')
+ cbuflock.release()
+ return cbuf
+
+printlock = threading.Lock()
def printframe(fb):
- h,w,_ = fb.shape
- print('\033[s\033[H', end='')
- bdf.console_render_buffer(fb.ctypes.data_as(POINTER(c_uint8)), w, h)
- print('\033[0m\033[u', end='')
-
-def scroll(text):
- """ Returns whether it could scroll all the text uninterrupted """
- fb = render_text(text);
- h,w,_ = fb.shape
- for i in range(-DISPLAY_WIDTH,w+1):
- if current_entry.entrytype == 'udp' or (current_entry in defaulttexts and textqueue):
- return False
- leftpad = np.zeros((DISPLAY_HEIGHT, max(-i, 0), 4), dtype=np.uint8)
- framedata = fb[:, max(0, i):min(i+DISPLAY_WIDTH, w)]
- rightpad = np.zeros((DISPLAY_HEIGHT, min(DISPLAY_WIDTH, max(0, i+DISPLAY_WIDTH-w)), 4), dtype=np.uint8)
- dice = np.concatenate((leftpad, framedata, rightpad), 1)
- sendframe(dice)
- #printframe(dice)
- fb = render_text(text);
- return True
-
-QueueEntry = namedtuple('QueueEntry', ['entrytype', 'remote', 'timestamp', 'text'])
-defaulttexts = [QueueEntry('text', '127.0.0.1', 0, t) for t in [
- '\x1B[92mMate Light\x1B[93m@\x1B[92mPlay store or \x1B[94;101mtcp://matelight.cbrp3.c-base.org:1337\x1B[0;91m ♥',
- '\x1B[92mMate Light\x1B[0;91m ♥ \x1B[92mUnicode',
- '\x1B[92mMate Light\x1B[0m powered by \x1B[95mMicrosoft™ \x1B[96mMarquee Manager® Professional OEM',
- '\x1B[92mMate Light\x1B[0m powered by \x1B[95mData Becker™ \x1B[96mLaufschriftstudio 2000® Platinum Edition',
- '\x1B[92mMate Light\x1B[0m powered by \x1B[95mApple™ \x1B[96miScroll®',
- '\x1B[92mMate Light\x1B[0m powered by \x1B[95mSiemens™ \x1B[96mLaufschrift® v.0.1.2b fuer Intel™ Pentium®',
- ]]
-current_entry = random.choice(defaulttexts)
-conns = {}
-textqueue = []
+ printlock.acquire()
+ print('\0337\033[H', end='')
+ print('Rendering frame @{}'.format(time()))
+ bdf.console_render_buffer(fb, DISPLAY_WIDTH, DISPLAY_HEIGHT)
+ #print('\033[0m\033[KCurrently rendering', current_entry.entrytype, 'from', current_entry.remote, ':', current_entry.text, '\0338', end='')
+ printlock.release()
def log(*args):
- print(strftime('[%m-%d %H:%M:%S]'), *args)
+ printlock.acquire()
+ print(strftime('\x1B[93m[%m-%d %H:%M:%S]\x1B[0m'), ' '.join(str(arg) for arg in args), '\x1B[0m')
+ printlock.release()
+
+class TextRenderer:
+ def __init__(self, text):
+ self.text = text
+ self.width, _ = compute_text_bounds(text)
+
+ def __iter__(self):
+ for i in range(-DISPLAY_WIDTH, self.width):
+ yield render_text(self.text, i)
+
+class MateLightUDPServer:
+ def __init__(self, port=1337, ip=''):
+ self.current_client = None
+ self.last_timestamp = 0
+ self.socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
+ self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
+ self.socket.bind((ip, port))
+ self.thread = threading.Thread(target = self.udp_receive)
+ self.thread.daemon = True
+ self.start = self.thread.start
+ self.frame_condition = threading.Condition()
+ self.frame = None
+
+ def frame_da(self):
+ return self.frame is not None
+
+ def __iter__(self):
+ while True:
+ with self.frame_condition:
+ if not self.frame_condition.wait_for(self.frame_da, timeout=UDP_TIMEOUT):
+ raise StopIteration()
+ frame, self.frame = self.frame, None
+ yield frame
+
+ def udp_receive(self):
+ while True:
+ try:
+ data, (addr, sport) = self.socket.recvfrom(FRAME_SIZE*3+4)
+ timestamp = time()
+ if timestamp - self.last_timestamp > UDP_TIMEOUT:
+ self.current_client = addr
+ log('\x1B[91mAccepting UDP data from\x1B[0m', addr)
+ if addr == self.current_client:
+ if len(data) == FRAME_SIZE*3+4:
+ frame = data[:-4]
+ crc1, = struct.unpack('!I', data[-4:])
+ if crc1:
+ crc2, = zlib.crc32(frame, 0),
+ if crc1 != crc2:
+ raise ValueError('Invalid frame CRC checksum: Expected {}, got {}'.format(crc2, crc1))
+ elif len(data) == FRAME_SIZE*3:
+ frame = data
+ else:
+ raise ValueError('Invalid frame size: {}'.format(len(data)))
+ self.last_timestamp = timestamp
+ with self.frame_condition:
+ self.frame = frame
+ self.frame_condition.notify()
+ except Exception as e:
+ log('Error receiving UDP frame:', e)
-class MateLightUDPHandler(BaseRequestHandler):
- def handle(self):
- try:
- global current_entry, conns
- data = self.request[0].strip()
- if len(data) != FRAME_SIZE+4:
- #raise ValueError('Invalid frame size: Expected {}, got {}'.format(FRAME_SIZE+4, len(data)))
- return
- frame = data[:-4]
- #crc1, = struct.unpack('!I', data[-4:])
- #crc2, = zlib.crc32(frame, 0),
- #if crc1 != crc2:
- # raise ValueError('Invalid frame CRC checksum: Expected {}, got {}'.format(crc2, crc1))
- #socket.sendto(b'ACK', self.client_address)
- a = np.array(list(frame), dtype=np.uint8)
- timestamp = time()
- addr = self.client_address[0]
- if addr not in conns:
- current_entry = QueueEntry('udp', addr, timestamp, '')
- conns[addr] = current_entry
- log('New UDP connection from', addr)
- else:
- conns[addr].timestamp = timestamp
- if current_entry.entrytype == 'udp' and current_entry.remote == addr:
- frame = a.reshape((DISPLAY_HEIGHT, DISPLAY_WIDTH, 3))
- sendframe(frame)
- #printframe(frame)
- except Exception as e:
- print('Error receiving UDP frame:', e)
- ex_type, ex, tb = sys.exc_info()
- traceback.print_tb(tb)
+renderqueue = deque()
class MateLightTCPTextHandler(BaseRequestHandler):
def handle(self):
- global current_entry, conns
+ global render_deque
data = str(self.request.recv(1024).strip(), 'UTF-8')
addr = self.client_address[0]
- timestamp = time()
if len(data) > 140:
self.request.sendall('TOO MUCH INFORMATION!\n')
return
- log('Text from {}: {}\x1B[0m'.format(addr, data))
- textqueue.append(QueueEntry('text', addr, timestamp, data))
+ log('\x1B[95mText from\x1B[0m {}: {}\x1B[0m'.format(addr, data))
+ renderqueue.append(TextRenderer(data))
self.request.sendall(b'KTHXBYE!\n')
TCPServer.allow_reuse_address = True
-server = TCPServer(('', 1337), MateLightTCPTextHandler)
-t = threading.Thread(target=server.serve_forever)
+tserver = TCPServer(('', 1337), MateLightTCPTextHandler)
+t = threading.Thread(target=tserver.serve_forever)
t.daemon = True
t.start()
-UDPServer.allow_reuse_address = True
-userver = UDPServer(('', 1337), MateLightUDPHandler)
-t = threading.Thread(target=userver.serve_forever)
-t.daemon = True
-t.start()
+userver = MateLightUDPServer()
+userver.start()
+
+defaultlines = [ TextRenderer(l[:-1].replace('\\x1B', '\x1B')) for l in open('default.lines').readlines() ]
+random.shuffle(defaultlines)
+defaulttexts = itertools.cycle(itertools.chain(*defaultlines))
if __name__ == '__main__':
- print('\n'*7)
+ #print('\033[?1049h'+'\n'*9)
while True:
- if current_entry.entrytype == 'text':
- if scroll(current_entry.text):
- textqueue.remove(current_entry)
- if textqueue:
- current_entry = textqueue[0]
- else:
- if conns:
- current_entry = random.choice(conns.values())
- else:
- current_entry = random.choice(defaulttexts)
- if current_entry.entrytype != 'udp' and textqueue:
- current_entry = textqueue[0]
- if current_entry.entrytype == 'udp':
- if time() - current_entry.timestamp > UDP_TIMEOUT:
- current_entry = random.choice(defaulttexts)
+ if renderqueue:
+ renderer = renderqueue.popleft()
+ elif userver.frame_da():
+ renderer = userver
+ else:
+ static_noise = False #time() % 300 < 60
+ if static_noise:
+ foo = os.urandom(640)
+ frame = bytes([v for c in zip(list(foo), list(foo), list(foo)) for v in c ])
else:
- sleep(0.2)
+ frame = next(defaulttexts)
+ sendframe(frame)
+ #printframe(next(defaulttexts))
+ continue
+ for frame in renderer:
+ sendframe(frame)
+ #printframe(frame)
diff --git a/host/terminal.py b/host/terminal.py
new file mode 100644
index 0000000..86b4db1
--- /dev/null
+++ b/host/terminal.py
@@ -0,0 +1,19 @@
+from pixelterm.pixelterm import termify_pixels
+
+class MockImage:
+ def __init__(self, nparray):
+ self.nparray = nparray
+ self.im = self
+
+ @property
+ def size(self):
+ h, w, _ = self.nparray.shape
+ return (w, h)
+
+ def getpixel(self, pos):
+ x, y = pos
+ return tuple(self.nparray[y][x])
+
+def printframe(framedata):
+ print(termify_pixels(MockImage(framedata)))
+
diff --git a/host/usb.c b/host/usb.c
new file mode 100644
index 0000000..5e696d5
--- /dev/null
+++ b/host/usb.c
@@ -0,0 +1,161 @@
+
+#include <stdio.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+#include <math.h>
+#include <libusb-1.0/libusb.h>
+
+#include "color.h"
+#include "usb.h"
+
+int matelight_usb_init(){
+ int rc = 0;
+ rc = libusb_init(NULL);
+ if(rc){
+ fprintf(stderr, "LIBML: Cannot initialize libusb\n");
+ return 1;
+ }
+ return 0;
+}
+
+void matelight_usb_destroy(){
+ libusb_exit(NULL);
+}
+
+static int matelight_cmp_str_desc(libusb_device_handle *dev, uint8_t index, char *value){
+ char data[256];
+ if(libusb_get_string_descriptor_ascii(dev, index, (unsigned char*)data, sizeof(data)) < 0){
+ fprintf(stderr, "LIBML: Cannot read device string descriptor\n");
+ return 0;
+ }
+ return strcmp(data, value) == 0;
+}
+
+matelight_handle *matelight_open(){
+ matelight_handle *out = NULL;
+ libusb_device** device_list;
+ struct libusb_device_descriptor desc;
+ ssize_t res = libusb_get_device_list(NULL, &device_list);
+ if(res == 0){
+ fprintf(stderr, "LIBML: Cannot find any connected matelight\n");
+ goto error;
+ }else if(res < 0){
+ fprintf(stderr, "LIBML: Error enumerating connected USB devices\n");
+ goto error;
+ }else{
+ out = calloc(res+1, sizeof(matelight_handle));
+ if(!out){
+ fprintf(stderr, "LIBML: Cannot allocate memory\n");
+ goto error;
+ }
+ memset(out, 0, (res+1)*sizeof(matelight_handle));
+ unsigned int found = 0;
+ for(ssize_t i=0; i<res; i++){
+ libusb_get_device_descriptor(device_list[i], &desc);
+ if(desc.idVendor == MATELIGHT_VID && desc.idProduct == MATELIGHT_PID){
+ libusb_device_handle *handle;
+ if(libusb_open(device_list[i], &(handle))){
+ fprintf(stderr, "LIBML: Cannot open Mate Light USB device\n");
+ goto error;
+ }
+ out[found].handle = handle;
+ if(matelight_cmp_str_desc(handle, desc.iManufacturer, "Gold & Apple"))
+ if(matelight_cmp_str_desc(handle, desc.iProduct, "Mate Light")){
+#define BUF_SIZE 256
+ char *serial = malloc(BUF_SIZE);
+ if(!serial){
+ fprintf(stderr, "LIBML: Cannot allocate memory\n");
+ goto error;
+ }
+ if(libusb_get_string_descriptor_ascii(out[found].handle, desc.iSerialNumber, (unsigned char*)serial, BUF_SIZE) < 0){
+ fprintf(stderr, "LIBML: Cannot read device string descriptor\n");
+ goto error;
+ }
+#undef BUF_SIZE
+ out[found].serial = serial;
+ found++;
+ }
+ }
+ }
+ out[found].handle = NULL;
+ out[found].serial = NULL;
+ }
+ libusb_free_device_list(device_list, 1);
+ return out;
+error:
+ if(res>0 && out){
+ for(ssize_t i=0; i<res; i++){
+ if(out[i].handle)
+ libusb_close(out[i].handle);
+ free(out[i].serial);
+ }
+ }
+ free(out);
+ libusb_free_device_list(device_list, 1);
+ return 0;
+}
+
+typedef struct{
+ uint8_t opcode;
+ uint8_t x, y;
+ rgb_t buf[CRATE_WIDTH*CRATE_HEIGHT];
+} crate_frame_t;
+
+int matelight_send_frame(matelight_handle *ml, void *buf, size_t w, size_t h, float brightness, int alpha){
+// fprintf(stderr, "\nFRAME START\n");
+ for(size_t cy=0; cy<h; cy++){
+// fprintf(stderr, "##### NEXT ROW\n");
+ for(size_t cx=0; cx<w; cx++){
+ crate_frame_t frame;
+ frame.opcode = 0;
+ frame.x = cx;
+ frame.y = cy;
+// fprintf(stderr, "CRATE %d %d\n", cx, cy);
+ for(size_t x=0; x<CRATE_WIDTH; x++){
+ for(size_t y=0; y<CRATE_HEIGHT; y++){
+ size_t dpos = y*CRATE_WIDTH + x;
+ size_t spos = w*CRATE_WIDTH*(cy*CRATE_HEIGHT+y) + cx*CRATE_WIDTH+x;
+ color_t *src;
+ if(alpha){
+ src = (((color_t*)buf)+spos);
+ }else{
+ src = (color_t*)(((rgb_t*)buf)+spos);
+ }
+ rgb_t *dst = frame.buf+dpos;
+ /* Gamma correction */
+#define GAMMA_APPLY(c) ((uint8_t)roundf(powf((c/255.0F), GAMMA) * brightness * 255))
+ dst->r = GAMMA_APPLY(src->r);
+ dst->g = GAMMA_APPLY(src->g);
+ dst->b = GAMMA_APPLY(src->b);
+// fprintf(stderr, "%c", ((dst->r > 1) ? '#' : '.'));
+#undef GAMMA_APPLY
+ }
+// fprintf(stderr, "\n");
+ }
+ int transferred = 0;
+ int rc = libusb_bulk_transfer(ml->handle, MATELIGHT_FRAMEDATA_ENDPOINT, (unsigned char*)&frame, sizeof(frame), &transferred, MATELIGHT_TIMEOUT);
+ if(rc){
+ fprintf(stderr, "LIBML: Cannot write frame data\n");
+ return 1;
+ }
+ if(transferred != sizeof(frame)){
+ fprintf(stderr, "LIBML: Could not transfer all frame data. Only transferred %d out of %d bytes.\n", transferred, sizeof(frame));
+ return 1;
+ }
+ }
+ }
+ uint8_t payload = 0x01;
+ int transferred = 0;
+ int rc = libusb_bulk_transfer(ml->handle, MATELIGHT_FRAMEDATA_ENDPOINT, &payload, sizeof(uint8_t), &transferred, MATELIGHT_TIMEOUT);
+ if(rc){
+ fprintf(stderr, "LIBML: Cannot write frame data\n");
+ return 1;
+ }
+ if(transferred != sizeof(uint8_t)){
+ fprintf(stderr, "LIBML: Could not transfer all frame data. Only transferred %d out of %d bytes.\n", transferred, sizeof(uint8_t));
+ return 1;
+ }
+ return 0;
+}
+
diff --git a/host/usb.h b/host/usb.h
new file mode 100644
index 0000000..fee3487
--- /dev/null
+++ b/host/usb.h
@@ -0,0 +1,27 @@
+#ifndef __USB_H__
+#define __USB_H__
+
+#include <libusb-1.0/libusb.h>
+
+#define MATELIGHT_VID 0x1cbe
+#define MATELIGHT_PID 0x0003
+
+#define CRATE_WIDTH 5
+#define CRATE_HEIGHT 4
+
+#define MATELIGHT_FRAMEDATA_ENDPOINT 0x01
+#define MATELIGHT_TIMEOUT 1000
+
+#define GAMMA 2.5F
+
+typedef struct {
+ libusb_device_handle *handle;
+ char *serial;
+} matelight_handle;
+
+int matelight_usb_init(void);
+void matelight_usb_destroy(void);
+matelight_handle *matelight_open(void);
+int matelight_send_frame(matelight_handle *ml, void *buf, size_t w, size_t h, float brightness, int alpha);
+
+#endif//__USB_H__