@desklink/host 0.5.10 → 0.5.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -104,37 +104,7 @@ The one step no install does for you is [kernel input access](#kernel-input-acce
104
104
  and only the portal backend needs it.
105
105
 
106
106
  macOS arm64 (preview) also has a prebuilt engine. There is no prebuilt engine for other platforms. Another Linux (arm64, musl)
107
- can [build it from source](#building-from-source). Windows has a source-built transport preview for
108
- `x86_64-pc-windows-msvc`: static libvpx enables `serve` and consumer-fed H.264
109
- encoded sources. Native capture, input, clipboard and the agent indicator are
110
- unavailable. Native Windows desktop capture sessions are not supported yet. Node resolution
111
- is an opt-in preview for Windows 11 22H2+ x64: set `DESKLINK_WINDOWS=1` and
112
- point `DESKLINK_ENGINE` at a built `desklink-host.exe`. Windows platform packages
113
- are not published yet. Without static libvpx, the executable's `capabilities`
114
- command remains available, but CLI `capabilities` and `bridge` require `serve`.
115
- The Windows bridge defaults to `--source display` and handles Ctrl+Break;
116
- consumer-fed video requires `--source encoded`.
117
- A startup exit `0xC0000135` is reported as `missing-system-library` (a required
118
- DLL could not load); spawn failures are reported as `engine-spawn-failed` with
119
- the underlying error. Elevated windows and the secure desktop remain outside
120
- Windows control support.
121
-
122
- On Windows with Visual Studio Build Tools and PowerShell 7, build libvpx with
123
- `release/build-libvpx-win.ps1 -OutDir <new-build-directory>`, set
124
- `DESKLINK_VPX_STATIC_DIR` to its `prefix` directory and
125
- `RUSTFLAGS=-Ctarget-feature=+crt-static`, then run `cargo build --locked --target
126
- x86_64-pc-windows-msvc --manifest-path engine/Cargo.toml`. The build retains
127
- libvpx's licence and patent grant in `prefix/THIRD_PARTY_LICENSES.txt`.
128
- Without that prefix the compile seam still builds and refuses `serve`.
129
- Hosted Windows CI resolves the opt-in engine through the published Node API
130
- and completes a `serve` hello/capabilities handshake with clean shutdown; it
131
- also installs the packed packages into an empty project, checks the installed
132
- `@desklink/host` tree against its pack manifest, starts the engine from that
133
- install, and checks Chrome decode and input relay, and DLL
134
- dependencies. That install proof is installation and startup on a hosted
135
- machine: the engine still comes from a source build, because Windows platform
136
- packages are not published, and capture, input, encoding quality, mixed DPI, GPU
137
- encoding and elevation stay with the dedicated rig lanes.
107
+ can [build it from source](#building-from-source).
138
108
 
139
109
  ### macOS (preview)
140
110
 
@@ -305,7 +275,7 @@ source-coordinate `x`, `y`, `visible`, the cursor image's `hotspot` where
305
275
  known, and monotonic `timestamp_us` (the same clock as local frame metadata).
306
276
  A compositor without that protocol still captures, cursor hidden, and says so
307
277
  in `cursor.limitation`. X11 reports positions even on still
308
- frames and already captures root pixels without a cursor. macOS, Windows, and consumer-encoded
278
+ frames and already captures root pixels without a cursor. macOS and consumer-encoded
309
279
  sources return `cursor-unavailable` for hidden requests. Omitting the option
310
280
  preserves existing behavior. See [the cursor protocol](https://github.com/umeranjum17/desklink/blob/main/packages/desktop-host/docs/PROTOCOL.md#cursor-free-capture).
311
281
 
@@ -385,7 +355,7 @@ platform". Linux variants are published only after their own qualification; macO
385
355
  is published as a default-enabled preview, not as qualified desktop support.
386
356
  See [Install and run](#install-and-run) for current support. Another variant,
387
357
  such as Linux arm64, needs a real machine to run a real journey first. macOS
388
- and Windows are separate backends, not separate builds.
358
+ is a separate backend, not a separate build.
389
359
 
390
360
  ### Building and packing a release
391
361
 
@@ -445,19 +415,19 @@ the current platform's host and engine tarballs into an empty project and checks
445
415
 
446
416
  Publishing is automatic. Merging a version bump to `main` runs
447
417
  `.github/workflows/release.yml`: the test, build, build-pack and per-OS engine
448
- jobs gate a publish job that releases every package whose exact version is not
449
- yet on npm, in dependency order — the engine platform packages first, then
450
- `@desklink/host` immediately after the engine packages it pins, then
451
- `@desklink/axi` gated on the host (not the reverse), then
418
+ jobs gate a publish job (`release/publish-set.sh`) that releases every package
419
+ whose exact version is not yet on npm, in dependency order — the engine
420
+ platform packages first, then `@desklink/host`, then `@desklink/axi`, then
452
421
  `@desklink/react-native` — all with `--provenance`. A version lockstep check
453
422
  fails the workflow when `@desklink/host`, `@desklink/react-native` and
454
- `@desklink/axi` disagree, so a half-bump never reaches publish. One package's
455
- failure never stops the others, but the job ends red and every hold or failure
456
- names the package and version.
457
- A dependent waits for a just-published package within npm's own availability
458
- window, shared through `release/readback-budget.json` with the manual
459
- `release/publish.mjs` route, so a fresh release publishes in one run instead of
460
- holding each package until the next re-run.
423
+ `@desklink/axi` disagree, so a half-bump never reaches publish. The publish job
424
+ publishes nothing unless every tarball of the set is present and the registry
425
+ answers for each package.
426
+ Each package then waits until the registry serves the one before it, within a
427
+ 30-minute budget that covers npm's processing and CDN lag (shared through
428
+ `release/readback-budget.json` with the manual `release/publish.mjs` route).
429
+ The first failure stops the job with an error naming what is served and what
430
+ is not, so a failed run leaves only a prefix of that order published.
461
431
  The workflow uses trusted publishing (OIDC) and no npm token. Re-runs skip
462
432
  versions already published.
463
433
 
package/engine/Cargo.lock CHANGED
@@ -536,7 +536,7 @@ checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c"
536
536
 
537
537
  [[package]]
538
538
  name = "desklink-host"
539
- version = "0.5.10"
539
+ version = "0.5.11"
540
540
  dependencies = [
541
541
  "anyhow",
542
542
  "ashpd",
package/engine/Cargo.toml CHANGED
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "desklink-host"
3
- version = "0.5.10"
3
+ version = "0.5.11"
4
4
  edition = "2021"
5
5
  rust-version = "1.97.1"
6
6
  license = "Apache-2.0"
@@ -9,8 +9,8 @@
9
9
  NSPoint target, cursor;
10
10
  CFTimeInterval clickAt, typeAt, endingAt;
11
11
  BOOL positioned;
12
- BOOL pointMode, pointVisible;
13
- CFTimeInterval pointDeadline;
12
+ BOOL pointMode, pointVisible, pointDrawnHidden;
13
+ CFTimeInterval pointDeadline, pointStarted;
14
14
  NSString *pointLabel;
15
15
  }
16
16
  - (void)event:(char)kind x:(double)x y:(double)y;
@@ -24,7 +24,7 @@
24
24
  - (void)enablePointMode { pointMode = YES; }
25
25
  - (void)pointX:(double)x y:(double)y timeout:(unsigned)timeout label:(NSString *)label {
26
26
  cursor = NSMakePoint(x, self.bounds.size.height-y);
27
- pointLabel = label; pointVisible = YES;
27
+ pointLabel = label; pointVisible = YES; pointStarted = CACurrentMediaTime();
28
28
  pointDeadline = CACurrentMediaTime() + timeout/1000.0;
29
29
  [self setNeedsDisplay:YES];
30
30
  }
@@ -43,7 +43,9 @@
43
43
  - (void)end { if (pointMode) { [NSApp terminate:nil]; return; } endingAt = CACurrentMediaTime(); }
44
44
  - (void)tick {
45
45
  if (pointMode) {
46
- if (pointVisible && CACurrentMediaTime() >= pointDeadline) { pointVisible = NO; [self setNeedsDisplay:YES]; }
46
+ if (pointVisible && CACurrentMediaTime() >= pointDeadline) pointVisible = NO;
47
+ if (pointVisible || !pointDrawnHidden) [self setNeedsDisplay:YES];
48
+ pointDrawnHidden = !pointVisible;
47
49
  return;
48
50
  }
49
51
  if (endingAt && CACurrentMediaTime() - endingAt > 0.7) { [NSApp terminate:nil]; return; }
@@ -56,17 +58,22 @@
56
58
  if (pointMode) {
57
59
  if (!pointVisible) return;
58
60
  [[NSColor colorWithCalibratedRed:0.30 green:0.62 blue:0.82 alpha:1] set];
59
- NSBezierPath *ring = [NSBezierPath bezierPathWithOvalInRect:NSMakeRect(cursor.x-18, cursor.y-18, 36, 36)];
60
- ring.lineWidth = 4; [ring stroke];
61
- [[NSBezierPath bezierPathWithOvalInRect:NSMakeRect(cursor.x-3,cursor.y-3,6,6)] fill];
61
+ // 72pt ring with a 1.2s pulse wave growing out of it.
62
+ NSBezierPath *ring = [NSBezierPath bezierPathWithOvalInRect:NSMakeRect(cursor.x-36, cursor.y-36, 72, 72)];
63
+ ring.lineWidth = 6; [ring stroke];
64
+ [[NSBezierPath bezierPathWithOvalInRect:NSMakeRect(cursor.x-6,cursor.y-6,12,12)] fill];
65
+ double phase = fmod(CACurrentMediaTime() - pointStarted, 1.2) / 1.2, wave = 36 + 18*phase;
66
+ [[NSColor colorWithCalibratedRed:0.30 green:0.62 blue:0.82 alpha:1-phase] set];
67
+ NSBezierPath *pulse = [NSBezierPath bezierPathWithOvalInRect:NSMakeRect(cursor.x-wave, cursor.y-wave, 2*wave, 2*wave)];
68
+ pulse.lineWidth = 6 - 4*phase; [pulse stroke];
62
69
  if (pointLabel.length) {
63
- NSDictionary *style = @{NSFontAttributeName: [NSFont systemFontOfSize:13], NSForegroundColorAttributeName: NSColor.whiteColor};
70
+ NSDictionary *style = @{NSFontAttributeName: [NSFont boldSystemFontOfSize:26], NSForegroundColorAttributeName: NSColor.whiteColor};
64
71
  NSSize size = [pointLabel sizeWithAttributes:style];
65
- CGFloat x = fmax(0, fmin(cursor.x+30, self.bounds.size.width-size.width-16));
66
- CGFloat y = fmax(0, fmin(cursor.y-14, self.bounds.size.height-28));
72
+ CGFloat x = fmax(0, fmin(cursor.x+66, self.bounds.size.width-size.width-24));
73
+ CGFloat y = fmax(0, fmin(cursor.y-20, self.bounds.size.height-40));
67
74
  [[NSColor colorWithCalibratedRed:0.07 green:0.19 blue:0.28 alpha:1] set];
68
- [[NSBezierPath bezierPathWithRoundedRect:NSMakeRect(x,y,size.width+16,28) xRadius:4 yRadius:4] fill];
69
- [pointLabel drawAtPoint:NSMakePoint(x+8,y+6) withAttributes:style];
75
+ [[NSBezierPath bezierPathWithRoundedRect:NSMakeRect(x,y,size.width+24,40) xRadius:6 yRadius:6] fill];
76
+ [pointLabel drawAtPoint:NSMakePoint(x+12,y+(40-size.height)/2) withAttributes:style];
70
77
  }
71
78
  return;
72
79
  }
@@ -203,27 +203,30 @@ static FT_Face point_font(FT_Library *library) {
203
203
  FcResult result;FcPattern *match=FcFontMatch(NULL,pattern,&result);FcPatternDestroy(pattern);
204
204
  FcChar8 *path=NULL;FT_Face face=NULL;
205
205
  if(match && FcPatternGetString(match,FC_FILE,0,&path)==FcResultMatch)
206
- if(!FT_New_Face(*library,(const char *)path,0,&face))FT_Set_Pixel_Sizes(face,0,13);
206
+ if(!FT_New_Face(*library,(const char *)path,0,&face))FT_Set_Pixel_Sizes(face,0,26);
207
207
  if(match)FcPatternDestroy(match);
208
208
  return face;
209
209
  }
210
- static void draw_point(struct Overlay *o,struct Buffer *b,double x,double y,const char *label,FT_Face font,int visible) {
210
+ // 72 logical px ring with a 1.2 s pulse wave growing out of it, and a 40px
211
+ // badge with 26px text. Opaque pixels only, like the X11 cue.
212
+ static void draw_point(struct Overlay *o,struct Buffer *b,double x,double y,const char *label,FT_Face font,int visible,double elapsed) {
211
213
  memset(b->pixels,0,b->length);if(!visible)return;
212
214
  int w=o->width,h=o->height;
213
- for(int py=fmax(0,y-21);py<fmin(h,y+22);py++)for(int px=fmax(0,x-21);px<fmin(w,x+22);px++) {
215
+ double phase=fmod(elapsed,1.2)/1.2,wave=36+18*phase,wave_half=3-2*phase;
216
+ for(int py=fmax(0,y-56);py<fmin(h,y+57);py++)for(int px=fmax(0,x-56);px<fmin(w,x+57);px++) {
214
217
  double d=hypot(px-x,py-y);
215
- if(fabs(d-18)<=2 || d<=3)b->pixels[(size_t)py*w+px]=0xff4c9ed0;
218
+ if(fabs(d-36)<=3 || d<=6 || fabs(d-wave)<=wave_half)b->pixels[(size_t)py*w+px]=0xff4c9ed0;
216
219
  }
217
220
  if(!*label)return;
218
- int badge_w=16;
221
+ int badge_w=24;
219
222
  for(const char *p=label;*p;p++)if(!FT_Load_Char(font,(unsigned char)*p,FT_LOAD_DEFAULT))badge_w+=font->glyph->advance.x>>6;
220
- badge_w=fmin(w,badge_w);int bx=fmax(0,fmin(x+30,w-badge_w)),by=fmax(0,fmin(y-14,h-28));
221
- for(int py=by;py<fmin(h,by+28);py++)for(int px=bx;px<bx+badge_w;px++)b->pixels[(size_t)py*w+px]=0xff123047;
222
- int pen=bx+8;
223
+ badge_w=fmin(w,badge_w);int bx=fmax(0,fmin(x+66,w-badge_w)),by=fmax(0,fmin(y-20,h-40));
224
+ for(int py=by;py<fmin(h,by+40);py++)for(int px=bx;px<bx+badge_w;px++)b->pixels[(size_t)py*w+px]=0xff123047;
225
+ int pen=bx+12;
223
226
  for(const char *p=label;*p;p++)if(!FT_Load_Char(font,(unsigned char)*p,FT_LOAD_RENDER)) {
224
227
  FT_GlyphSlot g=font->glyph;
225
228
  for(unsigned row=0;row<g->bitmap.rows;row++)for(unsigned col=0;col<g->bitmap.width;col++) {
226
- int px=pen+g->bitmap_left+col,py=by+18-g->bitmap_top+row;
229
+ int px=pen+g->bitmap_left+col,py=by+28-g->bitmap_top+row;
227
230
  if(px<0||px>=w||py<0||py>=h)continue;
228
231
  unsigned a=g->bitmap.buffer[row*g->bitmap.pitch+col];
229
232
  unsigned r=(18*(255-a)+255*a)/255,green=(48*(255-a)+255*a)/255,blue=(71*(255-a)+255*a)/255;
@@ -314,7 +317,7 @@ static int overlay_main(int source_w,int source_h,int point_mode,int origin_know
314
317
  unsigned timeout=0;int offset=0;
315
318
  if(sscanf(pending,"P %lf %lf %u %n",&px,&py,&timeout,&offset)==3 && offset && timeout>=1 && timeout<=120000 && strlen(pending+offset)<=96) {
316
319
  snprintf(label,sizeof(label),"%s",pending+offset);deadline=now()+timeout/1000.0;active=1;dirty=1;
317
- visible=locate(&o,px,py,&x,&y);
320
+ visible=locate(&o,px,py,&x,&y);cue_started=now();
318
321
  }
319
322
  }
320
323
  else if(kind=='S')ending=now();
@@ -336,11 +339,11 @@ static int overlay_main(int source_w,int source_h,int point_mode,int origin_know
336
339
  used=0;
337
340
  }else if(used<sizeof(pending)-1)pending[used++]=bytes[i];
338
341
  }
339
- if(point_mode && visible && now()>=deadline){visible=0;dirty=1;}
342
+ if(point_mode && visible){if(now()>=deadline)visible=0;dirty=1;}
340
343
  if(!active || (point_mode&&!dirty))continue;
341
344
  if(!point_mode){x+=(tx-x)*0.24;y+=(ty-y)*0.24;}
342
345
  for(int i=0;i<2;i++)if(!o.buffers[i].busy) {
343
- if(point_mode)draw_point(&o,&o.buffers[i],x,y,label,font,visible);
346
+ if(point_mode)draw_point(&o,&o.buffers[i],x,y,label,font,visible,now()-cue_started);
344
347
  else draw(&o,&o.buffers[i],x,y,click,typed,ending,started,cue_started);
345
348
  dirty=0;
346
349
  o.buffers[i].busy=1;
@@ -163,6 +163,50 @@ int desklink_agent_overlay_main(const char *display_name) {
163
163
  return 0;
164
164
  }
165
165
 
166
+ // The ring is 72 logical px across, a 6px band with a pulse wave that grows
167
+ // out from it every PULSE_SECONDS, so the cue reads at a glance. The label is
168
+ // the core "fixed" font drawn at 2x, because a core X server offers nothing larger.
169
+ #define POINT_RING 36
170
+ #define POINT_PULSE_SECONDS 1.2
171
+ #define POINT_BADGE_H 40
172
+ #define POINT_FRAME_SECONDS 0.033
173
+
174
+ static void point_shape(Display *d, Window window, Pixmap mask, GC shape, int x, int y,
175
+ int badge_x, int badge_y, int badge_w, double elapsed) {
176
+ double phase = fmod(elapsed, POINT_PULSE_SECONDS) / POINT_PULSE_SECONDS;
177
+ int wave = POINT_RING + (int)lround(18*phase), wave_width = 6 - (int)lround(4*phase);
178
+ XSetForeground(d, shape, 0); XFillRectangle(d, mask, shape, 0, 0, 0xffff, 0xffff);
179
+ XSetForeground(d, shape, 1);
180
+ XSetLineAttributes(d, shape, 6, LineSolid, CapRound, JoinRound);
181
+ XDrawArc(d, mask, shape, x-POINT_RING, y-POINT_RING, 2*POINT_RING, 2*POINT_RING, 0, 360*64);
182
+ XFillArc(d, mask, shape, x-6, y-6, 12, 12, 0, 360*64);
183
+ XSetLineAttributes(d, shape, wave_width, LineSolid, CapRound, JoinRound);
184
+ XDrawArc(d, mask, shape, x-wave, y-wave, 2*wave, 2*wave, 0, 360*64);
185
+ if (badge_w) XFillRectangle(d, mask, shape, badge_x, badge_y, badge_w, POINT_BADGE_H);
186
+ XShapeCombineMask(d, window, ShapeBounding, 0, 0, mask, ShapeSet);
187
+ }
188
+
189
+ // Text is rasterised once at 1x and drawn as 2x2 blocks.
190
+ static void point_text(Display *d, Window window, GC ink, XFontStruct *font, const char *label,
191
+ int len, int x, int y) {
192
+ int w = XTextWidth(font, label, len), h = font->ascent + font->descent;
193
+ Pixmap bits = XCreatePixmap(d, window, w, h, 1);
194
+ GC gc = XCreateGC(d, bits, 0, NULL);
195
+ XSetFont(d, gc, font->fid);
196
+ XSetForeground(d, gc, 0); XFillRectangle(d, bits, gc, 0, 0, w, h);
197
+ XSetForeground(d, gc, 1); XDrawString(d, bits, gc, 0, font->ascent, label, len);
198
+ XImage *image = XGetImage(d, bits, 0, 0, w, h, 1, ZPixmap);
199
+ XRectangle *blocks = malloc(sizeof(XRectangle) * (size_t)w * h);
200
+ int count = 0;
201
+ if (image && blocks)
202
+ for (int row = 0; row < h; row++) for (int col = 0; col < w; col++)
203
+ if (XGetPixel(image, col, row))
204
+ blocks[count++] = (XRectangle){(short)(x+2*col), (short)(y+2*row), 2, 2};
205
+ if (count) XFillRectangles(d, window, ink, blocks, count);
206
+ free(blocks); if (image) XDestroyImage(image);
207
+ XFreeGC(d, gc); XFreePixmap(d, bits);
208
+ }
209
+
166
210
  // Point cues are independent of the real cursor. A shaped override-redirect
167
211
  // window works without a compositor; an empty input shape passes every event
168
212
  // through and mapping it never asks for keyboard focus. It intentionally appears
@@ -185,12 +229,11 @@ int desklink_point_overlay_main(const char *display_name) {
185
229
  Pixmap mask = XCreatePixmap(d, root, width, height, 1);
186
230
  GC shape = XCreateGC(d, mask, 0, NULL), ink = XCreateGC(d, window, 0, NULL);
187
231
  XFontStruct *font = XLoadQueryFont(d, "fixed");
188
- if (font) XSetFont(d, ink, font->fid);
189
- XSetLineAttributes(d, shape, 4, LineSolid, CapRound, JoinRound);
190
232
  fcntl(STDIN_FILENO, F_SETFL, fcntl(STDIN_FILENO, F_GETFL) | O_NONBLOCK);
191
233
  puts("READY"); fflush(stdout);
192
234
  char pending[512]; size_t used = 0;
193
- double deadline = 0;
235
+ double deadline = 0, started = 0, drawn = 0;
236
+ int x = 0, y = 0, badge_x = 0, badge_y = 0, badge_w = 0;
194
237
  for (;;) {
195
238
  char bytes[512]; ssize_t n = read(STDIN_FILENO, bytes, sizeof(bytes));
196
239
  if (n == 0) break;
@@ -200,35 +243,41 @@ int desklink_point_overlay_main(const char *display_name) {
200
243
  double px, py; unsigned timeout; int offset = 0;
201
244
  if (sscanf(pending, "P %lf %lf %u %n", &px, &py, &timeout, &offset) != 3 || !offset) continue;
202
245
  const char *label = pending + offset;
203
- int x = (int)px, y = (int)py, len = (int)strlen(label);
204
- int badge_w = font ? XTextWidth(font, label, len) + 16 : len * 6 + 16;
246
+ int len = (int)strlen(label);
247
+ x = (int)px; y = (int)py;
248
+ int text_w = font ? 2*XTextWidth(font, label, len) : 12*len;
249
+ badge_w = len ? text_w + 24 : 0;
205
250
  if (badge_w > width) badge_w = width;
206
- int badge_x = x + 30, badge_y = y - 14;
251
+ badge_x = x + POINT_RING + 30; badge_y = y - POINT_BADGE_H/2;
207
252
  if (badge_x + badge_w > width) badge_x = width - badge_w;
208
253
  if (badge_y < 0) badge_y = 0;
209
- if (badge_y + 28 > height) badge_y = height - 28;
254
+ if (badge_y + POINT_BADGE_H > height) badge_y = height - POINT_BADGE_H;
210
255
  XUnmapWindow(d, window);
211
- XSetForeground(d, shape, 0); XFillRectangle(d, mask, shape, 0, 0, width, height);
212
- XSetForeground(d, shape, 1);
213
- XDrawArc(d, mask, shape, x-18, y-18, 36, 36, 0, 360*64);
214
- XFillArc(d, mask, shape, x-3, y-3, 6, 6, 0, 360*64);
215
- if (len) XFillRectangle(d, mask, shape, badge_x, badge_y, badge_w, 28);
216
- XShapeCombineMask(d, window, ShapeBounding, 0, 0, mask, ShapeSet);
256
+ started = now();
257
+ point_shape(d, window, mask, shape, x, y, badge_x, badge_y, badge_w, 0);
217
258
  XMapRaised(d, window);
218
259
  XClearWindow(d, window);
219
260
  if (len) {
220
261
  XSetForeground(d, ink, 0x123047);
221
- XFillRectangle(d, window, ink, badge_x, badge_y, badge_w, 28);
262
+ XFillRectangle(d, window, ink, badge_x, badge_y, badge_w, POINT_BADGE_H);
222
263
  XSetForeground(d, ink, 0xffffff);
223
- XDrawString(d, window, ink, badge_x+8, badge_y+18, label, len);
264
+ if (font) point_text(d, window, ink, font, label, len, badge_x+12,
265
+ badge_y + (POINT_BADGE_H - 2*(font->ascent+font->descent))/2);
224
266
  }
225
267
  XSync(d, False);
226
- deadline = now() + timeout/1000.0;
268
+ drawn = now();
269
+ deadline = drawn + timeout/1000.0;
270
+ }
271
+ double t = now();
272
+ if (deadline && t >= deadline) { XUnmapWindow(d, window); XFlush(d); deadline = 0; }
273
+ else if (deadline && t - drawn >= POINT_FRAME_SECONDS) {
274
+ point_shape(d, window, mask, shape, x, y, badge_x, badge_y, badge_w, t - started);
275
+ XFlush(d); drawn = t;
227
276
  }
228
- if (deadline && now() >= deadline) { XUnmapWindow(d, window); XFlush(d); deadline = 0; }
229
277
  usleep(5000);
230
278
  }
231
279
  XDestroyWindow(d, window); XFreeGC(d, shape); XFreeGC(d, ink);
232
280
  XFreePixmap(d, mask); if (font) XFreeFont(d, font); XCloseDisplay(d);
233
281
  return 0;
234
282
  }
283
+
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@desklink/host",
3
- "version": "0.5.10",
3
+ "version": "0.5.11",
4
4
  "license": "Apache-2.0",
5
5
  "repository": {
6
6
  "type": "git",
@@ -38,7 +38,7 @@
38
38
  "ws": "8.21.3"
39
39
  },
40
40
  "optionalDependencies": {
41
- "@desklink/host-linux-x64-gnu": "0.5.10",
42
- "@desklink/host-darwin-arm64": "0.5.10"
41
+ "@desklink/host-linux-x64-gnu": "0.5.11",
42
+ "@desklink/host-darwin-arm64": "0.5.11"
43
43
  }
44
44
  }