Skip to content

Commit bfb48e4

Browse files
committed
feat: added example code for a few languages
1 parent bb28e93 commit bfb48e4

8 files changed

Lines changed: 300 additions & 0 deletions

File tree

.gitignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,3 +22,7 @@
2222
.DS_Store
2323
**/*.rs.bk
2424

25+
# Compiled Examples
26+
/examples/c/get_active_game
27+
/examples/rust/target/
28+
/examples/rust/Cargo.lock

examples/README.md

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
# Contextd Examples
2+
3+
This directory contains examples of how to interact with the `contextd` daemon from different programming languages.
4+
5+
Because `contextd` uses [Varlink](https://varlink.org) over standard Unix sockets, you can use any language that supports Unix domain sockets and JSON serialization.
6+
7+
## Available Examples
8+
9+
### 1. Python
10+
Python is a great choice for scripting against `contextd` because it has built-in support for Unix sockets and JSON, requiring zero external dependencies.
11+
- **`python/get_active_game.py`**: Queries the core daemon for the currently foregrounded game.
12+
- **`python/subscribe_rgb.py`**: Subscribes to real-time ambient lighting updates from the RGB observer socket.
13+
14+
### 2. Shell
15+
For simple queries in bash scripts, the `varlinkctl` tool (usually installed alongside varlink) allows one-line interactions.
16+
- **`shell/get_diagnostics.sh`**: Uses `varlinkctl` to query system diagnostics.
17+
18+
### 3. Rust
19+
While the `contextd` daemon itself is written in Rust and uses the `varlink` crate, you can also interact with it using nothing but the standard library and `serde_json`.
20+
- **`rust/src/main.rs`**: A standard Cargo project showing how to connect to the Unix socket and parse a JSON response.
21+
*Run it with:* `cd rust && cargo run`
22+
23+
### 4. C
24+
A minimal C example demonstrating how to use POSIX sockets to send a Varlink JSON request and read the response.
25+
- **`c/get_active_game.c`**: Pure C with no external dependencies (uses standard `sys/socket.h` and `sys/un.h`).
26+
*Compile and run:* `gcc c/get_active_game.c -o get_active_game && ./get_active_game`
27+
28+
## Socket Paths
29+
Remember the daemon exposes three interfaces:
30+
1. **Core Socket**: `/run/contextd/public/contextd.socket`
31+
2. **RGB Observer**: `/run/contextd/public/contextd-rgb-observer.socket`
32+
3. **RGB Control**: `/run/contextd/private/contextd-rgb-control.socket`
33+
34+
> **Note:** The daemon must be running for these examples to work.

examples/c/get_active_game.c

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
// Compile with: gcc get_active_game.c -o get_active_game
2+
// Run with: ./get_active_game
3+
4+
#include <stdio.h>
5+
#include <stdlib.h>
6+
#include <string.h>
7+
#include <unistd.h>
8+
#include <sys/socket.h>
9+
#include <sys/un.h>
10+
11+
#define SOCKET_PATH "/run/contextd/public/contextd.socket"
12+
13+
// Note: String literals in C implicitly have a null terminator ('\0') at the end.
14+
// We use sizeof() which includes this null terminator because Varlink requires it.
15+
#define REQUEST "{\"method\":\"com.performativenonsense.contextd.GetActiveGame\",\"parameters\":{}}"
16+
17+
int main() {
18+
int sock = socket(AF_UNIX, SOCK_STREAM, 0);
19+
if (sock < 0) {
20+
perror("socket");
21+
return 1;
22+
}
23+
24+
struct sockaddr_un addr;
25+
memset(&addr, 0, sizeof(addr));
26+
addr.sun_family = AF_UNIX;
27+
strncpy(addr.sun_path, SOCKET_PATH, sizeof(addr.sun_path) - 1);
28+
29+
if (connect(sock, (struct sockaddr*)&addr, sizeof(addr)) < 0) {
30+
perror("connect");
31+
fprintf(stderr, "Is the contextd daemon running?\n");
32+
close(sock);
33+
return 1;
34+
}
35+
36+
// Send the varlink request (including the null byte)
37+
if (write(sock, REQUEST, sizeof(REQUEST)) < 0) {
38+
perror("write");
39+
close(sock);
40+
return 1;
41+
}
42+
43+
// Read the response until we hit the null byte
44+
char buffer[4096];
45+
int bytes_read;
46+
printf("Raw JSON Response:\n");
47+
48+
while ((bytes_read = read(sock, buffer, sizeof(buffer))) > 0) {
49+
for (int i = 0; i < bytes_read; i++) {
50+
if (buffer[i] == '\0') {
51+
printf("\n");
52+
close(sock);
53+
return 0;
54+
} else {
55+
putchar(buffer[i]);
56+
}
57+
}
58+
}
59+
60+
close(sock);
61+
return 0;
62+
}

examples/python/get_active_game.py

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
#!/usr/bin/env python3
2+
import socket
3+
import json
4+
import sys
5+
6+
# Path to the public core socket
7+
SOCKET_PATH = "/run/contextd/public/contextd.socket"
8+
9+
def main():
10+
try:
11+
# Create a Unix domain socket
12+
sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
13+
sock.connect(SOCKET_PATH)
14+
except FileNotFoundError:
15+
print(f"Error: Could not connect to {SOCKET_PATH}. Is contextd running?")
16+
sys.exit(1)
17+
18+
# Varlink requests are JSON objects ending with a null byte
19+
request = {
20+
"method": "com.performativenonsense.contextd.GetActiveGame",
21+
"parameters": {}
22+
}
23+
24+
# Send the request
25+
message = json.dumps(request).encode('utf-8') + b'\0'
26+
sock.sendall(message)
27+
28+
# Read the response (read until the null byte)
29+
data = bytearray()
30+
while True:
31+
chunk = sock.recv(1024)
32+
if not chunk:
33+
break
34+
data.extend(chunk)
35+
if b'\0' in chunk:
36+
break
37+
38+
# Parse and print the response
39+
response_str = data.split(b'\0')[0].decode('utf-8')
40+
response = json.loads(response_str)
41+
42+
if "error" in response:
43+
print(f"Error: {response['error']}")
44+
else:
45+
game = response.get("parameters", {}).get("game")
46+
if game:
47+
print(f"Active Game Detected:")
48+
print(f" Name: {game.get('name')}")
49+
print(f" Source: {game.get('source')}")
50+
print(f" PID: {game.get('pid')}")
51+
else:
52+
print("No active game is currently foregrounded.")
53+
54+
if __name__ == "__main__":
55+
main()

examples/python/subscribe_rgb.py

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
#!/usr/bin/env python3
2+
import socket
3+
import json
4+
import sys
5+
6+
# Path to the public RGB observer socket
7+
SOCKET_PATH = "/run/contextd/public/contextd-rgb-observer.socket"
8+
9+
def main():
10+
try:
11+
sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
12+
sock.connect(SOCKET_PATH)
13+
except FileNotFoundError:
14+
print(f"Error: Could not connect to {SOCKET_PATH}. Is contextd running in RGB mode?")
15+
sys.exit(1)
16+
17+
# Send a subscription request with "more": True
18+
request = {
19+
"method": "com.performativenonsense.contextd.rgb.Observer.SubscribeLightingContext",
20+
"parameters": {},
21+
"more": True
22+
}
23+
24+
message = json.dumps(request).encode('utf-8') + b'\0'
25+
sock.sendall(message)
26+
27+
print("Subscribed to RGB lighting updates. Press Ctrl+C to stop.")
28+
29+
# Read the stream of responses
30+
buffer = bytearray()
31+
try:
32+
while True:
33+
chunk = sock.recv(4096)
34+
if not chunk:
35+
print("Connection closed by daemon.")
36+
break
37+
38+
buffer.extend(chunk)
39+
40+
# Process complete messages (separated by null bytes)
41+
while b'\0' in buffer:
42+
msg_bytes, buffer = buffer.split(b'\0', 1)
43+
44+
try:
45+
response = json.loads(msg_bytes.decode('utf-8'))
46+
if "error" in response:
47+
print(f"Error: {response['error']}")
48+
continue
49+
50+
params = response.get("parameters", {})
51+
color = params.get("main_color")
52+
53+
if color:
54+
print(f"New Vibe Color -> R: {color.get('r'):3} | G: {color.get('g'):3} | B: {color.get('b'):3} | A: {color.get('a'):3}")
55+
56+
except json.JSONDecodeError:
57+
pass
58+
59+
except KeyboardInterrupt:
60+
print("\nUnsubscribing...")
61+
finally:
62+
sock.close()
63+
64+
if __name__ == "__main__":
65+
main()

examples/rust/Cargo.toml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
[package]
2+
name = "contextd-rust-example"
3+
version = "0.1.0"
4+
edition = "2021"
5+
6+
[dependencies]
7+
serde_json = "1.0"

examples/rust/src/main.rs

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
use std::io::{Read, Write};
2+
use std::os::unix::net::UnixStream;
3+
4+
fn main() -> std::io::Result<()> {
5+
let socket_path = "/run/contextd/public/contextd.socket";
6+
7+
println!("Connecting to {}...", socket_path);
8+
9+
// Connect to the Unix socket
10+
let mut stream = match UnixStream::connect(socket_path) {
11+
Ok(stream) => stream,
12+
Err(e) => {
13+
eprintln!("Failed to connect to contextd socket: {}", e);
14+
eprintln!("Is the contextd daemon running?");
15+
std::process::exit(1);
16+
}
17+
};
18+
19+
// Format the varlink request (JSON object)
20+
let request = serde_json::json!({
21+
"method": "com.performativenonsense.contextd.GetActiveGame",
22+
"parameters": {}
23+
});
24+
25+
// Varlink requires requests to be terminated by a null byte
26+
let mut message = serde_json::to_vec(&request).unwrap();
27+
message.push(b'\0');
28+
29+
// Send the request
30+
stream.write_all(&message)?;
31+
32+
// Read the response up to the null byte
33+
let mut buffer = Vec::new();
34+
let mut buf = [0; 1024];
35+
loop {
36+
let bytes_read = stream.read(&mut buf)?;
37+
if bytes_read == 0 {
38+
break;
39+
}
40+
for &b in &buf[..bytes_read] {
41+
if b == b'\0' {
42+
// Parse and print the response
43+
if let Ok(response) = serde_json::from_slice::<serde_json::Value>(&buffer) {
44+
println!("Response:\n{:#?}", response);
45+
}
46+
return Ok(());
47+
}
48+
buffer.push(b);
49+
}
50+
}
51+
52+
Ok(())
53+
}

examples/shell/get_diagnostics.sh

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
#!/bin/bash
2+
# This script uses the varlinkctl utility to query the contextd daemon.
3+
4+
SOCKET="unix:/run/contextd/public/contextd.socket"
5+
INTERFACE="com.performativenonsense.contextd"
6+
7+
if ! command -v varlinkctl &> /dev/null; then
8+
echo "Error: varlinkctl is not installed. Please install it (usually available in the 'varlink' package)."
9+
exit 1
10+
fi
11+
12+
echo "Querying system diagnostics from contextd..."
13+
echo "------------------------------------------"
14+
15+
# Use jq if available for pretty printing, otherwise just output the raw JSON
16+
if command -v jq &> /dev/null; then
17+
varlinkctl call "$SOCKET/$INTERFACE.GetDiagnostics" | jq .
18+
else
19+
varlinkctl call "$SOCKET/$INTERFACE.GetDiagnostics"
20+
fi

0 commit comments

Comments
 (0)