You are browsing as a guest. Sign up (or log in) to start making projects!

kgurchiek

@kgurchiek

Joined June 22nd, 2026

  • 7Devlogs
  • 2Projects
  • 0Ships
  • 0Votes
Open comments for this post

1h 49m 44s logged

android-desktop is live!

Just published my second npm package, this time for working with app windows on android’s desktop mode for external displays.

Now that this is polished I’ll officially be moving on to the hardware stage, I’ll just need to get funding for a monitor, stereo amplifier, 12V to 20V booster, and I should be good to start building.

Usage

parseDisplayInfo(data)

Parses a DisplayInfo object to JSON. This is mainly intended to be used internally by getDisplays().

  • data: A string containing DisplayInfo data. See the DisplayInfo source (especially the toString() function) for more info.

getDisplays()

Returns an array of DisplayInfo objects. See https://android.googlesource.com/platform/frameworks/base/+/master/core/java/android/view/DisplayInfo.java for more info on what’s included. To avoid issues when translationg to a JSON structure, any entries that were singular values rather than key/value pairs are put inside a _flags array, since most are intended to be flags. However, as you can see in the toString() function of the DisplayInfo source linked above, the first value entered is always the name of the device, so this will always be the first item in _flags.

getDensity(displayId)

Returns the dpi of a given display.

  • displayId: The id of the display

setDensity(displayId)

Sets the dpi of a given display. This can be useful for scaling app content.

  • displayId: The id of the display

startActivity(displayId, pkg, forceStart)

Starts an activity on a given display. If forceStart is set to true

  • displayId: The id of the display
  • pkg: The package name of the app to start. Some apps may require you to include an intent as well.
  • forceStart: If true, the app is closed and reopened, which can help with stability. Set to true by default.

getTaskId(displayId, pkg)

Returns the task id of an app on a given display. Returns null if it couldn’t be found.

  • displayId: The id of the display.
  • pkg: The package name of the app.

resizeTask(taskId, x1, y1, x2, y2)

Sets the position and size of an app window on external displays in desktop mode.

  • taskId: The task id of the app.
  • x1: The x coordinate to move the top left corner to.
  • y1: The y coordinate to move the top left corner to.
  • x2: The x coordinate to move the bottom right corner to.
  • y2: The y coordinate to move the bottom right corner to.
0
0
4
Open comments for this post

2h 25m 43s logged

Apps are working!

I’m using adb to spawn in windows for the apps I want and resize them to fill a grid on the screen. This is all fully customizable in config.json.

I changed up a lot of my plan for this project, instead of connecting my laptop to the display and forwarding the touch events to my phone I’m now just connecting my phone to a usb c hub that supports alt display to connect it directly to the monitor while also having a data cable connected to my laptop for adb.

This unfortunately means that showing my laptop’s display on the screen wil now be much less convenient… and that I completely wasted my time writing evdevlib. I was kinda excited to use my own package for a project, but it just isn’t worth the noticeable latency when forwarding inputs and using a capture card. I’ll probably look into plugging the phone and laptop into some sort of splitter to toggle which usb c is forwarded to the monitor, or worst-case scenario I could just leave the monitor input on auto and unplug the hdmi from my laptop when I’m not using it, since I doubt I’ll need it very often.

Either way I’m very happy with how this is turning out so far, the last major hurtle will be connecting the audio to my car. The monitor does have an aux output so I’ll just have to go buy an amplifier at some point, as well as the usb c hub. Until then I’ll just smooth some things out until I’m comfortable posting the code publicly.

0
0
3
Open comments for this post

10h 9m 24s logged

Since I’ll be using an alternate display on desktop mode, I’ll need to pass the displayId when I forward input so that it doesn’t input on my phone’s builtin screen. The id of the builtin one is conveniently always 0, but the id of the external display changes every time I plug my phone into the display card, so I decided to automate the process by parsing the output of adb shell cmd display get-displays whenever the script starts. This tuned out to be way more of a pain than I expected, especially since the syntax isn’t consistent across the entire output, but I’m happy with how it turned out, even if I never use it again. I might make another npm package for this one in the future, but I haven’t really done enough testing to be confident enough in its reliability yet. It seems to work fine for the display info at least, so I’ll move on for now.

0
0
2
Open comments for this post

5h 1m 35s logged

Just published the evdevlib package on npm!

Usage

listDevices([options])

Lists available character devices.

  • options: An object used to provide optional arguments.
    • info: Automatically fetches the device name from /sys/class/input.
    • path: Which directory to scan for devices (default: /dev/input).

Example:

import { listDevices } from 'evdevlib';

console.log(listDevices()); // Prints: [ { path: '/dev/input/event0' }]
console.log(listDevices({ info: true })); // Prints: [ { path: '/dev/input/event0', name: 'Mouse' }]

Class: DeviceListener

Listens to a device’s input stream

  • constructor(path, […args]):
    • path: The path of the interface to listen to (e.g. /dev/input/event0)
    • args: Args to pass to the EventEmitter constructor
  • stop(): Destroys the read stream and stops listening for input.
  • events
    • data: Emitted when input is received from the device.
    • error: Emitted when an ‘error’ event is received from the read stream.
      • error
    • close: Emitted when a ‘close’ event is received from the read stream.
    • end: Emitted after stop() or when an ‘end’ event is received from the read stream.

Example:

import { DeviceListener } from 'evdevlib';

let listener = new DeviceListener('/dev/input/event0');
listener.on('data', input => console.log(input));
listener.on('error', error => console.log('Error reading device input:', error));
console.log('Listening for input...');
// ...
listener.stop();
console.log('Stopped listening for input.');

An input event for relative mouse movement would look something like this:

{
    tv_sec: 1783887711n,
    tv_usec: 338192n,
    type: 'REL',
    code: 'REL_X',
    value: 1
}

See https://docs.kernel.org/input/event-codes.html#input-event-codes for all event types.

0
0
2
Open comments for this post

42m 29s logged

/streamsnipe is finished!

It uses the Twitch api to fetch all live Minecraft streams and then tries to find what server they’re playing on. I already had this working somewhat well for a while but since I added the case insensitive searching from the last update, username searching got relatively slow, so I decided to rewrite it to automatically run searches in the background every minute and cache the results. Then I also realized that since the only option is to filter by language I can easily fit that in the custom id of the discord embed buttons, allowing me to store the information on discord instead of in memory and keep the embed alive forever without timing out to save ram. Unfortunately I can’t do this for the search command since it has to many options, so for now it still times out after 10 minutes of inactivity.

0
0
1
Open comments for this post

11h 47m 48s logged

Published the new v2 api in preparation for the upcoming streamsnipe feature!

  • new includePlayers endpoint returns player history in /servers responses
  • /playerHistory has been removed
  • you can now put args in the “data” property of an object to allow for additional settings to be included
    • caseInsensitive option added for onlinePlayer and playerHistory (for finding players from their Twitch usernames)
    • I’ll probably use this more in the future, maybe for “not” conditions or something

Historical player storage was previously done by having one table that had every player’s name and uuid, and then a second table that stored when each player was last seen on each server, referencing each with their primary keys. This avoided storing a ton of duplicate usernames and uuids when users played on multiple different servers, but turns out it was still way faster if I just stored all of the data on one table to avoid a whole extra JOIN in the query. This also required a lot of reworking on the scanner and api, and I was too lazy to update the old v0 api so I officially discontinued it.

0
0
1

Followers

Loading…