initial
This commit is contained in:
commit
6c60003ceb
9 changed files with 676 additions and 0 deletions
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
build
|
||||
cmake-build-debug
|
||||
.idea
|
||||
53
kde-app/JBLQunatumStatus/CMakeLists.txt
Normal file
53
kde-app/JBLQunatumStatus/CMakeLists.txt
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
cmake_minimum_required(VERSION 3.20)
|
||||
|
||||
project(helloworld)
|
||||
|
||||
set(QT_MIN_VERSION "6.6.0")
|
||||
set(KF_MIN_VERSION "6.0.0")
|
||||
|
||||
find_package(ECM ${KF_MIN_VERSION} REQUIRED NO_MODULE)
|
||||
set(CMAKE_MODULE_PATH ${ECM_MODULE_PATH} ${CMAKE_CURRENT_SOURCE_DIR}/cmake)
|
||||
|
||||
include(KDEInstallDirs)
|
||||
include(KDECMakeSettings)
|
||||
include(KDECompilerSettings NO_POLICY_SCOPE)
|
||||
include(FeatureSummary)
|
||||
|
||||
find_package(Qt6 ${QT_MIN_VERSION} CONFIG REQUIRED COMPONENTS
|
||||
Core # QCommandLineParser, QStringLiteral
|
||||
Widgets # QApplication
|
||||
Gui
|
||||
Quick
|
||||
)
|
||||
|
||||
find_package(KF6 ${KF_MIN_VERSION} REQUIRED COMPONENTS
|
||||
CoreAddons # KAboutData
|
||||
I18n # KLocalizedString
|
||||
WidgetsAddons # KMessageBox
|
||||
)
|
||||
|
||||
find_package(KF6StatusNotifierItem)
|
||||
|
||||
|
||||
add_executable(helloworld
|
||||
netlinkmonitor.h
|
||||
netlinkmonitor.cpp)
|
||||
|
||||
target_sources(helloworld
|
||||
PRIVATE
|
||||
main.cpp
|
||||
)
|
||||
|
||||
target_link_libraries(helloworld
|
||||
Qt6::Widgets
|
||||
Qt6::Core
|
||||
KF6::CoreAddons
|
||||
KF6::I18n
|
||||
KF6::WidgetsAddons
|
||||
Qt6::Quick
|
||||
KF6::StatusNotifierItem
|
||||
)
|
||||
|
||||
install(TARGETS helloworld ${KDE_INSTALL_TARGETS_DEFAULT_ARGS})
|
||||
|
||||
feature_summary(WHAT ALL INCLUDE_QUIET_PACKAGES FATAL_ON_MISSING_REQUIRED_PACKAGES)
|
||||
106
kde-app/JBLQunatumStatus/main.cpp
Normal file
106
kde-app/JBLQunatumStatus/main.cpp
Normal file
|
|
@ -0,0 +1,106 @@
|
|||
|
||||
#include <QApplication>
|
||||
#include <QTimer>
|
||||
#include <QFile>
|
||||
#include <QTextStream>
|
||||
#include <QDebug>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <sys/inotify.h>
|
||||
#include <unistd.h>
|
||||
#include <errno.h>
|
||||
#include "netlinkmonitor.h"
|
||||
|
||||
// KStatusNotifierItem from KF6::Notifications
|
||||
#include <KStatusNotifierItem>
|
||||
#include <KLocalizedString> // if you want i18n (need KF6I18n)
|
||||
#define BUF_LEN (10 * (sizeof(struct inotify_event) + NAME_MAX + 1))
|
||||
|
||||
|
||||
static QString readSysfsFile(const QString &path)
|
||||
{
|
||||
QFile file(path);
|
||||
if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
|
||||
qWarning() << "Cannot open sysfs:" << path;
|
||||
return QString();
|
||||
}
|
||||
QTextStream in(&file);
|
||||
return in.readLine().trimmed();
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
// Use QApplication because we need a GUI event loop for system tray icons
|
||||
QApplication app(argc, argv);
|
||||
|
||||
// 1) Create our tray icon via KStatusNotifierItem
|
||||
KStatusNotifierItem *trayIcon = new KStatusNotifierItem;
|
||||
trayIcon->setTitle(QStringLiteral("JBL Quantum 610"));
|
||||
trayIcon->setIconByName(QStringLiteral("audio-headset"));
|
||||
// (You can also set a custom icon path with setIconByPixmap)
|
||||
|
||||
// 2) Set category & status
|
||||
trayIcon->setCategory(KStatusNotifierItem::Hardware);
|
||||
trayIcon->setStatus(KStatusNotifierItem::Active);
|
||||
|
||||
// 3) Function to update the tray tooltip with sysfs data
|
||||
auto updateTray = [trayIcon]() {
|
||||
// Example sysfs paths: adjust to your kernel module's actual files
|
||||
QString batteryStr = readSysfsFile(QStringLiteral("/sys/class/power_supply/JBL_Quantum_610/capacity"));
|
||||
QString micMuteStr = readSysfsFile(QStringLiteral("/sys/bus/hid/devices/0003:0ECB:205C.0007/mic_mute"));
|
||||
|
||||
if (batteryStr.isEmpty()) {
|
||||
batteryStr = QStringLiteral("?");
|
||||
}
|
||||
bool isMuted = (micMuteStr.trimmed() == QLatin1String("1"));
|
||||
|
||||
// Compose tooltip title and subtitle
|
||||
QString tooltipTitle = QStringLiteral("JBL Quantum 610");
|
||||
QString tooltipSubTitle =
|
||||
QStringLiteral("Battery: %1%\nMic: %2")
|
||||
.arg(batteryStr)
|
||||
.arg(isMuted ? QStringLiteral("Muted") : QStringLiteral("Unmuted"));
|
||||
|
||||
trayIcon->setToolTipTitle(tooltipTitle);
|
||||
trayIcon->setToolTipSubTitle(tooltipSubTitle);
|
||||
|
||||
// Optionally change the icon depending on battery level / mute
|
||||
// For example:
|
||||
int batteryVal = batteryStr.toInt();
|
||||
if (batteryVal < 20) {
|
||||
trayIcon->setIconByName(QStringLiteral("battery-caution"));
|
||||
} else {
|
||||
trayIcon->setIconByName(QStringLiteral("audio-headset"));
|
||||
}
|
||||
};
|
||||
// 4) QTimer to update every 10 seconds (example)
|
||||
QTimer timer;
|
||||
//QObject::connect(&timer, &QTimer::timeout, updateTray);
|
||||
//timer.start(10000);
|
||||
NetlinkMonitor netlinkMonitor;
|
||||
if (!netlinkMonitor.setupNetlink()) {
|
||||
qWarning("Failed to setup netlink socket. Exiting...");
|
||||
return -1;
|
||||
}
|
||||
updateTray();
|
||||
|
||||
QObject::connect(&netlinkMonitor, &NetlinkMonitor::messageReceived,
|
||||
[trayIcon](const QString &msg) {
|
||||
printf("%s\n", msg.toStdString().c_str());
|
||||
if (msg == QStringLiteral("JBL610_DEVICE_MUTED")) {
|
||||
printf("MUTED");
|
||||
trayIcon->setIconByName(QStringLiteral("audio-headphones"));
|
||||
} else {
|
||||
trayIcon->setIconByName(QStringLiteral("audio-headset"));
|
||||
printf("UNMUTED");
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
|
||||
// Call once at startup
|
||||
|
||||
|
||||
// 5) Show the tray icon (KStatusNotifierItem auto-shows if the desktop supports it)
|
||||
return app.exec();
|
||||
}
|
||||
111
kde-app/JBLQunatumStatus/netlinkmonitor.cpp
Normal file
111
kde-app/JBLQunatumStatus/netlinkmonitor.cpp
Normal file
|
|
@ -0,0 +1,111 @@
|
|||
#include "netlinkmonitor.h"
|
||||
#include <QDebug>
|
||||
#include <unistd.h>
|
||||
#include <errno.h>
|
||||
#include <string.h>
|
||||
#include <sys/socket.h>
|
||||
#include <linux/netlink.h>
|
||||
|
||||
#define NETLINK_MYPROTO NETLINK_USERSOCK // Matches your kernel module, or use NETLINK_USERSOCK
|
||||
#define MY_NETLINK_GROUP 27 // Multicast group your kernel module broadcasts on
|
||||
#define MAX_PAYLOAD 1024
|
||||
|
||||
NetlinkMonitor::NetlinkMonitor(QObject *parent)
|
||||
: QObject(parent)
|
||||
, m_sockFd(-1)
|
||||
, m_socketNotifier(nullptr)
|
||||
{
|
||||
}
|
||||
|
||||
NetlinkMonitor::~NetlinkMonitor()
|
||||
{
|
||||
if (m_socketNotifier) {
|
||||
delete m_socketNotifier;
|
||||
m_socketNotifier = nullptr;
|
||||
}
|
||||
if (m_sockFd != -1) {
|
||||
close(m_sockFd);
|
||||
m_sockFd = -1;
|
||||
}
|
||||
}
|
||||
|
||||
bool NetlinkMonitor::setupNetlink()
|
||||
{
|
||||
// 1. Create netlink socket
|
||||
m_sockFd = socket(AF_NETLINK, SOCK_RAW, NETLINK_MYPROTO);
|
||||
if (m_sockFd < 0) {
|
||||
qWarning() << "Failed to create netlink socket:" << strerror(errno);
|
||||
return false;
|
||||
}
|
||||
|
||||
// 2. Bind to a local address with the desired groups
|
||||
struct sockaddr_nl addr;
|
||||
memset(&addr, 0, sizeof(addr));
|
||||
addr.nl_family = AF_NETLINK;
|
||||
addr.nl_pid = getpid(); // our user-space PID
|
||||
// Subscribe to the group (bitmask). For group #1, you set bit (1-1).
|
||||
addr.nl_groups = 1 << (MY_NETLINK_GROUP - 1);
|
||||
|
||||
if (bind(m_sockFd, reinterpret_cast<struct sockaddr*>(&addr), sizeof(addr)) < 0) {
|
||||
qWarning() << "Failed to bind netlink socket:" << strerror(errno);
|
||||
close(m_sockFd);
|
||||
m_sockFd = -1;
|
||||
return false;
|
||||
}
|
||||
|
||||
// 3. Create a QSocketNotifier to watch for incoming data
|
||||
m_socketNotifier = new QSocketNotifier(m_sockFd, QSocketNotifier::Read, this);
|
||||
connect(m_socketNotifier, &QSocketNotifier::activated,
|
||||
this, &NetlinkMonitor::handleReadyRead);
|
||||
|
||||
qDebug() << "NetlinkMonitor: socket setup complete, fd =" << m_sockFd;
|
||||
return true;
|
||||
}
|
||||
|
||||
void NetlinkMonitor::handleReadyRead()
|
||||
{
|
||||
// This slot is triggered whenever the netlink socket has data to read.
|
||||
char buffer[MAX_PAYLOAD];
|
||||
struct iovec iov = {
|
||||
.iov_base = buffer,
|
||||
.iov_len = sizeof(buffer)
|
||||
};
|
||||
struct sockaddr_nl srcAddr;
|
||||
struct msghdr msg;
|
||||
struct nlmsghdr *nlh;
|
||||
|
||||
memset(&srcAddr, 0, sizeof(srcAddr));
|
||||
memset(&msg, 0, sizeof(msg));
|
||||
|
||||
msg.msg_name = &srcAddr;
|
||||
msg.msg_namelen = sizeof(srcAddr);
|
||||
msg.msg_iov = &iov;
|
||||
msg.msg_iovlen = 1;
|
||||
|
||||
ssize_t ret = recvmsg(m_sockFd, &msg, 0);
|
||||
if (ret < 0) {
|
||||
qWarning() << "recvmsg() failed:" << strerror(errno);
|
||||
return;
|
||||
}
|
||||
|
||||
// The netlink message is in the buffer as one or more nlmsghdr blocks.
|
||||
for (nlh = (struct nlmsghdr *)buffer; NLMSG_OK(nlh, ret);
|
||||
nlh = NLMSG_NEXT(nlh, ret)) {
|
||||
|
||||
if (nlh->nlmsg_type == NLMSG_DONE) {
|
||||
// The actual payload is after the nlmsghdr
|
||||
char *payload = (char *)NLMSG_DATA(nlh);
|
||||
// Convert to QString, then emit a signal
|
||||
QString msgStr = QString::fromLatin1(payload);
|
||||
messageReceived(msgStr);
|
||||
printf("%s\n", msgStr.toStdString().c_str());
|
||||
}
|
||||
else if (nlh->nlmsg_type == NLMSG_ERROR) {
|
||||
qWarning() << "Received netlink error message";
|
||||
}
|
||||
else {
|
||||
// In a real app, handle other message types as needed
|
||||
qDebug() << "Received netlink type:" << nlh->nlmsg_type;
|
||||
}
|
||||
}
|
||||
}
|
||||
31
kde-app/JBLQunatumStatus/netlinkmonitor.h
Normal file
31
kde-app/JBLQunatumStatus/netlinkmonitor.h
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
#ifndef NETLINKMONITOR_H
|
||||
#define NETLINKMONITOR_H
|
||||
|
||||
#include <QObject>
|
||||
#include <QSocketNotifier>
|
||||
|
||||
class NetlinkMonitor : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit NetlinkMonitor(QObject *parent = nullptr);
|
||||
~NetlinkMonitor();
|
||||
|
||||
// Call this to open and configure the netlink socket.
|
||||
// Returns true on success, false on failure.
|
||||
Q_INVOKABLE bool setupNetlink();
|
||||
|
||||
Q_SIGNALS:
|
||||
// Emitted whenever a new netlink message arrives.
|
||||
void messageReceived(const QString &msg);
|
||||
|
||||
private Q_SLOTS:
|
||||
// Called automatically by QSocketNotifier when netlink data is available.
|
||||
void handleReadyRead();
|
||||
|
||||
private:
|
||||
int m_sockFd; // The netlink socket file descriptor
|
||||
QSocketNotifier *m_socketNotifier;
|
||||
};
|
||||
|
||||
#endif // NETLINKMONITOR_H
|
||||
15
kernel/Makefile
Normal file
15
kernel/Makefile
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
obj-m += jbl_headset_battery.o
|
||||
|
||||
all:
|
||||
make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules
|
||||
clean:
|
||||
make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean
|
||||
install:
|
||||
mkdir -p /lib/modules/$(shell uname -r)/extra
|
||||
cp jbl_headset_battery.ko /lib/modules/$(shell uname -r)/extra/
|
||||
depmod -a
|
||||
installdkms:
|
||||
mkdir -p /usr/src/jbl-headset-0.1/
|
||||
cp jbl_headset_battery.c /usr/src/jbl-headset-0.1/
|
||||
cp Makefile /usr/src/jbl-headset-0.1/
|
||||
cp dkms.conf /usr/src/jbl-headset-0.1
|
||||
6
kernel/dkms.conf
Normal file
6
kernel/dkms.conf
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
MAKE="make"
|
||||
CLEAN="make clean"
|
||||
BUILT_MODULE_NAME="jbl_headset_battery"
|
||||
DEST_MODULE_LOCATION="/extra"
|
||||
PACKAGE_NAME="jbl-headset"
|
||||
PACKAGE_VERSION="0.1"
|
||||
282
kernel/jbl_headset_battery.c
Normal file
282
kernel/jbl_headset_battery.c
Normal file
|
|
@ -0,0 +1,282 @@
|
|||
#include <linux/module.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/hid.h>
|
||||
#include <linux/power_supply.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/netlink.h>
|
||||
#include <linux/skbuff.h>
|
||||
#include <net/sock.h>
|
||||
#include <linux/kernel.h>
|
||||
|
||||
#define JBL_VID 0x0ecb
|
||||
#define JBL_PID 0x205c
|
||||
#define MY_NETLINK_GROUP 27
|
||||
//#define NETLINK_MYPROTO 31
|
||||
|
||||
static struct sock *nl_sk = NULL;
|
||||
|
||||
/* List of HID devices this driver supports */
|
||||
static const struct hid_device_id jbl_hid_table[] = {
|
||||
{ HID_USB_DEVICE(JBL_VID, JBL_PID) },
|
||||
{ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(hid, jbl_hid_table);
|
||||
|
||||
|
||||
static void jbl_netlink_broadcast(const char *msg) {
|
||||
struct sk_buff *skb_out;
|
||||
struct nlmsghdr *nlh;
|
||||
int msg_size = strlen(msg);
|
||||
int res;
|
||||
|
||||
if (!nl_sk) {
|
||||
pr_err("jbl610: netlink socket not ready!\n");
|
||||
return;
|
||||
}
|
||||
|
||||
skb_out = nlmsg_new(msg_size, GFP_KERNEL);
|
||||
if (!skb_out) {
|
||||
pr_err("jbl610: Failed to allocate skb\n");
|
||||
return;
|
||||
}
|
||||
|
||||
nlh = nlmsg_put(skb_out, 0, 0, NLMSG_DONE, msg_size, 0);
|
||||
if (!nlh) {
|
||||
pr_err("jbl610: nlmsg_put() failed\n");
|
||||
kfree_skb(skb_out);
|
||||
return;
|
||||
}
|
||||
|
||||
memcpy(nlmsg_data(nlh), msg, msg_size);
|
||||
res = netlink_broadcast(nl_sk, skb_out, 0, MY_NETLINK_GROUP, GFP_KERNEL);
|
||||
//res = netlink_unicast(nl_sk, skb_out, 0, MY_NETLINK_GROUP, GFP_KERNEL);
|
||||
if (res < 0) {
|
||||
pr_err("jbl610: netlink_broadcast error: %d\n", res);
|
||||
} else {
|
||||
pr_info("jbl610: broadcasted message: \"%s\"\n", msg);
|
||||
}
|
||||
}
|
||||
|
||||
struct jbl_headset_data {
|
||||
struct hid_device *hdev;
|
||||
struct power_supply *battery;
|
||||
|
||||
int capacity;
|
||||
|
||||
int status;
|
||||
int present;
|
||||
int muted;
|
||||
};
|
||||
|
||||
static enum power_supply_property jbl_battery_props[] = {
|
||||
POWER_SUPPLY_PROP_CAPACITY,
|
||||
POWER_SUPPLY_PROP_STATUS,
|
||||
POWER_SUPPLY_PROP_MODEL_NAME,
|
||||
POWER_SUPPLY_PROP_MANUFACTURER,
|
||||
POWER_SUPPLY_PROP_PRESENT
|
||||
};
|
||||
|
||||
static int jbl_battery_get_property(
|
||||
struct power_supply *psy,
|
||||
enum power_supply_property psp,
|
||||
union power_supply_propval *val)
|
||||
{
|
||||
struct jbl_headset_data *jbl = power_supply_get_drvdata(psy);
|
||||
|
||||
switch (psp) {
|
||||
case POWER_SUPPLY_PROP_CAPACITY:
|
||||
val->intval = jbl->capacity; // e.g. 0..100
|
||||
return 0;
|
||||
case POWER_SUPPLY_PROP_STATUS:
|
||||
|
||||
val->intval = jbl->status;
|
||||
return 0;
|
||||
case POWER_SUPPLY_PROP_MODEL_NAME:
|
||||
val->strval = "JBL Quantum 610";
|
||||
return 0;
|
||||
case POWER_SUPPLY_PROP_MANUFACTURER:
|
||||
val->strval = "JBL";
|
||||
return 0;
|
||||
case POWER_SUPPLY_PROP_PRESENT:
|
||||
val->intval = jbl->present;
|
||||
return 0;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
}
|
||||
|
||||
static const struct power_supply_desc jbl_battery_desc = {
|
||||
.name = "JBL_Quantum_610",
|
||||
.type = POWER_SUPPLY_TYPE_WIRELESS,
|
||||
.properties = jbl_battery_props,
|
||||
.num_properties = ARRAY_SIZE(jbl_battery_props),
|
||||
.get_property = jbl_battery_get_property,
|
||||
};
|
||||
|
||||
static int jbl_raw_event(struct hid_device *hdev, struct hid_report *report,
|
||||
u8 *data, int size)
|
||||
{
|
||||
struct jbl_headset_data *jbl = hid_get_drvdata(hdev);
|
||||
|
||||
if (size > 1 && data[0] == 0x08) {
|
||||
|
||||
jbl->present = true; // if we have any reports from device, that's mean that it is now connected
|
||||
int new_cap = data[1];
|
||||
if (new_cap != jbl->capacity) {
|
||||
dev_info(&hdev->dev, "Capacity changed, old value: %d, new: %d", jbl->capacity, new_cap);
|
||||
if(new_cap > jbl->capacity) {
|
||||
jbl->status = POWER_SUPPLY_STATUS_CHARGING;
|
||||
} else {
|
||||
jbl->status = POWER_SUPPLY_STATUS_DISCHARGING;
|
||||
}
|
||||
|
||||
|
||||
if(new_cap == 100) {
|
||||
jbl->status = POWER_SUPPLY_STATUS_FULL;
|
||||
}
|
||||
char buffer[30];
|
||||
sprintf(buffer, "JBL610_BATTERY=%d", new_cap);
|
||||
jbl_netlink_broadcast(buffer);
|
||||
|
||||
jbl->capacity = new_cap;
|
||||
power_supply_changed(jbl->battery);
|
||||
}
|
||||
}
|
||||
|
||||
// device disconnected
|
||||
if(size > 1 && data[0] == 0x09) {
|
||||
jbl->present = 0;
|
||||
}
|
||||
|
||||
// device connected
|
||||
if(size > 1 && data[0] == 0x07 && data[1] == 0x01) {
|
||||
jbl->present = 1;
|
||||
}
|
||||
|
||||
if(size > 1 && data[0] == 0x06) {
|
||||
int muted = data[1] == 0 ? 1 : 0;
|
||||
char buf[20];
|
||||
if(muted) {
|
||||
sprintf(buf, "JBL610_DEVICE_MUTED");
|
||||
} else {
|
||||
sprintf(buf, "JBL610_DEVICE_UNMUTED");
|
||||
}
|
||||
jbl_netlink_broadcast(buf);
|
||||
|
||||
jbl->muted = muted;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static ssize_t mic_mute_show(struct device *dev,
|
||||
struct device_attribute *attr,
|
||||
char *buf)
|
||||
{
|
||||
struct jbl_headset_data *jbl = dev_get_drvdata(dev);
|
||||
return sprintf(buf, "%d\n", jbl->muted ? 1 : 0);
|
||||
}
|
||||
|
||||
|
||||
static DEVICE_ATTR_RO(mic_mute); // read-only attribute, for example
|
||||
|
||||
static int jbl_probe(struct hid_device *hdev, const struct hid_device_id *id)
|
||||
{
|
||||
int ret;
|
||||
struct jbl_headset_data *jbl;
|
||||
struct power_supply_config psy_cfg = { };
|
||||
|
||||
/* Allocate memory for our driver data */
|
||||
jbl = devm_kzalloc(&hdev->dev, sizeof(*jbl), GFP_KERNEL);
|
||||
if (!jbl)
|
||||
return -ENOMEM;
|
||||
|
||||
jbl->hdev = hdev;
|
||||
hid_set_drvdata(hdev, jbl);
|
||||
|
||||
/* Register with the HID subsystem */
|
||||
ret = hid_parse(hdev);
|
||||
if (ret) {
|
||||
dev_err(&hdev->dev, "hid_parse failed\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT);
|
||||
if (ret) {
|
||||
dev_err(&hdev->dev, "hid_hw_start failed\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Setup power_supply */
|
||||
psy_cfg.drv_data = jbl; // we can retrieve jbl in get_property
|
||||
jbl->capacity = 50; // default (unknown) until we get a real reading
|
||||
jbl->status = POWER_SUPPLY_STATUS_DISCHARGING;
|
||||
|
||||
jbl->battery = devm_power_supply_register(&hdev->dev,
|
||||
&jbl_battery_desc,
|
||||
&psy_cfg);
|
||||
if (IS_ERR(jbl->battery)) {
|
||||
dev_err(&hdev->dev, "Failed to register power supply\n");
|
||||
hid_hw_stop(hdev);
|
||||
return PTR_ERR(jbl->battery);
|
||||
}
|
||||
|
||||
ret = device_create_file(&hdev->dev, &dev_attr_mic_mute);
|
||||
if (ret) {
|
||||
dev_err(&hdev->dev, "Failed to create mic_mute attribute\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
struct netlink_kernel_cfg cfg = {
|
||||
.input = NULL, // We do NOT receive from user space
|
||||
.groups = 0,
|
||||
};
|
||||
|
||||
pr_info("jbl610: netlink Initializing...\n");
|
||||
|
||||
nl_sk = netlink_kernel_create(&init_net, NETLINK_USERSOCK, &cfg);
|
||||
//nl_sk =netlink_kernel_create(&init_net, NETLINK_USERSOCK, 1, jbl_nl_receive_callback, NULL, THIS_MODULE)
|
||||
if (!nl_sk) {
|
||||
pr_err("jbl610: Error creating netlink socket.\n");
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
|
||||
/* Example: Send a test broadcast right away (for demonstration). */
|
||||
// jbl_netlink_broadcast("jbl_driver_initialized");
|
||||
|
||||
|
||||
dev_info(&hdev->dev, "JBL headset driver probed\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void jbl_nl_receive_callback(struct sk_buff *skb) {
|
||||
nlmsg_free(skb);
|
||||
}
|
||||
|
||||
/* Called when the driver is unbound or device is removed */
|
||||
static void jbl_remove(struct hid_device *hdev)
|
||||
{
|
||||
if (nl_sk) {
|
||||
netlink_kernel_release(nl_sk);
|
||||
nl_sk = NULL;
|
||||
}
|
||||
pr_info("jbl610: Exiting.\n");
|
||||
device_remove_file(&hdev->dev, &dev_attr_mic_mute);
|
||||
hid_hw_stop(hdev);
|
||||
/* power_supply is auto-freed by devm_ once the device is gone */
|
||||
}
|
||||
|
||||
static struct hid_driver jbl_driver = {
|
||||
.name = "jbl_headset",
|
||||
.id_table = jbl_hid_table,
|
||||
.probe = jbl_probe,
|
||||
.remove = jbl_remove,
|
||||
.raw_event = jbl_raw_event, // We'll get HID input packets here
|
||||
};
|
||||
|
||||
module_hid_driver(jbl_driver);
|
||||
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_AUTHOR("Kirill Ivlev <webster.spb@gmail.com>");
|
||||
MODULE_DESCRIPTION("Kernel driver for JBL Headset battery");
|
||||
69
kernel/netlinktest.c
Normal file
69
kernel/netlinktest.c
Normal file
|
|
@ -0,0 +1,69 @@
|
|||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/socket.h>
|
||||
#include <linux/netlink.h>
|
||||
|
||||
#define MAX_PAYLOAD 1024
|
||||
#define MYPROTO NETLINK_USERSOCK
|
||||
#define MY_GROUP 27
|
||||
|
||||
|
||||
int main(void)
|
||||
{
|
||||
int sock_fd;
|
||||
struct sockaddr_nl addr;
|
||||
struct nlmsghdr *nlh;
|
||||
struct iovec iov;
|
||||
struct msghdr msg;
|
||||
char buffer[MAX_PAYLOAD];
|
||||
|
||||
|
||||
/* 1. Create netlink socket */
|
||||
sock_fd = socket(AF_NETLINK, SOCK_RAW, NETLINK_USERSOCK);
|
||||
if (sock_fd < 0) {
|
||||
perror("socket");
|
||||
return -1;
|
||||
}
|
||||
|
||||
memset(&addr, 0, sizeof(addr));
|
||||
addr.nl_family = AF_NETLINK;
|
||||
addr.nl_pid = getpid();
|
||||
addr.nl_groups = 1 << (MY_GROUP - 1);
|
||||
|
||||
// (Group is 1-based, sets bit (group-1) for subscription)
|
||||
|
||||
/* 2. Bind the socket */
|
||||
if (bind(sock_fd, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
|
||||
perror("bind");
|
||||
close(sock_fd);
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
||||
while (1) {
|
||||
/* 3. Prepare to receive messages in a loop */
|
||||
memset(buffer, 0, sizeof(buffer));
|
||||
nlh = (struct nlmsghdr *)buffer;
|
||||
iov.iov_base = (void *)nlh;
|
||||
iov.iov_len = MAX_PAYLOAD;
|
||||
|
||||
memset(&msg, 0, sizeof(msg));
|
||||
msg.msg_name = (void *)&addr;
|
||||
msg.msg_namelen = sizeof(addr);
|
||||
msg.msg_iov = &iov;
|
||||
msg.msg_iovlen = 1;
|
||||
|
||||
printf("Waiting for kernel broadcast...\n");
|
||||
if (recvmsg(sock_fd, &msg, 0) < 0) {
|
||||
perror("recvmsg");
|
||||
break;
|
||||
}
|
||||
|
||||
printf("Received kernel message: \"%s\"\n", (char *)NLMSG_DATA(nlh));
|
||||
}
|
||||
|
||||
close(sock_fd);
|
||||
return 0;
|
||||
}
|
||||
Loading…
Reference in a new issue