38 Commits

Author SHA1 Message Date
p d48653a270 CMakeLists.txt: improve crosscompilation
Alpine 3.24 Success
It shouldn't have worked.  But it did anyway.
2026-08-21 13:10:40 +02:00
p 0f22f385fd Win64Depends.sh: move to supported ucrt64
Alpine 3.24 Success
2026-08-21 12:17:07 +02:00
p dfcab40ca4 eizoctl: indicate USB-C properly on input switch
Alpine 3.24 Success
2026-07-23 10:52:12 +02:00
p 0a1cf529de elksmart-comm: improve compression
Alpine 3.24 Success
The NEC encoding has the same count of 1s and 0s.
2026-07-22 13:44:40 +02:00
p 7fc34885a7 elksmart-comm: add support for more devices
Mainly a particularly overheating variant of EKX5S-T.
2026-07-22 13:44:40 +02:00
p 5a5409c4a0 elksmart-comm: dis/claim USB interface correctly 2026-07-22 13:08:33 +02:00
p fa4bfd80df Update README.adoc
Alpine 3.23 Success
2026-07-20 04:17:05 +02:00
p 2b9d655656 upscat: fix device filtering
Alpine 3.23 Success
Static allocation is a gift that keeps on giving.

Windows should now work fine in the non-Input polling mode.
2026-07-07 14:25:07 +02:00
p 74af8e884c Update README.adoc, upscat touch-ups
Alpine 3.23 Success
The tool only works on Windows with my CyberPower UPS.
2026-07-07 13:49:26 +02:00
p 8b5aff8081 upscat: touch-ups
Alpine 3.23 Success
2026-07-06 20:58:56 +02:00
p f2e586906f upscat: touch-ups
Alpine 3.23 Scripts failed
2026-07-06 20:53:33 +02:00
p 2c30ddfa3d Add upscat
Alpine 3.23 Scripts failed
This is a simple utility to track UPS status.
2026-07-06 20:45:17 +02:00
p 97b86c168e eizoctl: fix error reporting 2026-07-06 19:23:46 +02:00
p 41e928da52 eizoctl: handle usage ranges better 2026-07-06 09:16:16 +02:00
p ae195bf855 eizoctl: quote formatted strings 2026-07-06 07:42:13 +02:00
p 5035750647 elksmart-comm: detach any kernel driver
Alpine 3.23 Success
2026-03-07 07:26:52 +01:00
p 8eb738b093 Bump version
Alpine 3.23 Success
macOS Success
2026-01-04 15:01:19 +01:00
p c6f47a0981 CMakeLists.txt: improve searching for hidapi
Alpine 3.21 Success
Debian-based distributions don't include the CMake module.
2025-08-02 18:55:44 +02:00
p c8f3b9ba38 eizoctl: fix compatibility with newer MinGW-w64
Alpine 3.21 Success
2025-08-02 17:47:40 +02:00
p d8b01cdaee eizoctl: add an option to filter by serial number
Alpine 3.21 Success
It turns out that this is actually critical when switching inputs
in certain wild setups.
2025-07-31 21:16:16 +02:00
p 135f336a7c eizoctl: add an option to list monitors 2025-07-31 21:15:26 +02:00
p b4c1817c10 Update README.adoc
Alpine 3.20 Success
2025-01-22 13:36:34 +01:00
p 32ea934947 Update README.adoc
Alpine 3.20 Success
2025-01-22 13:25:39 +01:00
p 55984bc7ef Bump copyright years
Alpine 3.20 Success
2025-01-18 20:32:02 +01:00
p dab190e857 eizoctl: fix message formatting on Windows
This increases the binary size, but at least we stop showing
Chinese characters instead of ASCII.
2025-01-18 20:32:02 +01:00
p 2594f8467d eizoctl: also report USB-C from --input '?'
And related cleanup.
2025-01-18 20:32:02 +01:00
p 9039db44f6 eizoctl: make --events work with GUIs
Also add a --quiet option, and expand the program help.
2025-01-18 20:32:01 +01:00
p 89cab6fa39 eizoctl: display non-first numbers correctly
Humans count from 1, not from 0.
2025-01-18 18:57:16 +01:00
p aea9c334e0 Bump liberty, replace help2man with help2adoc
Alpine 3.20 Success
2025-01-01 06:09:49 +01:00
p e53cddb030 Fix up code style adjustments 2024-12-26 12:26:19 +01:00
p 8832ba2227 Improve Windows packaging
macOS Success
Alpine 3.20 Success
2024-12-23 17:14:59 +01:00
p 7bd6993b59 Bump liberty
Alpine 3.20 Success
2024-12-17 06:38:47 +01:00
p 8717f425f4 eizo-pcap-decode.go: update for newer models
Alpine 3.20 Success
2024-11-28 13:29:55 +01:00
p 7e008c154b Bump release
Alpine 3.20 Success
2024-11-28 11:22:43 +01:00
p 9d619115be Port eizoctltray to macOS
Also bump minimum CMake version for hidapi_ROOT,
and don't try to run the help2man Perl script in MSYS2.

AppKit is a very miserable thing.
2024-11-28 11:22:32 +01:00
p 02da76e958 eizo-pcap-decode.go: figure out USBHID
Alpine 3.20 Success
2024-11-27 10:00:08 +01:00
p be50fd4b8f Add an EIZO packet decoding tool
Alpine 3.20 Success
It is fairly dumb, but it allows to make more sense of what is
going on over the wires while using EIZO software.
2024-11-26 11:10:15 +01:00
p 781fd392fe README.adoc: fix a typo
Alpine 3.20 Success
2024-11-26 03:27:09 +01:00
18 changed files with 2947 additions and 167 deletions
+110 -37
View File
@@ -1,5 +1,5 @@
cmake_minimum_required (VERSION 3.10)
project (usb-drivers VERSION 1.0.0
cmake_minimum_required (VERSION 3.12)
project (usb-drivers VERSION 1.2.0
DESCRIPTION "User space USB drivers" LANGUAGES C)
# Moar warnings
@@ -14,10 +14,9 @@ endif ()
if (WIN32 AND CMAKE_CROSSCOMPILING)
set (win32_deps_root "${PROJECT_SOURCE_DIR}")
set (win32_deps_prefix "${win32_deps_root}/mingw64")
set (win32_deps_prefix "${win32_deps_root}/ucrt64")
list (APPEND CMAKE_PREFIX_PATH "${win32_deps_prefix}")
list (APPEND CMAKE_INCLUDE_PATH "${win32_deps_prefix}/lib")
set (CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -mms-bitfields")
list (APPEND CMAKE_FIND_ROOT_PATH ${win32_deps_prefix})
@@ -34,10 +33,25 @@ endif ()
# Dependencies
set (CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/liberty/cmake)
include (IconUtils)
include (CTest)
find_package (PkgConfig REQUIRED)
pkg_check_modules (libusb libusb-1.0)
pkg_search_module (hidapi hidapi hidapi-hidraw hidapi-libusb)
# On MSYS2, the CMake package cannot link statically, but pkg-config can.
# On macOS, we explicitly want to use the CMake package.
if (NOT WIN32)
find_package (hidapi QUIET)
if (hidapi_FOUND)
set (hidapi_INCLUDE_DIRS)
set (hidapi_LIBRARY_DIRS)
set (hidapi_LIBRARIES hidapi::hidapi)
endif ()
endif ()
if (NOT hidapi_FOUND)
pkg_search_module (hidapi hidapi hidapi-hidraw hidapi-libusb)
endif ()
option (WITH_LIBUSB "Compile with libusb-based utilities" ${libusb_FOUND})
option (WITH_HIDAPI "Compile with hidapi-based utilities" ${hidapi_FOUND})
@@ -67,6 +81,18 @@ if (WITH_LIBUSB AND NOT WIN32)
target_link_directories (elksmart-comm PUBLIC ${libusb_LIBRARY_DIRS})
target_link_libraries (elksmart-comm ${libusb_LIBRARIES})
endif ()
if (WITH_LIBUSB AND NOT WIN32 AND BUILD_TESTING)
add_executable (test-elksmart-comm $<TARGET_PROPERTY:elksmart-comm,SOURCES>)
set_target_properties (test-elksmart-comm
PROPERTIES COMPILE_DEFINITIONS TESTING)
target_include_directories (test-elksmart-comm
PUBLIC $<TARGET_PROPERTY:elksmart-comm,INCLUDE_DIRECTORIES>)
target_link_directories (test-elksmart-comm
PUBLIC $<TARGET_PROPERTY:elksmart-comm,LINK_DIRECTORIES>)
target_link_libraries (test-elksmart-comm
PUBLIC $<TARGET_PROPERTY:elksmart-comm,LINK_LIBRARIES>)
add_test (NAME test-elksmart-comm COMMAND test-elksmart-comm)
endif ()
if (WITH_LIBUSB)
list (APPEND targets razer-bw-te-ctl)
add_executable (razer-bw-te-ctl razer-bw-te-ctl.c)
@@ -81,11 +107,29 @@ if (WITH_HIDAPI)
target_include_directories (eizoctl PUBLIC ${hidapi_INCLUDE_DIRS})
target_link_directories (eizoctl PUBLIC ${hidapi_LIBRARY_DIRS})
target_link_libraries (eizoctl ${hidapi_LIBRARIES})
list (APPEND targets upscat)
add_executable (upscat upscat.c)
target_include_directories (upscat PUBLIC ${hidapi_INCLUDE_DIRS})
target_link_directories (upscat PUBLIC ${hidapi_LIBRARY_DIRS})
target_link_libraries (upscat ${hidapi_LIBRARIES})
endif ()
if (WITH_HIDAPI AND BUILD_TESTING)
add_executable (test-upscat $<TARGET_PROPERTY:upscat,SOURCES>)
set_target_properties (test-upscat PROPERTIES COMPILE_DEFINITIONS TESTING)
target_include_directories (test-upscat
PUBLIC $<TARGET_PROPERTY:upscat,INCLUDE_DIRECTORIES>)
target_link_directories (test-upscat
PUBLIC $<TARGET_PROPERTY:upscat,LINK_DIRECTORIES>)
target_link_libraries (test-upscat
PUBLIC $<TARGET_PROPERTY:upscat,LINK_LIBRARIES>)
add_test (NAME test-upscat
COMMAND test-upscat upscat-eaton3p.bin upscat-cyberpower.bin
WORKING_DIRECTORY "${PROJECT_SOURCE_DIR}")
endif ()
if (WITH_HIDAPI AND WIN32)
list (APPEND targets_gui eizoctltray)
include (IconUtils)
set (icon_png_list)
foreach (icon_size 16 32 48)
icon_to_png (eizoctltray ${PROJECT_SOURCE_DIR}/eizoctltray.svg
@@ -97,7 +141,6 @@ if (WITH_HIDAPI AND WIN32)
set (icon_ico ${PROJECT_BINARY_DIR}/eizoctltray.ico)
icon_for_win32 (${icon_ico} "${icon_png_list}" "${icon_png}")
list (APPEND icon_ico_list )
set_property (SOURCE eizoctltray.rc
APPEND PROPERTY OBJECT_DEPENDS ${icon_ico})
@@ -108,47 +151,78 @@ if (WITH_HIDAPI AND WIN32)
target_link_directories (eizoctltray PUBLIC ${hidapi_LIBRARY_DIRS})
target_link_libraries (eizoctltray ${hidapi_LIBRARIES} powrprof)
endif ()
if (WITH_HIDAPI AND APPLE)
list (APPEND targets_gui eizoctltray)
# We override the language for the command line target as well,
# but that doesn't and must not pose any problems.
enable_language (OBJC)
set_source_files_properties (eizoctl.c PROPERTIES LANGUAGE OBJC)
set (MACOSX_BUNDLE_GUI_IDENTIFIER name.janouch.eizoctltray)
set (MACOSX_BUNDLE_ICON_FILE eizoctltray.icns)
icon_to_icns (${PROJECT_SOURCE_DIR}/eizoctltray.svg
"${MACOSX_BUNDLE_ICON_FILE}" icon)
add_executable (eizoctltray MACOSX_BUNDLE eizoctl.c "${icon}")
target_compile_definitions (eizoctltray PUBLIC -DTRAY)
target_compile_options (eizoctltray PUBLIC -fobjc-arc)
target_link_libraries (eizoctltray ${hidapi_LIBRARIES} "-framework Cocoa")
endif ()
# Generate documentation from help output
if (NOT CMAKE_CROSSCOMPILING)
find_program (HELP2MAN_EXECUTABLE help2man)
if (NOT HELP2MAN_EXECUTABLE)
message (FATAL_ERROR "help2man not found")
endif ()
if (NOT WIN32 AND NOT CMAKE_CROSSCOMPILING)
set (HELP2ADOC "${PROJECT_SOURCE_DIR}/liberty/tools/help2adoc.awk")
set (ASCIIMAN "${PROJECT_SOURCE_DIR}/liberty/tools/asciiman.awk")
foreach (target ${targets})
set (page_output "${PROJECT_BINARY_DIR}/${target}.1")
list (APPEND project_MAN_PAGES "${page_output}")
add_custom_command (OUTPUT ${page_output}
COMMAND ${HELP2MAN_EXECUTABLE} -N
"${PROJECT_BINARY_DIR}/${target}" -o ${page_output}
DEPENDS ${target}
COMMENT "Generating man page for ${target}" VERBATIM)
set (page_adoc "${PROJECT_BINARY_DIR}/${target}.1.adoc")
set (page_roff "${PROJECT_BINARY_DIR}/${target}.1")
list (APPEND project_MAN_PAGES "${page_roff}")
# $<TARGET_FILE:tgt> could be used, if we didn't have to escape it.
string (REPLACE "\\" "\\\\"
target_path "${PROJECT_BINARY_DIR}/${target}")
add_custom_command (OUTPUT "${page_adoc}"
COMMAND env LC_ALL=C awk -f "${HELP2ADOC}"
-v "Target=${target_path}" > "${page_adoc}"
DEPENDS "${target}" "${HELP2ADOC}"
COMMENT "Generating AsciiDoc man page for ${target}" VERBATIM)
add_custom_command (OUTPUT "${page_roff}"
COMMAND env LC_ALL=C awk -f "${ASCIIMAN}"
"${page_adoc}" > "${page_roff}"
DEPENDS "${page_adoc}" "${ASCIIMAN}"
COMMENT "Generating roff man page for ${target}" VERBATIM)
endforeach ()
add_custom_target (docs ALL DEPENDS ${project_MAN_PAGES})
endif ()
# The files to be installed
include (GNUInstallDirs)
if (NOT WIN32)
include (GNUInstallDirs)
# These should be accessible by users, but need to touch system devices.
# Use the setuid bit, for simplicity.
set (SETUID "SETUID" CACHE STRING "Set this empty on permission issues")
install (TARGETS ${targets} DESTINATION ${CMAKE_INSTALL_BINDIR}
PERMISSIONS
OWNER_WRITE OWNER_READ OWNER_EXECUTE
GROUP_READ GROUP_EXECUTE
WORLD_READ WORLD_EXECUTE
${SETUID})
install (TARGETS ${targets_gui} DESTINATION ${CMAKE_INSTALL_BINDIR})
install (FILES LICENSE DESTINATION ${CMAKE_INSTALL_DOCDIR})
# These should be accessible by users, but need to touch system devices.
# Use the setuid bit, for simplicity.
set (SETUID "SETUID" CACHE STRING "Set this empty on permission issues")
install (TARGETS ${targets} DESTINATION ${CMAKE_INSTALL_BINDIR}
PERMISSIONS
OWNER_WRITE OWNER_READ OWNER_EXECUTE
GROUP_READ GROUP_EXECUTE
WORLD_READ WORLD_EXECUTE
${SETUID})
install (TARGETS ${targets_gui} DESTINATION ${CMAKE_INSTALL_BINDIR})
install (FILES LICENSE DESTINATION ${CMAKE_INSTALL_DOCDIR})
foreach (page ${project_MAN_PAGES})
string (REGEX MATCH "\\.([0-9])$" manpage_suffix "${page}")
install (FILES "${page}"
DESTINATION "${CMAKE_INSTALL_MANDIR}/man${CMAKE_MATCH_1}")
endforeach ()
foreach (page ${project_MAN_PAGES})
string (REGEX MATCH "\\.([0-9])$" manpage_suffix "${page}")
install (FILES "${page}"
DESTINATION "${CMAKE_INSTALL_MANDIR}/man${CMAKE_MATCH_1}")
endforeach ()
set (CPACK_SET_DESTDIR TRUE)
else ()
install (TARGETS ${targets} ${targets_gui} DESTINATION .)
endif ()
# CPack
set (CPACK_PACKAGE_VENDOR "Premysl Eric Janouch")
@@ -162,5 +236,4 @@ set (CPACK_SOURCE_GENERATOR "TGZ;ZIP")
set (CPACK_SOURCE_IGNORE_FILES "/\\\\.git;/build;/CMakeLists.txt.user")
set (CPACK_SOURCE_PACKAGE_FILE_NAME "${PROJECT_NAME}-${PROJECT_VERSION}")
set (CPACK_SET_DESTDIR TRUE)
include (CPack)
+1 -1
View File
@@ -1,4 +1,4 @@
Copyright (c) 2013, 2024, Přemysl Eric Janouch <p@janouch.name>
Copyright (c) 2013, 2024 - 2026, Přemysl Eric Janouch <p@janouch.name>
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted.
+25
View File
@@ -0,0 +1,25 @@
Unreleased
* Added upscat for streaming status from UPS devices
1.2.0 (2026-01-04)
* eizoctl: added a --quiet option to suppress information and/or errors
* eizoctl: added a --list option to list connected monitors
* eizoctl: added a --serial option to only act on particular monitors
* eizoctl: fixed input port reporting
1.1.0 (2024-11-28)
* Ported eizoctltray to macOS as well
1.0.0 (2024-11-26)
* Initial release
+112 -6
View File
@@ -1,6 +1,7 @@
USB drivers
===========
:compact-option:
:source-highlighter: chroma
_usb-drivers_ is a collection of utilities to control various hardware over USB.
@@ -19,11 +20,66 @@ and may not run at the same time, as it would contend for device access.
eizoctltray
~~~~~~~~~~~
_eizoctltray_ is a derived Windows utility that can stay in the systray.
When holding the Shift or Control keys while switching singnal inputs,
_eizoctltray_ is a derived Windows/macOS utility that can stay in the systray.
When holding the Shift or Control keys while switching signal inputs,
it will also suspend or power off the system, respectively.
image::eizoctltray.png["eizoctltray with expanded context menu", 343, 229]
image:eizoctltray-win.png["eizoctltray on Windows with expanded menu", 343, 278]
image:eizoctltray-mac.png["eizoctltray on macOS with expanded menu", 343, 278]
Installation
^^^^^^^^^^^^
On Windows, copy it to
__Users\*\AppData\Roaming\Microsoft\Windows\Start Menu\Programs\Startup__.
On macOS, copy it to the _Applications_ folder,
then add it in _System Settings → General → Login Items → Open at Login_.
Automation
^^^^^^^^^^
_eizoctltray_ can also be used the same way as _eizoctl_, just with any output
redirected to message windows, rather than a console window or a terminal.
This is useful for automation, such as with AutoHotkey.
Beware that Windows is not a fan of how rapidly EIZO monitors switch upstream
USB ports. Thus, if you invoke port switching with keyboard shortcuts,
remember to add a small delay, so that pressed modifier keys are not remembered.
You will also want to silence any error messages.
.AutoHotkey example
```autohotkey
#Requires AutoHotkey v2.0
exe := A_Startup . "\eizoctltray.exe"
^#F1:: { ; Windows + Control + F1
Sleep 500
Run exe " -qq --input HDMI"
}
^#F2:: { ; Windows + Control + F2
Sleep 500
Run exe " -qq --input DP"
}
^#F3:: { ; Windows + Control + F3
Sleep 500
Run exe " -qq --input USB-C"
}
#Home:: { ; Windows + Home
Run exe " -q --brightness +0.1"
}
#End:: { ; Windows + End
Run exe " -q --brightness -0.1"
}
```
On macOS, the simplest way to bind keyboard shortcuts is the Shortcuts app,
with _Run Shell Scripts_ actions:
```
/Applications/eizoctltray.app/Contents/MacOS/eizoctltray -q --input HDMI
```
If you have issues with entering a specific key combination, like I did
with ^⌘F1 etc., try changing it later within _System Settings_ → _Keyboard_ →
_Keyboard Shortcuts..._ → _Services_ → _Shortcuts_.
elksmart-comm
~~~~~~~~~~~~~
@@ -46,19 +102,46 @@ Make sure to let the Windows Razer Synapse tool upgrade the firmware to the
newest version before running the program. There might be some issues otherwise
due to protocol changes, although I don't really deem it very probable.
upscat
~~~~~~
_upscat_ streams the basic status of UPS devices that use the USB HID protocol,
in CSV format. You'll get the power status, remaining capacity, run time,
and load percentage. It's been verified with Eaton 3S/3P series and CyberPower
BR1200ELCD, both working quite differently.
This program is meant to be a simple replacement for NUT:
```sh
upscat | awk -F, '
BEGIN {
getline
for (i = 1; i <= NF; i++) csv[$i] = i
}
!$csv["AC Present"] && $csv["Remaining Capacity"] < 50 {
print "On battery with " $csv["Remaining Capacity"] "% left, shutting down"
system("systemctl poweroff")
exit
}'
```
Beware that in my experience, merely having Eatons connected over USB can
mess with your system's ability to suspend.
Packages
--------
Regular releases are sporadic. git master should be stable enough.
You can get a package with the latest development version
as a https://git.janouch.name/p/nixexprs[Nix derivation].
Windows binaries can be downloaded from
Windows/macOS binaries can be downloaded from
https://git.janouch.name/p/usb-drivers/releases[the Releases page on Gitea].
They aren't signed/trusted--feel free to either rebuild them yourself,
or contact me about a donation to change that.
Building
--------
Build dependencies:
CMake, pkg-config, liberty (included), help2man +
Build-only dependencies:
CMake, pkg-config, liberty (included) +
Runtime dependencies:
libusb-1.0 (elksmart-comm, razer-bw-te-ctl), hidapi >= 0.14 (eizoctl)
@@ -77,6 +160,29 @@ Or you can try telling CMake to make a package for you. For Debian it is:
$ cpack -G DEB
# dpkg -i usb-drivers-*.deb
Windows
~~~~~~~
You can either build within an MSYS2 environment,
or cross-compile using Mingw-w64:
$ sh -e cmake/Win64Depends.sh
$ cmake -DCMAKE_TOOLCHAIN_FILE=liberty/cmake/toolchains/MinGW-w64-x64.cmake \
-DCMAKE_BUILD_TYPE=Release -B build
$ cmake --build build
macOS
~~~~~
You can either build _eizoctltray_ against Homebrew,
or link hidapi statically for a standalone portable app:
$ git clone https://github.com/libusb/hidapi.git
$ cmake -S hidapi -DBUILD_SHARED_LIBS=OFF \
-DCMAKE_INSTALL_PREFIX=$PWD/hidapi-build \
-DCMAKE_BUILD_TYPE=Release -B hidapi-build
$ cmake --build hidapi-build -- install
$ cmake -Dhidapi_ROOT=$PWD/hidapi-build -DCMAKE_BUILD_TYPE=Release -B build
$ cmake --build build
Contributing and Support
------------------------
Use https://git.janouch.name/p/usb-drivers to report bugs, request features,
+6 -5
View File
@@ -1,7 +1,8 @@
#!/bin/sh -e
# Win64Depends.sh: download dependencies from MSYS2 for cross-compilation.
# Dependencies: AWK, sed, sha256sum, cURL, bsdtar
repository=https://repo.msys2.org/mingw/mingw64/
repository=https://repo.msys2.org/mingw/ucrt64/
pkg=mingw-w64-ucrt-x86_64
status() {
echo "$(tput bold)-- $*$(tput sgr0)"
@@ -9,7 +10,7 @@ status() {
dbsync() {
status Fetching repository DB
[ -f db.tsv ] || curl -# "$repository/mingw64.db" | bsdtar -xOf- | awk '
[ -f db.tsv ] || curl -# "$repository/ucrt64.db" | bsdtar -xOf- | awk '
function flush() { print f["%NAME%"] f["%FILENAME%"] f["%DEPENDS%"] }
NR > 1 && $0 == "%FILENAME%" { flush(); for (i in f) delete f[i] }
!/^[^%]/ { field = $0; next } { f[field] = f[field] $0 "\t" }
@@ -52,10 +53,10 @@ extract() {
# This directory name matches the prefix in .pc files, so we don't need to
# modify them (pkgconf has --prefix-variable, but CMake can't pass that option).
mkdir -p mingw64
cd mingw64
mkdir -p ucrt64
cd ucrt64
dbsync
fetch mingw-w64-x86_64-hidapi mingw-w64-x86_64-libusb
fetch $pkg-hidapi $pkg-libusb
verify
extract
+240
View File
@@ -0,0 +1,240 @@
// Usage: tshark { -r FILE | -i INTERFACE } -l -T ek --disable-protocol usbhid \
// | go run eizo-pcap-decode.go [ | less -R]
//
// This cannot be done through -T json, because tshark doesn't immediately
// flush the current object's trailing newline, but rather waits to decide
// if it should follow it with a comma. Even with -l, it will flush it late.
// It would be good if we could convince it not to wrap packets in a big array.
package main
import (
"encoding/binary"
"encoding/hex"
"encoding/json"
"errors"
"flag"
"fmt"
"io"
"os"
"strings"
)
type Packet struct {
Layers struct {
USB struct {
Source string `json:"usb_usb_src"`
Destination string `json:"usb_usb_dst"`
Direction string `json:"usb_usb_endpoint_address_direction"`
MacEndpointType string `json:"usb_usb_darwin_endpoint_type"`
TransferType string `json:"usb_usb_transfer_type"`
} `json:"usb"`
CapData string `json:"usb_usb_capdata"`
ControlResponse string `json:"usb_usb_control_Response"`
DataFragment string `json:"usb_usb_data_fragment"`
} `json:"layers"`
}
func (p *Packet) addr() string {
if p.Layers.USB.Source == "host" {
return p.Layers.USB.Destination
} else {
return p.Layers.USB.Source
}
}
func (p *Packet) isInterrupt() bool {
return p.Layers.USB.MacEndpointType == "3" ||
p.Layers.USB.TransferType == "0x01"
}
func (p *Packet) isControl() bool {
return p.Layers.USB.MacEndpointType == "0" ||
p.Layers.USB.TransferType == "0x02"
}
func (p *Packet) isIncoming() bool {
return p.Layers.USB.Direction == "1"
}
func hexDecode(encoded string) []byte {
decoded, err := hex.DecodeString(strings.ReplaceAll(encoded, ":", ""))
if err != nil {
panic(err)
}
return decoded
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
var (
raw *bool
le = binary.LittleEndian
fmtIn, fmtOut, fmtReset string
)
func decodeSubreport(id byte, data []byte) string {
critical := isCriticalSubreport(id)
if len(data) < 6 || critical && len(data) < 8 {
return fmt.Sprintf("%x", data)
}
var cs uint16
if critical {
data, cs = data[2:], le.Uint16(data[0:2])
}
usage := uint32(le.Uint16(data[:2]))<<16 | uint32(le.Uint16(data[2:4]))
filtered := make([]byte, len(data)-6)
for i, b := range data[6:] {
if b < 32 || b > 126 {
filtered[i] = '.'
} else {
filtered[i] = b
}
}
if critical {
return fmt.Sprintf("<> %08x %04x=%04x %x %s",
usage, cs, le.Uint16(data[4:6]), data[6:], string(filtered))
} else if usage == 0xff0000f1 {
// No idea what this is, but it follows the format.
return fmt.Sprintf("<> %08x %04x %s",
usage, le.Uint16(data[4:6]), decodeMP(data[6:]))
} else {
return fmt.Sprintf("<> %08x %04x %x %s",
usage, le.Uint16(data[4:6]), data[6:], string(filtered))
}
}
func decodeResult(data []byte) string {
if len(data) < 7 {
return fmt.Sprintf("%x", data)
}
usage := uint32(le.Uint16(data[:2]))<<16 | uint32(le.Uint16(data[2:4]))
return fmt.Sprintf(">< %08x %04x %02x", usage, le.Uint16(data[4:6]),
data[6])
}
func decodeMP(data []byte) string {
var out string
for i := 0; i+1 < len(data); {
sz := int(data[i+1])
if data[i] == 0xff || i+sz > len(data) {
break
}
if out != "" {
out += " "
}
out += fmt.Sprintf("[%02x] %x", data[i], data[i+2:i+2+sz])
i += 2 + sz
}
return out
}
func isSetSubreport(id byte) bool {
switch id {
case 2, 4, 11, 13:
return true
}
return false
}
func isGetSubreport(id byte) bool {
switch id {
case 3, 5, 12, 14:
return true
}
return false
}
func isCriticalSubreport(id byte) bool {
switch id {
case 11, 12, 13, 14:
return true
}
return false
}
func isSubreport(id byte) bool {
return isSetSubreport(id) || isGetSubreport(id)
}
func processInterrupt(p *Packet) {
data := hexDecode(p.Layers.CapData)
if len(data) < 1 {
return
}
if *raw {
fmt.Printf("%s INT %02x %x\n", p.addr(), data[0], data[1:])
} else if isSubreport(data[0]) {
fmt.Printf("%s INT %s\n", p.addr(), decodeSubreport(data[0], data[1:]))
}
}
func processControl(p *Packet) {
// macOS (Darwin) and Linux report Set_Feature differently.
data := hexDecode(p.Layers.ControlResponse)
if len(data) == 0 {
data = hexDecode(p.Layers.DataFragment)
}
if len(data) < 1 {
return
}
if p.isIncoming() {
if *raw {
fmt.Printf("%s IN %02x %x\n", p.addr(), data[0], data[1:])
} else if data[0] == 1 {
fmt.Printf("%s IN SR %x\n", p.addr(), data[5:])
} else if isGetSubreport(data[0]) {
fmt.Printf("%s IN %s%s%s\n", p.addr(),
fmtIn, decodeSubreport(data[0], data[1:]), fmtReset)
} else if data[0] == 6 {
fmt.Printf("%s IN PC %04x\n", p.addr(), le.Uint16(data[1:]))
} else if data[0] == 7 {
fmt.Printf("%s IN %s\n", p.addr(), decodeResult(data[1:]))
} else if data[0] == 8 {
fmt.Printf("%s IN ID %s %s\n", p.addr(), data[1:9], data[9:])
} else if data[0] == 9 {
fmt.Printf("%s IN MP %s\n", p.addr(), decodeMP(data[1:]))
} else if data[0] == 10 {
fmt.Printf("%s IN CS %04x\n", p.addr(), le.Uint16(data[1:]))
} else {
fmt.Printf("%s IN %02x %x\n", p.addr(), data[0], data[1:])
}
} else {
if *raw {
fmt.Printf("%s OUT %02x %x\n", p.addr(), data[0], data[1:])
} else if isSetSubreport(data[0]) {
fmt.Printf("%s OUT %s%s%s\n", p.addr(),
fmtOut, decodeSubreport(data[0], data[1:]), fmtReset)
} else if data[0] == 10 {
fmt.Printf("%s OUT CS %04x\n", p.addr(), le.Uint16(data[1:]))
} else if data[0] != 1 && !isGetSubreport(data[0]) {
fmt.Printf("%s OUT %02x %x\n", p.addr(), data[0], data[1:])
}
}
}
func main() {
raw = flag.Bool("raw", false, "Do not decode EIZO packets")
flag.Parse()
if _, ok := os.LookupEnv("NO_COLOR"); !ok {
fmtIn, fmtOut, fmtReset = "\x1b[34m", "\x1b[31m", "\x1b[m"
}
decoder := json.NewDecoder(os.Stdin)
for {
var p Packet
if err := decoder.Decode(&p); err != nil {
if errors.Is(err, io.EOF) {
break
}
fmt.Fprintf(os.Stderr, "%v\n", err)
} else if p.isInterrupt() {
processInterrupt(&p)
} else if p.isControl() {
processControl(&p)
}
}
}
+430 -72
View File
@@ -4,7 +4,7 @@
* This program stays independent of the liberty library
* in order to build on Windows.
*
* Copyright (c) 2024, Přemysl Eric Janouch <p@janouch.name>
* Copyright (c) 2025, Přemysl Eric Janouch <p@janouch.name>
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted.
@@ -18,6 +18,14 @@
* CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*
*/
// On Windows, vswprintf() interprets %s in the width of the format string,
// and %hs is not really compliant with any standard:
// https://devblogs.microsoft.com/oldnewthing/20190830-00/?p=102823
#ifdef _WIN32
#define __USE_MINGW_ANSI_STDIO
#endif
#include <stdarg.h>
#include <stdbool.h>
#include <stdint.h>
@@ -41,7 +49,9 @@
#define hid_init hidapi_hid_init
#endif
#if defined __GNUC__
#if defined __MINGW_GNU_PRINTF
#define ATTRIBUTE_PRINTF(x, y) __MINGW_GNU_PRINTF((x), (y))
#elif defined __GNUC__
#define ATTRIBUTE_PRINTF(x, y) __attribute__((format(printf, x, y)))
#else
#define ATTRIBUTE_PRINTF(x, y)
@@ -132,7 +142,6 @@ struct parser {
struct parser_state_local {
uint32_t usage;
uint32_t usage_minimum;
uint32_t usage_maximum;
uint32_t designator_index;
uint32_t designator_minimum;
uint32_t designator_maximum;
@@ -256,7 +265,8 @@ parse_item(
break; case PARSER_ITEM_TAG_LOCAL_USAGE_MINIMUM:
parser->local.usage_minimum = u;
break; case PARSER_ITEM_TAG_LOCAL_USAGE_MAXIMUM:
parser->local.usage_maximum = u;
// This adds to usages from parser->local.usage_minimum through u.
return "usage ranges are not supported";
break; case PARSER_ITEM_TAG_LOCAL_DESIGNATOR_INDEX:
parser->local.designator_index = u;
break; case PARSER_ITEM_TAG_LOCAL_DESIGNATOR_MINIMUM:
@@ -803,7 +813,7 @@ eizo_port_by_name(const char *name)
return index;
}
static char *
static const char *
eizo_port_to_name(uint16_t port)
{
const char *stem = NULL;
@@ -811,14 +821,14 @@ eizo_port_to_name(uint16_t port)
if (group && group < sizeof g_port_names / sizeof g_port_names[0])
stem = g_port_names[group][0];
static char buffer[32] = "";
static char buf[32] = "";
if (!stem)
snprintf(buffer, sizeof buffer, "%x", port);
snprintf(buf, sizeof buf, "%x", port);
else if (!number)
snprintf(buffer, sizeof buffer, "%s", stem);
snprintf(buf, sizeof buf, "%s", stem);
else
snprintf(buffer, sizeof buffer, "%s %d", stem, number);
return buffer;
snprintf(buf, sizeof buf, "%s %d", stem, (number + 1));
return buf;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
@@ -880,8 +890,22 @@ eizo_get_input_port(struct eizo_monitor *m, uint16_t *port)
return true;
}
static void
eizo_get_input_ports(struct eizo_monitor *m, uint16_t *ports, size_t size)
{
struct eizo_profile_item *item = &m->profile[EIZO_PROFILE_KEY_INPUT_PORTS];
if (item->len) {
for (size_t i = 0; i < size && i < item->len / 4; i++)
ports[i] = peek_u16le(item->data + i * 4);
} else {
const uint16_t *db = eizo_ports_by_product_name(m->product);
for (size_t i = 0; i < size && db && db[i]; i++)
ports[i] = db[i];
}
}
static uint16_t
eizo_resolve_port(struct eizo_monitor *m, const char *port)
eizo_resolve_port_by_name(struct eizo_monitor *m, const char *port)
{
uint8_t usb_c_index = 0;
if (eizo_port_by_name_in_group(port, g_port_names_usb_c, &usb_c_index)) {
@@ -893,6 +917,26 @@ eizo_resolve_port(struct eizo_monitor *m, const char *port)
return eizo_port_by_name(port);
}
static const char *
eizo_resolve_port_to_name(struct eizo_monitor *m, uint16_t port)
{
// USB-C ports are a bit tricky, they only need to be /displayed/ as such.
struct eizo_profile_item *item =
&m->profile[EIZO_PROFILE_KEY_USB_C_INPUT_PORTS];
for (uint8_t i = 0; i < item->len / 2; i++) {
if (port != peek_u16le(item->data + i * 2))
continue;
static char buf[32] = "";
if (!i)
snprintf(buf, sizeof buf, "%s", g_port_names_usb_c[0]);
else
snprintf(buf, sizeof buf, "%s %u", g_port_names_usb_c[0], (i + 1));
return buf;
}
return eizo_port_to_name(port);
}
static bool
eizo_set_input_port(struct eizo_monitor *m, uint16_t port)
{
@@ -920,69 +964,128 @@ eizo_restart(struct eizo_monitor *m)
// --- Main --------------------------------------------------------------------
struct catbuf {
char buf[4096];
size_t len;
};
static const char *
catf(struct catbuf *b, const char *format, ...) ATTRIBUTE_PRINTF(2, 3);
static const char *
catf(struct catbuf *b, const char *format, ...)
{
va_list ap;
va_start(ap, format);
int result = vsnprintf(b->buf + b->len, sizeof b->buf - b->len, format, ap);
va_end(ap);
if (result >= 0) {
b->len += result;
if (b->len >= sizeof b->buf)
b->len = sizeof b->buf - 1;
}
return b->buf;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
typedef void (*print_fn)(const char *format, ...) ATTRIBUTE_PRINTF(1, 2);
static void print_dummy(const char *format, ...)
{
(void) format;
}
static bool
eizo_watch(struct eizo_monitor *m)
eizo_watch(struct eizo_monitor *m, print_fn output, print_fn error)
{
uint8_t buf[1024] = {};
int res = 0;
while (true) {
if ((res = hid_read(m->dev, buf, sizeof buf)) < 0)
return eizo_monitor_failf(m, "watch: %ls", hid_error(m->dev));
return eizo_monitor_failf(m, "watch: %ls", hid_read_error(m->dev));
if (buf[0] != EIZO_REPORT_ID_GET &&
buf[0] != EIZO_REPORT_ID_GET_LONG) {
printf("Unknown report ID\n");
error("Unknown report ID: %02x\n", buf[0]);
continue;
}
struct catbuf message = {{0}, 0};
uint16_t page = peek_u16le(&buf[1]), id = peek_u16le(&buf[3]);
uint32_t usage = page << 16 | id;
printf("%08x", usage);
catf(&message, "%08x", usage);
const struct parser_report *r = eizo_monitor_subreport(m, usage);
if (!r) {
printf(" unknown usage\n");
output("%s", catf(&message, " unknown usage\n"));
continue;
}
size_t rlen = r->report_size / 8 * r->report_count;
if ((size_t) res < 7 + rlen) {
printf(" received data too short\n");
output("%s", catf(&message, " received data too short\n"));
continue;
}
if (r->report_size == 16)
for (size_t i = 0; i + 1 < rlen; i += 2)
printf(" %04x", peek_u16le(&buf[7 + i]));
catf(&message, " %04x", peek_u16le(&buf[7 + i]));
else
for (size_t i = 0; i < rlen; i++)
printf(" %02x", buf[7 + i]);
printf("\n");
catf(&message, " %02x", buf[7 + i]);
output("%s", catf(&message, "\n"));
}
}
typedef void (*print_fn)(const char *format, ...) ATTRIBUTE_PRINTF(1, 2);
static const char *usage = "Usage: %s OPTION...\n\n"
" -l, --list\n"
" List all connected EIZO monitors, with their serial number.\n"
" -s, --serial SERIAL\n"
" Only act on the monitor matching the specified serial number.\n"
" -b, --brightness [+-]BRIGHTNESS\n"
" Change monitor brightness; values go from 0 to 1 and may be relative.\n"
" -i, --input NAME\n"
" Change monitor input ports; use '?' to retrieve current values.\n"
" -r, --restart\n"
" Reboot monitors.\n"
" -e, --events\n"
" Watch for events reported by monitors.\n"
" -q, --quiet\n"
" Use once to suppress informative messages, twice to suppress errors.\n"
" -h, --help\n"
" Display this help and exit.\n"
" -V, --version\n"
" Output version information and exit.\n";
static int
run(int argc, char *argv[], print_fn output, print_fn error, bool verbose)
run(int argc, char *argv[], print_fn output, print_fn error)
{
const char *name = argv[0];
const char *usage = "Usage: %s [--brightness [+-]BRIGHTNESS] [--input NAME]"
" [--restart] [--events]\n";
static struct option opts[] = {
{"input", required_argument, NULL, 'i'},
{"list", no_argument, NULL, 'l'},
{"serial", required_argument, NULL, 's'},
{"brightness", required_argument, NULL, 'b'},
{"input", required_argument, NULL, 'i'},
{"restart", no_argument, NULL, 'r'},
{"events", no_argument, NULL, 'e'},
{"quiet", no_argument, NULL, 'q'},
{"help", no_argument, NULL, 'h'},
{"version", no_argument, NULL, 'V'},
{}
};
int quiet = 0;
double brightness = NAN;
bool relative = false, restart = false, events = false;
const char *port = NULL;
bool list = false, relative = false, restart = false, events = false;
const char *serial = NULL, *port = NULL;
int c = 0;
while ((c = getopt_long(argc, argv, "b:i:h", opts, NULL)) != -1)
while ((c = getopt_long(argc, argv, "ls:b:i:reqhV", opts, NULL)) != -1)
switch (c) {
case 'l':
list = true;
break;
case 's':
serial = optarg;
break;
case 'b':
relative = *optarg == '+' || *optarg == '-';
if (sscanf(optarg, "%lf", &brightness) && isfinite(brightness))
@@ -999,6 +1102,9 @@ run(int argc, char *argv[], print_fn output, print_fn error, bool verbose)
case 'e':
events = true;
break;
case 'q':
quiet++;
break;
case 'h':
output(usage, name);
return 0;
@@ -1024,9 +1130,11 @@ run(int argc, char *argv[], print_fn output, print_fn error, bool verbose)
error("%ls\n", hid_error(NULL));
return 1;
}
if (quiet > 0)
output = print_dummy;
if (quiet > 1)
error = print_dummy;
// It should be possible to choose a particular monitor,
// but it is generally more useful to operate on all of them.
struct hid_device_info *devs = hid_enumerate(USB_VID_EIZO, 0), *p = devs;
for (; p; p = p->next) {
struct eizo_monitor m = {};
@@ -1035,6 +1143,15 @@ run(int argc, char *argv[], print_fn output, print_fn error, bool verbose)
continue;
}
if (list)
output("%s %s\n", m.product, m.serial);
// Generously assuming that different products/models
// don't share serial numbers,
// which would otherwise deserve another filtering option.
if (serial && strcmp(serial, m.serial))
goto next;
if (isfinite(brightness)) {
double prev = 0.;
if (!eizo_get_brightness(&m, &prev)) {
@@ -1043,42 +1160,43 @@ run(int argc, char *argv[], print_fn output, print_fn error, bool verbose)
double next = relative ? brightness + prev : brightness;
if (!eizo_set_brightness(&m, next))
error("Failed to set brightness: %s\n", m.error);
else if (verbose)
else
output("%s %s: brightness: %.2f -> %.2f\n",
m.product, m.serial, prev, next);
}
}
if (port) {
uint16_t prev = 0;
uint16_t next = eizo_resolve_port(&m, port);
uint16_t next = eizo_resolve_port_by_name(&m, port);
if (!eizo_get_input_port(&m, &prev)) {
error("Failed to get input port: %s\n", m.error);
} else if (!strcmp(port, "?")) {
output("%s %s: input: %s\n",
m.product, m.serial, eizo_port_to_name(prev));
output("%s %s: input: %s\n", m.product, m.serial,
eizo_resolve_port_to_name(&m, prev));
} else if (!next) {
error("Failed to resolve port name: %s\n", port);
} else {
if (!eizo_set_input_port(&m, next))
error("Failed to set input port: %s\n", m.error);
else if (verbose)
output("%s %s: input: %s -> %s\n",
m.product, m.serial, eizo_port_to_name(prev), port);
else
output("%s %s: input: %s -> %s\n", m.product, m.serial,
eizo_resolve_port_to_name(&m, prev), port);
}
}
if (restart) {
if (!eizo_restart(&m))
error("Failed to restart: %s\n", m.error);
else if (verbose)
else
output("%s %s: restart\n", m.product, m.serial);
}
if (events) {
if (!verbose)
if (quiet)
error("Watching events is not possible in this mode\n");
else if (!eizo_watch(&m))
else if (!eizo_watch(&m, output, error))
error("%s\n", m.error);
}
next:
eizo_monitor_close(&m);
}
hid_free_enumeration(devs);
@@ -1110,11 +1228,11 @@ stdio_error(const char *format, ...)
int
main(int argc, char *argv[])
{
return run(argc, argv, stdio_output, stdio_error, true);
return run(argc, argv, stdio_output, stdio_error);
}
// --- Windows -----------------------------------------------------------------
#else
#elif defined _WIN32
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
@@ -1133,10 +1251,13 @@ message_printf(const char *format, va_list ap)
return NULL;
mbstowcs(format_wide, format, format_wide_len);
int message_len = vswprintf(NULL, 0, format_wide, ap) + 1;
// Note that just vswprintf() cannot be used like this
// (at least since mingw-w64 commit c85d64),
// and vsnwprintf() is a MinGW extension, acting like C11 vsnwprintf_s.
int message_len = vsnwprintf(NULL, 0, format_wide, ap) + 1;
wchar_t *message = calloc(message_len, sizeof *message);
if (message_len > 0 && message)
vswprintf(message, message_len, format_wide, ap);
vsnwprintf(message, message_len, format_wide, ap);
free(format_wide);
return message;
@@ -1153,8 +1274,8 @@ message_output(const char *format, ...)
wchar_t *message = message_printf(format, ap);
va_end(ap);
if (message) {
MessageBox(
NULL, message, NULL, MB_ICONINFORMATION | MB_OK | MB_APPLMODAL);
MessageBox(NULL, message,
L"Message", MB_ICONINFORMATION | MB_OK | MB_APPLMODAL);
free(message);
}
}
@@ -1208,36 +1329,15 @@ append_monitor(struct eizo_monitor *m, HMENU menu, UINT_PTR base)
AppendMenu(menu, flags_darker, base + IDM_DARKER, L"Darker");
AppendMenu(menu, MF_SEPARATOR, 0, NULL);
uint16_t ports[16] = {0};
struct eizo_profile_item *item = &m->profile[EIZO_PROFILE_KEY_INPUT_PORTS];
if (item->len) {
for (size_t i = 0; i < 15 && i < item->len / 4; i++)
ports[i] = peek_u16le(item->data + i * 4);
} else {
const uint16_t *db = eizo_ports_by_product_name(m->product);
for (size_t i = 0; i < 15 && db && db[i]; i++)
ports[i] = db[i];
}
uint16_t current = 0;
uint16_t ports[16] = {0}, current = 0;
eizo_get_input_ports(m, ports, sizeof ports / sizeof ports[0] - 1);
(void) eizo_get_input_port(m, &current);
if (!ports[0])
ports[0] = current;
// USB-C ports are a bit tricky, they only need to be /displayed/ as such.
item = &m->profile[EIZO_PROFILE_KEY_USB_C_INPUT_PORTS];
for (size_t i = 0; ports[i]; i++) {
uint8_t usb_c = 0;
for (size_t u = 0; u < item->len / 2; u++)
if (ports[i] == peek_u16le(item->data + u * 2))
usb_c = u + 1;
if (!usb_c)
snwprintf(buf, sizeof buf, L"%s", eizo_port_to_name(ports[i]));
else if (usb_c == 1)
snwprintf(buf, sizeof buf, L"%s", g_port_names_usb_c[0]);
else
snwprintf(buf, sizeof buf, L"%s %u", g_port_names_usb_c[0], usb_c);
snwprintf(buf, sizeof buf, L"%s",
eizo_resolve_port_to_name(m, ports[i]));
UINT flags = MF_STRING;
if (ports[i] == current)
@@ -1390,7 +1490,7 @@ wWinMain(
char *mb = mbargv[i + 1] = calloc(len, sizeof *mb);
wcstombs(mb, argv[i], len);
}
return run(argc + 1, mbargv, message_output, message_error, false);
return run(argc + 1, mbargv, message_output, message_error);
}
LocalFree(argv);
@@ -1442,4 +1542,262 @@ wWinMain(
return msg.wParam;
}
// --- macOS -------------------------------------------------------------------
#elif defined __APPLE__
#include <AppKit/AppKit.h>
#include <AppKit/NSStatusBar.h>
#include <Foundation/Foundation.h>
static void message_output(const char *format, ...) ATTRIBUTE_PRINTF(1, 2);
static void message_error(const char *format, ...) ATTRIBUTE_PRINTF(1, 2);
static void
message_output(const char *format, ...)
{
va_list ap;
va_start(ap, format);
NSString *message = [[NSString alloc]
initWithFormat:[NSString stringWithUTF8String: format] arguments:ap];
va_end(ap);
NSAlert *alert = [NSAlert new];
[alert setMessageText:message];
[alert setAlertStyle:NSAlertStyleInformational];
// XXX: How to make the OK button the first responder?
[alert addButtonWithTitle:@"OK"];
[NSApp activate];
[alert.window makeKeyAndOrderFront:nil];
[alert runModal];
}
static void
message_error(const char *format, ...)
{
va_list ap;
va_start(ap, format);
NSString *message = [[NSString alloc]
initWithFormat:[NSString stringWithUTF8String: format] arguments:ap];
va_end(ap);
NSAlert *alert = [NSAlert new];
[alert setMessageText:message];
[alert setAlertStyle:NSAlertStyleCritical];
[alert addButtonWithTitle:@"OK"];
[NSApp activate];
[alert.window makeKeyAndOrderFront:nil];
[alert runModal];
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
/// Monitor provides reference counting, and enables use of NSArray.
@interface Monitor : NSObject
@property (assign, nonatomic) struct eizo_monitor *monitor;
- (instancetype)initWithMonitor:(struct eizo_monitor *)monitor;
@end
@implementation Monitor
- (instancetype)initWithMonitor:(struct eizo_monitor *)monitor {
if (self = [super init]) {
_monitor = monitor;
}
return self;
}
- (void)dealloc {
if (_monitor) {
eizo_monitor_close(_monitor);
free(_monitor);
_monitor = NULL;
}
}
@end
@interface ApplicationDelegate
: NSObject <NSApplicationDelegate, NSMenuDelegate>
@property (strong, nonatomic) NSStatusItem *statusItem;
@property (strong, nonatomic) NSMutableArray<Monitor *> *monitors;
@end
@implementation ApplicationDelegate
- (Monitor *)getMonitorFrom:(NSControl *)control {
NSInteger index = control.tag / 0x1000;
if (!self.monitors || index < 0 || index >= self.monitors.count)
return nil;
return self.monitors[index];
}
- (void)setBrightness:(NSControl *)sender {
Monitor *m = [self getMonitorFrom:sender];
if (!m)
return;
eizo_set_brightness(m.monitor, sender.doubleValue);
}
- (void)setInputPort:(NSControl *)sender {
Monitor *m = [self getMonitorFrom:sender];
NSUInteger input = sender.tag % 0x1000;
if (!m)
return;
eizo_set_input_port(m.monitor, input);
NSEventModifierFlags mods = [NSEvent modifierFlags];
if (mods & NSEventModifierFlagShift) {
NSTask *task = [[NSTask alloc] init];
task.launchPath = @"/usr/bin/pmset";
task.arguments = @[@"sleepnow"];
[task launch];
}
}
- (void)appendMonitor:(Monitor *)m toMenu:(NSMenu *)menu base:(NSInteger)base {
NSMenuItem *titleItem = [NSMenuItem new];
titleItem.attributedTitle = [[NSAttributedString alloc]
initWithString:[NSString stringWithFormat:@"%s %s",
m.monitor->product, m.monitor->serial]
attributes:@{ NSFontAttributeName: [NSFont boldSystemFontOfSize:0] }];
[menu addItem:titleItem];
[menu addItem:[NSMenuItem separatorItem]];
[menu addItem:[NSMenuItem sectionHeaderWithTitle:@"Brightness"]];
double brightness = 0;
(void) eizo_get_brightness(m.monitor, &brightness);
// XXX: So, while having a slider is strictly more useful,
// this is not something you're supposed to do in AppKit, if only because:
// - It does not respond to keyboard.
// - Positioning it properly is dark magic.
NSSlider *slider = [NSSlider
sliderWithValue:brightness minValue:0. maxValue:1.
target:self action:@selector(setBrightness:)];
slider.tag = base;
slider.continuous = true;
NSView *sliderView = [[NSView alloc]
initWithFrame:NSMakeRect(0, 0, 200., slider.knobThickness + 2.)];
[sliderView addSubview:slider];
slider.translatesAutoresizingMaskIntoConstraints = false;
[NSLayoutConstraint activateConstraints:@[
[slider.leftAnchor
constraintEqualToAnchor:sliderView.leftAnchor constant:+23.],
[slider.rightAnchor
constraintEqualToAnchor:sliderView.rightAnchor constant:-6.],
[slider.centerYAnchor
constraintEqualToAnchor:sliderView.centerYAnchor]
]];
NSMenuItem *brightnessItem = [[NSMenuItem alloc]
initWithTitle:@"" action:nil keyEquivalent:@""];
brightnessItem.view = sliderView;
[menu addItem:brightnessItem];
[menu addItem:[NSMenuItem separatorItem]];
[menu addItem:[NSMenuItem sectionHeaderWithTitle:@"Input ports"]];
uint16_t ports[16] = {0}, current = 0;
eizo_get_input_ports(m.monitor, ports, sizeof ports / sizeof ports[0] - 1);
(void) eizo_get_input_port(m.monitor, &current);
if (!ports[0])
ports[0] = current;
for (size_t i = 0; ports[i]; i++) {
NSString *title = [NSString stringWithUTF8String:
eizo_resolve_port_to_name(m.monitor, ports[i])];
NSMenuItem *inputPortItem = [[NSMenuItem alloc]
initWithTitle:title action:@selector(setInputPort:)
keyEquivalent:@""];
inputPortItem.tag = base + ports[i];
if (ports[i] == current)
inputPortItem.state = NSControlStateValueOn;
[menu addItem:inputPortItem];
}
}
- (void)showMenu {
struct hid_device_info *devs = hid_enumerate(USB_VID_EIZO, 0);
NSMutableArray<Monitor *> *monitors = [NSMutableArray array];
NSMenu *menu = [NSMenu new];
[menu setDelegate:self];
for (struct hid_device_info *p = devs; p; p = p->next) {
struct eizo_monitor *m = calloc(1, sizeof *m);
if (!m)
continue;
if (!eizo_monitor_open(m, p)) {
message_error("%s", m->error);
free(m);
continue;
}
Monitor *monitor = [[Monitor alloc] initWithMonitor:m];
[self appendMonitor:monitor toMenu:menu base:0x1000 * monitors.count];
[menu addItem:[NSMenuItem separatorItem]];
[monitors addObject:monitor];
}
if (!monitors.count) {
NSMenuItem *item = [[NSMenuItem alloc]
initWithTitle:@"No monitors found" action:nil keyEquivalent:@""];
item.enabled = false;
[menu addItem:item];
}
[menu addItem:[NSMenuItem separatorItem]];
[menu addItem:[[NSMenuItem alloc]
initWithTitle:@"Quit" action:@selector(terminate:) keyEquivalent:@"q"]];
self.monitors = monitors;
// XXX: Unfortunately, this is not how menus should behave,
// but we really want to generate the menu on demand.
self.statusItem.menu = menu;
[self.statusItem.button performClick:nil];
self.statusItem.menu = nil;
}
- (void)menuDidClose:(NSMenu *)menu {
// Close and free up the devices as soon as possible, but no sooner.
dispatch_async(dispatch_get_main_queue(), ^{
self.monitors = nil;
});
}
- (void)applicationDidFinishLaunching:(NSNotification *)notification {
NSStatusBar *systemBar = [NSStatusBar systemStatusBar];
self.statusItem = [systemBar statusItemWithLength:NSSquareStatusItemLength];
if (!self.statusItem.button)
return;
// Not bothering with templates,
// the icon would need to have a hole through it to look better.
NSImage *image = [NSApp applicationIconImage];
// One would expect the status bar to pick a reasonable size
// automatically, but that is not what happens.
image.size = NSMakeSize(systemBar.thickness, systemBar.thickness);
self.statusItem.button.image = image;
self.statusItem.button.action = @selector(showMenu);
}
@end
int
main(int argc, char *argv[])
{
@autoreleasepool {
if (argc > 1)
return run(argc, argv, message_output, message_error);
NSApplication *app = [NSApplication sharedApplication];
ApplicationDelegate *delegate = [ApplicationDelegate new];
app.delegate = delegate;
[app setActivationPolicy:NSApplicationActivationPolicyAccessory];
[app run];
}
return 0;
}
#endif
Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.7 KiB

BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.7 KiB

+239 -41
View File
@@ -196,7 +196,7 @@ compress_value(unsigned value, struct str *encoded)
}
static void
compress_pulses (const struct pulse *pulses, size_t len, struct str *encoded)
compress_pulses(const struct pulse *pulses, size_t len, struct str *encoded)
{
unsigned counts[len];
memset(counts, 0, sizeof counts);
@@ -205,17 +205,18 @@ compress_pulses (const struct pulse *pulses, size_t len, struct str *encoded)
if (pulse_equal(pulses[i], pulses[k]))
counts[i]++;
struct pulse p1 = {}, p2 = {};
size_t top1 = 0, top2 = 0;
size_t top1 = 0;
for (size_t i = 0; i < len; i++)
if (counts[i] > counts[top1])
p1 = pulses[top1 = i];
top1 = i;
size_t top2 = top1;
for (size_t i = 0; i < len; i++)
if (counts[i] < counts[top1] &&
counts[i] > counts[top2])
p2 = pulses[top2 = i];
else if (counts[top2] == counts[top1])
p2 = pulses[top2 = i];
if (!pulse_equal(pulses[i], pulses[top1]) &&
(top2 == top1 || counts[i] > counts[top2]))
top2 = i;
struct pulse p1 = pulses[top1], p2 = pulses[top2];
// Although I haven't really tried it, something tells me that
// this will work even in the degenerated case of len <= 2.
@@ -241,6 +242,116 @@ compress_pulses (const struct pulse *pulses, size_t len, struct str *encoded)
}
}
// --- Huffman -----------------------------------------------------------------
// LLM-reconstructed from the Ocrustar application, untested.
struct huffman_node {
struct huffman_node *parent;
unsigned frequency;
bool bit;
};
static void
huffman_push(struct huffman_node **heap, size_t *len, struct huffman_node *node)
{
// Equal frequencies must behave like Java's PriorityQueue.
size_t child = (*len)++;
while (child) {
size_t parent = (child - 1) / 2;
if (node->frequency >= heap[parent]->frequency)
break;
heap[child] = heap[parent];
child = parent;
}
heap[child] = node;
}
static struct huffman_node *
huffman_pop(struct huffman_node **heap, size_t *len)
{
struct huffman_node *result = heap[0], *node = heap[--*len];
if (!*len)
return result;
size_t parent = 0;
while (parent < *len / 2) {
size_t child = parent * 2 + 1;
if (child + 1 < *len &&
heap[child + 1]->frequency < heap[child]->frequency)
child++;
if (heap[child]->frequency >= node->frequency)
break;
heap[parent] = heap[child];
parent = child;
}
heap[parent] = node;
return result;
}
static void
huffman_encode(const struct str *input, struct str *encoded)
{
unsigned frequencies[256] = {};
for (size_t i = 0; i < input->len; i++)
frequencies[(uint8_t) input->str[i]]++;
struct huffman_node nodes[511] = {};
struct huffman_node *heap[256];
struct huffman_node *leaves[256] = {};
size_t nodes_len = 0, heap_len = 0;
for (size_t i = 0; i < N_ELEMENTS(frequencies); i++) {
if (!frequencies[i])
continue;
struct huffman_node *node = leaves[i] = &nodes[nodes_len++];
node->frequency = frequencies[i];
huffman_push(heap, &heap_len, node);
}
str_pack_u16(encoded, heap_len);
for (size_t i = 0; i < N_ELEMENTS(frequencies); i++) {
if (!frequencies[i])
continue;
str_pack_u8(encoded, i);
str_pack_u16(encoded, frequencies[i]);
}
while (heap_len > 1) {
struct huffman_node *left = huffman_pop(heap, &heap_len);
struct huffman_node *right = huffman_pop(heap, &heap_len);
struct huffman_node *node = &nodes[nodes_len++];
node->frequency = left->frequency + right->frequency;
left->parent = right->parent = node;
left->bit = false;
right->bit = true;
huffman_push(heap, &heap_len, node);
}
// The padding count precedes the data, so reserve it for now.
size_t padding = encoded->len;
str_pack_u8(encoded, 0);
uint8_t bits[256];
uint8_t byte = 0;
size_t bit = 0;
for (size_t i = 0; i < input->len; i++) {
size_t len = 0;
for (struct huffman_node *node = leaves[(uint8_t) input->str[i]];
node->parent; node = node->parent)
bits[len++] = node->bit;
while (len) {
byte = byte << 1 | bits[--len];
if (++bit == 8) {
str_pack_u8(encoded, byte);
byte = bit = 0;
}
}
}
if (bit) {
encoded->str[padding] = 8 - bit;
str_pack_u8(encoded, byte << (8 - bit));
}
}
// --- Device interaction ------------------------------------------------------
enum {
@@ -251,11 +362,18 @@ enum {
// 0x184 (EKX5S-T, international edition)
USB_PRODUCT_SMTCTL_SMART_EKX4S = 0x0195,
USB_PRODUCT_SMTCTL_SMART_EKX5S_T = 0x0184,
USB_PRODUCT_SMTCTL_SMART_0132 = 0x0132,
// There should only ever be one interface.
USB_INTERFACE = 0,
};
enum device_protocol {
DEVICE_PROTOCOL_UNKNOWN,
DEVICE_PROTOCOL_PLAIN,
DEVICE_PROTOCOL_HUFFMAN,
};
static uint8_t
c_transmit[] = {-1, -1, -1, -1},
c_learn[] = {-2, -2, -2, -2},
@@ -277,7 +395,8 @@ init_device_from_desc(struct libusb_config_descriptor *desc, struct error **e)
return error_set(e, "unexpected alternate setting count");
const struct libusb_interface_descriptor *asd = desc->interface->altsetting;
if (asd->bInterfaceClass != LIBUSB_CLASS_COMM)
if (asd->bInterfaceClass != LIBUSB_CLASS_COMM &&
asd->bInterfaceClass != LIBUSB_CLASS_VENDOR_SPEC)
return error_set(e, "unexpected USB interface class");
if (asd->bNumEndpoints != 2)
return error_set(e, "unexpected endpoint count");
@@ -306,20 +425,25 @@ init_device_from_desc(struct libusb_config_descriptor *desc, struct error **e)
static bool
init_device(libusb_device_handle *device, struct error **e)
{
int result = libusb_kernel_driver_active(device, USB_INTERFACE);
if (result == 1) {
// macOS for some reason claims the interface and we need to detach it
// before the following call to libusb_get_active_config_descriptor().
if ((result = libusb_detach_kernel_driver(device, USB_INTERFACE)))
return error_set(e, "cannot detach kernel driver: %s",
libusb_strerror(result));
print_debug("detached the kernel driver");
} else if (result) {
return error_set(e, "%s", libusb_strerror(result));
}
struct libusb_config_descriptor *desc = NULL;
int result =
libusb_get_active_config_descriptor(libusb_get_device(device), &desc);
if (result)
if ((result = libusb_get_active_config_descriptor(
libusb_get_device(device), &desc)))
return error_set(e, "%s", libusb_strerror(result));
bool ok = true;
if ((result = libusb_kernel_driver_active(device, USB_INTERFACE)) == 1)
ok = error_set(e, "device is claimed by a kernel driver");
else if (result)
ok = error_set(e, "%s", libusb_strerror(result));
else
ok = init_device_from_desc(desc, e);
bool ok = init_device_from_desc(desc, e);
libusb_free_config_descriptor(desc);
return ok;
}
@@ -348,7 +472,8 @@ checksum(const uint8_t *b, size_t len)
static bool
send_transmit(libusb_device_handle *device, unsigned long frequency,
const struct pulse *pulses, size_t pulses_len, struct error **e)
const struct pulse *pulses, size_t pulses_len,
enum device_protocol protocol, struct error **e)
{
if (g_debug_mode)
for (size_t i = 0; i < pulses_len;) {
@@ -358,6 +483,12 @@ send_transmit(libusb_device_handle *device, unsigned long frequency,
struct str compressed = str_make();
compress_pulses(pulses, pulses_len, &compressed);
if (protocol == DEVICE_PROTOCOL_HUFFMAN) {
struct str huffman = str_make();
huffman_encode(&compressed, &huffman);
str_free(&compressed);
compressed = huffman;
}
struct str message = str_make();
str_append_data(&message, c_transmit, sizeof c_transmit);
@@ -503,8 +634,23 @@ send_learn(libusb_device_handle *device, struct error **e)
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
static enum device_protocol
classify_device(const uint8_t *b, size_t len)
{
if (len == 6 && !memcmp(b, c_identify, sizeof c_identify) &&
b[4] == 0x02 && b[5] == 0xaa)
return DEVICE_PROTOCOL_HUFFMAN;
if (len == 6 && !memcmp(b, c_identify, sizeof c_identify) &&
b[4] == 0x70 && b[5] == 0x01)
return DEVICE_PROTOCOL_PLAIN;
if (len == 6 && !memcmp(b, "\xfa\xfa\xfa\xfa", 4))
return DEVICE_PROTOCOL_PLAIN;
return DEVICE_PROTOCOL_UNKNOWN;
}
static bool
send_identify(libusb_device_handle *device, struct error **e)
send_identify(libusb_device_handle *device, enum device_protocol *protocol,
struct error **e)
{
uint8_t buffer[64] = {};
int result = 0, len = 0;
@@ -512,30 +658,36 @@ send_identify(libusb_device_handle *device, struct error **e)
device, g.endpoint_in, buffer, sizeof buffer, &len, 10)))
/* Flush buffers. */;
if ((result = libusb_bulk_transfer(
device, g.endpoint_out, c_identify, sizeof c_identify, &len, 100)))
return error_set(e, "identify/send: %s", libusb_strerror(result));
if ((result = libusb_bulk_transfer(
device, g.endpoint_in, buffer, sizeof buffer, &len, 100)))
return error_set(e, "identify/recv: %s", libusb_strerror(result));
int attempt = 0;
while (true) {
if ((result = libusb_bulk_transfer(device, g.endpoint_out,
c_identify, sizeof c_identify, &len, 100)))
return error_set(e, "identify/send: %s", libusb_strerror(result));
if (!(result = libusb_bulk_transfer(device, g.endpoint_in,
buffer, sizeof buffer, &len, 500)))
break;
if (result != LIBUSB_ERROR_TIMEOUT || ++attempt == 4)
return error_set(e, "identify/recv: %s", libusb_strerror(result));
}
// XXX: Sometimes, the device doesn't send any identification values.
if (len != 6 || memcmp(buffer, c_identify, sizeof c_identify) ||
buffer[4] != 0x70 || buffer[5] != 0x01)
if ((*protocol = classify_device(buffer, len)) == DEVICE_PROTOCOL_UNKNOWN)
return error_set(e, "device busy or not supported");
print_debug("device protocol: %s",
*protocol == DEVICE_PROTOCOL_HUFFMAN ? "Huffman" : "plain");
#if 0
// The EKX4S does not respond to this request.
static uint8_t c_serial[] = { -5, -5, -5, -5 };
if ((result = libusb_bulk_transfer (device, g.endpoint_out,
if ((result = libusb_bulk_transfer(device, g.endpoint_out,
c_serial, sizeof c_serial, &len, 100)))
return error_set (e, "serial/send: %s", libusb_strerror (result));
if ((result = libusb_bulk_transfer (device, g.endpoint_in,
return error_set(e, "serial/send: %s", libusb_strerror(result));
if ((result = libusb_bulk_transfer(device, g.endpoint_in,
buffer, sizeof buffer, &len, 100)))
return error_set (e, "serial/recv: %s", libusb_strerror (result));
return error_set(e, "serial/recv: %s", libusb_strerror(result));
if (len < (int) sizeof c_serial ||
memcmp (buffer, c_serial, sizeof c_serial))
return error_set (e, "serial retrieval failed");
memcmp(buffer, c_serial, sizeof c_serial))
return error_set(e, "serial retrieval failed");
#endif
return true;
}
@@ -544,7 +696,8 @@ static bool
run(libusb_device_handle *device, unsigned long frequency, bool nec,
char **codes, size_t codes_len, struct error **e)
{
if (!send_identify(device, e))
enum device_protocol protocol = DEVICE_PROTOCOL_UNKNOWN;
if (!send_identify(device, &protocol, e))
return false;
if (!codes_len)
return send_learn(device, e);
@@ -562,7 +715,8 @@ run(libusb_device_handle *device, unsigned long frequency, bool nec,
? encode_nec(&code, &pulses_len, e)
: decode_learned(&code, &pulses_len, e);
ok = pulses && send_transmit(device, frequency, pulses, pulses_len, e);
ok = pulses && send_transmit(
device, frequency, pulses, pulses_len, protocol, e);
free(pulses);
if (!ok)
break;
@@ -573,6 +727,47 @@ run(libusb_device_handle *device, unsigned long frequency, bool nec,
return ok;
}
// --- Tests -------------------------------------------------------------------
#ifdef TESTING
static bool
test_huffman(const void *data, size_t len, const char *expected_hex)
{
const struct str input = {.str = (char *) data, .len = len};
struct str expected = str_make(), encoded = str_make();
huffman_encode(&input, &encoded);
bool ok = read_hex(expected_hex, &expected) &&
encoded.len == expected.len &&
!memcmp(encoded.str, expected.str, expected.len);
if (!ok) {
fprintf(stderr, "Huffman mismatch\nexpected: %s\nactual: ",
expected_hex);
for (size_t i = 0; i < encoded.len; i++)
fprintf(stderr, "%02x", (uint8_t) encoded.str[i]);
fputc('\n', stderr);
}
str_free(&expected);
str_free(&encoded);
return ok;
}
int
main(void)
{
bool ok = true;
ok &= test_huffman("", 0, "000000");
ok &= test_huffman("\0", 1, "000100000100");
ok &= test_huffman("\0\1\2\3", 4,
"00040000010100010200010300010039");
ok &= test_huffman("ABRACADABRA", 11,
"00054100054200024300014400015200020159cf58");
return !ok;
}
#define main main_shadowed
#endif // TESTING
// --- Main --------------------------------------------------------------------
int
@@ -642,6 +837,9 @@ main(int argc, char *argv[])
if (!device && !result)
device = find_device(
USB_VENDOR_SMTCTL, USB_PRODUCT_SMTCTL_SMART_EKX5S_T, &result);
if (!device && !result)
device = find_device(
USB_VENDOR_SMTCTL, USB_PRODUCT_SMTCTL_SMART_0132, &result);
if (result)
exit_fatal("couldn't open device: %s", libusb_strerror(result));
@@ -651,7 +849,7 @@ main(int argc, char *argv[])
struct error *e = NULL;
if (!init_device(device, &e))
exit_fatal("%s", e->message);
if ((result = libusb_claim_interface(device, USB_INTERFACE)) == 1)
if ((result = libusb_claim_interface(device, USB_INTERFACE)))
exit_fatal("couldn't claim interface: %s", libusb_strerror(result));
if (!run(device, frequency, nec, argv, argc, &e)) {
@@ -659,7 +857,7 @@ main(int argc, char *argv[])
error_free(e);
}
if ((result = libusb_release_interface(device, USB_INTERFACE)) == 1)
if ((result = libusb_release_interface(device, USB_INTERFACE)))
exit_fatal("couldn't release interface: %s", libusb_strerror(result));
libusb_close(device);
+1 -1
Submodule liberty updated: 492815c8fc...9268fb8eba
+4 -4
View File
@@ -198,8 +198,8 @@ parse_options(int argc, char *argv[],
};
if (argc == 1) {
show_usage (argv[0]);
exit (EXIT_FAILURE);
show_usage(argv[0]);
exit(EXIT_FAILURE);
}
int c;
@@ -256,7 +256,7 @@ parse_options(int argc, char *argv[],
!strcasecmp(optarg, "on") ||
!strcasecmp(optarg, "yes")) {
new_config->gaming_mode = true;
} else if (!strcasecmp (optarg, "false") ||
} else if (!strcasecmp(optarg, "false") ||
!strcasecmp(optarg, "off") ||
!strcasecmp(optarg, "no")) {
new_config->gaming_mode = false;
@@ -362,7 +362,7 @@ main(int argc, char *argv[])
if ((result = apply_options(device, &options, &new_config)))
FAIL(error_4, "operation failed: %s\n",
libusb_error_name (result));
libusb_error_name(result));
error_4:
if ((result = libusb_release_interface(device, BW_CTL_IFACE)))
FAIL(error_3, "couldn't release interface: %s\n",
Binary file not shown.
Binary file not shown.
+153
View File
@@ -0,0 +1,153 @@
#!/bin/sh -e
# upscat-gen-usb.sh: convert tables from the USB HID specification to C code
#
# Copyright (c) 2026, Přemysl Eric Janouch <p@janouch.name>
# SPDX-License-Identifier: 0BSD
if [ ! -e hut1_7.pdf ]
then curl -LO https://www.usb.org/sites/default/files/hut1_7.pdf
fi
# Our program doesn't need all of them, so we're filtering them here.
process() (qpdf --show-attachment=HidUsageTables.json hut1_7.pdf \
| jq -r '.UsagePages[] | select(.Name | IN("Battery System", "Power")) |
'"$1"' | @tsv' | sort -n -k 1,1 -k 2,2 | awk -F'\t' '
function ident(s) { gsub(/[^[:alnum:]]/, "_", s); return s } '"{ $2 }")
pages='[.Id, .Name]'
usages='. as $page | $page.UsageIds[] |
[$page.Id, .Id, $page.Name, (.Name | gsub("\\\\_"; "_")),
(.Kinds | join("/"))]'
generated='. as $page | .UsageIdGenerator | select(.) |
[$page.Id, .StartUsageId, .EndUsageId, $page.Name, .NamePrefix,
(.Kinds | join("/"))]'
cat <<EOF >upscat-usb.h
// Code generated by $0. DO NOT EDIT.
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
enum {
USB_HID_COLLECTION_PHYSICAL = 0x00,
USB_HID_COLLECTION_APPLICATION = 0x01,
USB_HID_COLLECTION_LOGICAL = 0x02,
USB_HID_COLLECTION_REPORT = 0x03,
USB_HID_COLLECTION_NAMED_ARRAY = 0x04,
USB_HID_COLLECTION_USAGE_SWITCH = 0x05,
USB_HID_COLLECTION_USAGE_MODIFIER = 0x06,
};
enum {
$(process "$pages" '
printf("\t/// %s\n", $2)
printf("\tUSB_HID_USAGE_PAGE__%s = 0x%04x,\n", toupper(ident($2)), $1)
')
};
static const struct usb_hid_usage_page_info {
uint16_t usage_page;
const char *name;
} usb_hid_usage_pages[] = {
$(process "$pages" 'printf("\t{0x%04x, \"%s\"},\n", $1, $2)')
};
$(process "$usages" '
printf("/// %s: %s (%s)\n", $3, $4, $5)
printf("#define USB_HID_USAGE__%s__%s 0x%04x%04x\n",
toupper(ident($3)), toupper(ident($4)), $1, $2)
')
static const struct usb_hid_usage_info {
uint32_t usage;
const char *name;
} usb_hid_usages[] = {
$(process "$usages" 'printf("\t{0x%04x%04x, \"%s\"},\n", $1, $2, $4)')
};
static const struct usb_hid_usage_range_info {
uint32_t usage_start;
uint32_t usage_end;
const char *name_prefix;
} usb_hid_usages_ranged[] = {
$(process "$generated" '
printf("\t// %s (%s)\n", $4, $6)
printf("\t{0x%04x%04x, 0x%04x%04x, \"%s\"},\n", $1, $2, $1, $3, $5)
')
};
static int
usb_hid_usage_pages_cmp(const void *a, const void *b)
{
const uint16_t *key = a;
const struct usb_hid_usage_page_info *entry = b;
return (*key > entry->usage_page) - (*key < entry->usage_page);
}
static int
usb_hid_usages_cmp(const void *a, const void *b)
{
const uint32_t *key = a;
const struct usb_hid_usage_info *entry = b;
return (*key > entry->usage) - (*key < entry->usage);
}
static int
usb_hid_usages_ranged_cmp(const void *a, const void *b)
{
const uint32_t *key = a;
const struct usb_hid_usage_range_info *entry = b;
return (*key > entry->usage_end) - (*key < entry->usage_start);
}
static const char *
usb_hid_usage_page_to_string(uint16_t usage_page)
{
const struct usb_hid_usage_page_info *entry = bsearch(
&usage_page, usb_hid_usage_pages,
sizeof usb_hid_usage_pages / sizeof usb_hid_usage_pages[0],
sizeof usb_hid_usage_pages[0], usb_hid_usage_pages_cmp);
if (entry)
return entry->name;
static char buffer[32];
snprintf(buffer, sizeof buffer, "%04x", usage_page);
return buffer;
}
static const char *
usb_hid_usage_to_string(uint32_t usage)
{
const struct usb_hid_usage_info *info = bsearch(
&usage, usb_hid_usages,
sizeof usb_hid_usages / sizeof usb_hid_usages[0],
sizeof usb_hid_usages[0], usb_hid_usages_cmp);
if (info)
return info->name;
static char buffer[64];
const struct usb_hid_usage_range_info *range = bsearch(
&usage, usb_hid_usages_ranged,
sizeof usb_hid_usages_ranged / sizeof usb_hid_usages_ranged[0],
sizeof usb_hid_usages_ranged[0], usb_hid_usages_ranged_cmp);
if (range) {
snprintf(buffer, sizeof buffer,
"%s %u", range->name_prefix, (unsigned) (usage & 0xffff));
return buffer;
}
snprintf(buffer, sizeof buffer, "%04x", (unsigned) (usage & 0xffff));
return buffer;
}
static const char *
usb_hid_usage_to_string_full(uint32_t usage)
{
static char buffer[128];
snprintf(buffer, sizeof buffer, "%s: %s",
usb_hid_usage_page_to_string(usage >> 16),
usb_hid_usage_to_string(usage));
return buffer;
}
EOF
+623
View File
@@ -0,0 +1,623 @@
// Code generated by ./upscat-gen-usb.sh. DO NOT EDIT.
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
enum {
USB_HID_COLLECTION_PHYSICAL = 0x00,
USB_HID_COLLECTION_APPLICATION = 0x01,
USB_HID_COLLECTION_LOGICAL = 0x02,
USB_HID_COLLECTION_REPORT = 0x03,
USB_HID_COLLECTION_NAMED_ARRAY = 0x04,
USB_HID_COLLECTION_USAGE_SWITCH = 0x05,
USB_HID_COLLECTION_USAGE_MODIFIER = 0x06,
};
enum {
/// Power
USB_HID_USAGE_PAGE__POWER = 0x0084,
/// Battery System
USB_HID_USAGE_PAGE__BATTERY_SYSTEM = 0x0085,
};
static const struct usb_hid_usage_page_info {
uint16_t usage_page;
const char *name;
} usb_hid_usage_pages[] = {
{0x0084, "Power"},
{0x0085, "Battery System"},
};
/// Power: iName (SV)
#define USB_HID_USAGE__POWER__INAME 0x00840001
/// Power: Present Status (CL)
#define USB_HID_USAGE__POWER__PRESENT_STATUS 0x00840002
/// Power: Changed Status (CL)
#define USB_HID_USAGE__POWER__CHANGED_STATUS 0x00840003
/// Power: UPS (CA)
#define USB_HID_USAGE__POWER__UPS 0x00840004
/// Power: Power Supply (CA)
#define USB_HID_USAGE__POWER__POWER_SUPPLY 0x00840005
/// Power: Battery System (CP)
#define USB_HID_USAGE__POWER__BATTERY_SYSTEM 0x00840010
/// Power: Battery System Id (SV)
#define USB_HID_USAGE__POWER__BATTERY_SYSTEM_ID 0x00840011
/// Power: Battery (CP)
#define USB_HID_USAGE__POWER__BATTERY 0x00840012
/// Power: Battery Id (SV)
#define USB_HID_USAGE__POWER__BATTERY_ID 0x00840013
/// Power: Charger (CP)
#define USB_HID_USAGE__POWER__CHARGER 0x00840014
/// Power: Charger Id (SV)
#define USB_HID_USAGE__POWER__CHARGER_ID 0x00840015
/// Power: Power Converter (CP)
#define USB_HID_USAGE__POWER__POWER_CONVERTER 0x00840016
/// Power: Power Converter Id (SV)
#define USB_HID_USAGE__POWER__POWER_CONVERTER_ID 0x00840017
/// Power: Outlet System (CP)
#define USB_HID_USAGE__POWER__OUTLET_SYSTEM 0x00840018
/// Power: Outlet System Id (SV)
#define USB_HID_USAGE__POWER__OUTLET_SYSTEM_ID 0x00840019
/// Power: Input (CP)
#define USB_HID_USAGE__POWER__INPUT 0x0084001a
/// Power: Input Id (SV)
#define USB_HID_USAGE__POWER__INPUT_ID 0x0084001b
/// Power: Output (CP)
#define USB_HID_USAGE__POWER__OUTPUT 0x0084001c
/// Power: Output Id (SV)
#define USB_HID_USAGE__POWER__OUTPUT_ID 0x0084001d
/// Power: Flow (CP)
#define USB_HID_USAGE__POWER__FLOW 0x0084001e
/// Power: Flow Id (SV)
#define USB_HID_USAGE__POWER__FLOW_ID 0x0084001f
/// Power: Outlet (CP)
#define USB_HID_USAGE__POWER__OUTLET 0x00840020
/// Power: Outlet Id (SV)
#define USB_HID_USAGE__POWER__OUTLET_ID 0x00840021
/// Power: Gang (CL/CP)
#define USB_HID_USAGE__POWER__GANG 0x00840022
/// Power: Gang Id (SV)
#define USB_HID_USAGE__POWER__GANG_ID 0x00840023
/// Power: Power Summary (CL/CP)
#define USB_HID_USAGE__POWER__POWER_SUMMARY 0x00840024
/// Power: Power Summary Id (SV)
#define USB_HID_USAGE__POWER__POWER_SUMMARY_ID 0x00840025
/// Power: Voltage (DV)
#define USB_HID_USAGE__POWER__VOLTAGE 0x00840030
/// Power: Current (DV)
#define USB_HID_USAGE__POWER__CURRENT 0x00840031
/// Power: Frequency (DV)
#define USB_HID_USAGE__POWER__FREQUENCY 0x00840032
/// Power: Apparent Power (DV)
#define USB_HID_USAGE__POWER__APPARENT_POWER 0x00840033
/// Power: Active Power (DV)
#define USB_HID_USAGE__POWER__ACTIVE_POWER 0x00840034
/// Power: Percent Load (DV)
#define USB_HID_USAGE__POWER__PERCENT_LOAD 0x00840035
/// Power: Temperature (DV)
#define USB_HID_USAGE__POWER__TEMPERATURE 0x00840036
/// Power: Humidity (DV)
#define USB_HID_USAGE__POWER__HUMIDITY 0x00840037
/// Power: Bad Count (DV)
#define USB_HID_USAGE__POWER__BAD_COUNT 0x00840038
/// Power: Config Voltage (SV/DV)
#define USB_HID_USAGE__POWER__CONFIG_VOLTAGE 0x00840040
/// Power: Config Current (SV/DV)
#define USB_HID_USAGE__POWER__CONFIG_CURRENT 0x00840041
/// Power: Config Frequency (SV/DV)
#define USB_HID_USAGE__POWER__CONFIG_FREQUENCY 0x00840042
/// Power: Config Apparent Power (SV/DV)
#define USB_HID_USAGE__POWER__CONFIG_APPARENT_POWER 0x00840043
/// Power: Config Active Power (SV/DV)
#define USB_HID_USAGE__POWER__CONFIG_ACTIVE_POWER 0x00840044
/// Power: Config Percent Load (SV/DV)
#define USB_HID_USAGE__POWER__CONFIG_PERCENT_LOAD 0x00840045
/// Power: Config Temperature (SV/DV)
#define USB_HID_USAGE__POWER__CONFIG_TEMPERATURE 0x00840046
/// Power: Config Humidity (SV/DV)
#define USB_HID_USAGE__POWER__CONFIG_HUMIDITY 0x00840047
/// Power: Switch On Control (DV)
#define USB_HID_USAGE__POWER__SWITCH_ON_CONTROL 0x00840050
/// Power: Switch Off Control (DV)
#define USB_HID_USAGE__POWER__SWITCH_OFF_CONTROL 0x00840051
/// Power: Toggle Control (DV)
#define USB_HID_USAGE__POWER__TOGGLE_CONTROL 0x00840052
/// Power: Low Voltage Transfer (DV)
#define USB_HID_USAGE__POWER__LOW_VOLTAGE_TRANSFER 0x00840053
/// Power: High Voltage Transfer (DV)
#define USB_HID_USAGE__POWER__HIGH_VOLTAGE_TRANSFER 0x00840054
/// Power: Delay Before Reboot (DV)
#define USB_HID_USAGE__POWER__DELAY_BEFORE_REBOOT 0x00840055
/// Power: Delay Before Startup (DV)
#define USB_HID_USAGE__POWER__DELAY_BEFORE_STARTUP 0x00840056
/// Power: Delay Before Shutdown (DV)
#define USB_HID_USAGE__POWER__DELAY_BEFORE_SHUTDOWN 0x00840057
/// Power: Test (DV)
#define USB_HID_USAGE__POWER__TEST 0x00840058
/// Power: Module Reset (DV)
#define USB_HID_USAGE__POWER__MODULE_RESET 0x00840059
/// Power: Audible Alarm Control (DV)
#define USB_HID_USAGE__POWER__AUDIBLE_ALARM_CONTROL 0x0084005a
/// Power: Present (DF)
#define USB_HID_USAGE__POWER__PRESENT 0x00840060
/// Power: Good (DF)
#define USB_HID_USAGE__POWER__GOOD 0x00840061
/// Power: Internal Failure (DF)
#define USB_HID_USAGE__POWER__INTERNAL_FAILURE 0x00840062
/// Power: Voltag Out Of Range (DF)
#define USB_HID_USAGE__POWER__VOLTAG_OUT_OF_RANGE 0x00840063
/// Power: Frequency Out Of Range (DF)
#define USB_HID_USAGE__POWER__FREQUENCY_OUT_OF_RANGE 0x00840064
/// Power: Overload (DF)
#define USB_HID_USAGE__POWER__OVERLOAD 0x00840065
/// Power: Over Charged (DF)
#define USB_HID_USAGE__POWER__OVER_CHARGED 0x00840066
/// Power: Over Temperature (DF)
#define USB_HID_USAGE__POWER__OVER_TEMPERATURE 0x00840067
/// Power: Shutdown Requested (DF)
#define USB_HID_USAGE__POWER__SHUTDOWN_REQUESTED 0x00840068
/// Power: Shutdown Imminent (DF)
#define USB_HID_USAGE__POWER__SHUTDOWN_IMMINENT 0x00840069
/// Power: Switch On/Off (DF)
#define USB_HID_USAGE__POWER__SWITCH_ON_OFF 0x0084006b
/// Power: Switchable (DF)
#define USB_HID_USAGE__POWER__SWITCHABLE 0x0084006c
/// Power: Used (DF)
#define USB_HID_USAGE__POWER__USED 0x0084006d
/// Power: Boost (DF)
#define USB_HID_USAGE__POWER__BOOST 0x0084006e
/// Power: Buck (DF)
#define USB_HID_USAGE__POWER__BUCK 0x0084006f
/// Power: Initialized (DF)
#define USB_HID_USAGE__POWER__INITIALIZED 0x00840070
/// Power: Tested (DF)
#define USB_HID_USAGE__POWER__TESTED 0x00840071
/// Power: Awaiting Power (DF)
#define USB_HID_USAGE__POWER__AWAITING_POWER 0x00840072
/// Power: Communication Lost (DF)
#define USB_HID_USAGE__POWER__COMMUNICATION_LOST 0x00840073
/// Power: iManufacturer (SV)
#define USB_HID_USAGE__POWER__IMANUFACTURER 0x008400fd
/// Power: iProduct (SV)
#define USB_HID_USAGE__POWER__IPRODUCT 0x008400fe
/// Power: iSerialNumber (SV)
#define USB_HID_USAGE__POWER__ISERIALNUMBER 0x008400ff
/// Battery System: Smart Battery Battery Mode (CL)
#define USB_HID_USAGE__BATTERY_SYSTEM__SMART_BATTERY_BATTERY_MODE 0x00850001
/// Battery System: Smart Battery Battery Status (NAry)
#define USB_HID_USAGE__BATTERY_SYSTEM__SMART_BATTERY_BATTERY_STATUS 0x00850002
/// Battery System: Smart Battery Alarm Warning (NAry)
#define USB_HID_USAGE__BATTERY_SYSTEM__SMART_BATTERY_ALARM_WARNING 0x00850003
/// Battery System: Smart Battery Charger Mode (CL)
#define USB_HID_USAGE__BATTERY_SYSTEM__SMART_BATTERY_CHARGER_MODE 0x00850004
/// Battery System: Smart Battery Charger Status (CL)
#define USB_HID_USAGE__BATTERY_SYSTEM__SMART_BATTERY_CHARGER_STATUS 0x00850005
/// Battery System: Smart Battery Charger Spec Info (CL)
#define USB_HID_USAGE__BATTERY_SYSTEM__SMART_BATTERY_CHARGER_SPEC_INFO 0x00850006
/// Battery System: Smart Battery Selector State (CL)
#define USB_HID_USAGE__BATTERY_SYSTEM__SMART_BATTERY_SELECTOR_STATE 0x00850007
/// Battery System: Smart Battery Selector Presets (CL)
#define USB_HID_USAGE__BATTERY_SYSTEM__SMART_BATTERY_SELECTOR_PRESETS 0x00850008
/// Battery System: Smart Battery Selector Info (CL)
#define USB_HID_USAGE__BATTERY_SYSTEM__SMART_BATTERY_SELECTOR_INFO 0x00850009
/// Battery System: Optional Mfg Function 1 (DV)
#define USB_HID_USAGE__BATTERY_SYSTEM__OPTIONAL_MFG_FUNCTION_1 0x00850010
/// Battery System: Optional Mfg Function 2 (DV)
#define USB_HID_USAGE__BATTERY_SYSTEM__OPTIONAL_MFG_FUNCTION_2 0x00850011
/// Battery System: Optional Mfg Function 3 (DV)
#define USB_HID_USAGE__BATTERY_SYSTEM__OPTIONAL_MFG_FUNCTION_3 0x00850012
/// Battery System: Optional Mfg Function 4 (DV)
#define USB_HID_USAGE__BATTERY_SYSTEM__OPTIONAL_MFG_FUNCTION_4 0x00850013
/// Battery System: Optional Mfg Function 5 (DV)
#define USB_HID_USAGE__BATTERY_SYSTEM__OPTIONAL_MFG_FUNCTION_5 0x00850014
/// Battery System: Connection To SM Bus (DF)
#define USB_HID_USAGE__BATTERY_SYSTEM__CONNECTION_TO_SM_BUS 0x00850015
/// Battery System: Output Connection (DF)
#define USB_HID_USAGE__BATTERY_SYSTEM__OUTPUT_CONNECTION 0x00850016
/// Battery System: Charger Connection (DF)
#define USB_HID_USAGE__BATTERY_SYSTEM__CHARGER_CONNECTION 0x00850017
/// Battery System: Battery Insertion (DF)
#define USB_HID_USAGE__BATTERY_SYSTEM__BATTERY_INSERTION 0x00850018
/// Battery System: Use Next (DF)
#define USB_HID_USAGE__BATTERY_SYSTEM__USE_NEXT 0x00850019
/// Battery System: OK To Use (DF)
#define USB_HID_USAGE__BATTERY_SYSTEM__OK_TO_USE 0x0085001a
/// Battery System: Battery Supported (DF)
#define USB_HID_USAGE__BATTERY_SYSTEM__BATTERY_SUPPORTED 0x0085001b
/// Battery System: Selector Revision (DF)
#define USB_HID_USAGE__BATTERY_SYSTEM__SELECTOR_REVISION 0x0085001c
/// Battery System: Charging Indicator (DF)
#define USB_HID_USAGE__BATTERY_SYSTEM__CHARGING_INDICATOR 0x0085001d
/// Battery System: Manufacturer Access (DV)
#define USB_HID_USAGE__BATTERY_SYSTEM__MANUFACTURER_ACCESS 0x00850028
/// Battery System: Remaining Capacity Limit (DV)
#define USB_HID_USAGE__BATTERY_SYSTEM__REMAINING_CAPACITY_LIMIT 0x00850029
/// Battery System: Remaining Time Limit (DV)
#define USB_HID_USAGE__BATTERY_SYSTEM__REMAINING_TIME_LIMIT 0x0085002a
/// Battery System: At Rate (DV)
#define USB_HID_USAGE__BATTERY_SYSTEM__AT_RATE 0x0085002b
/// Battery System: Capacity Mode (DV)
#define USB_HID_USAGE__BATTERY_SYSTEM__CAPACITY_MODE 0x0085002c
/// Battery System: Broadcast To Charger (DV)
#define USB_HID_USAGE__BATTERY_SYSTEM__BROADCAST_TO_CHARGER 0x0085002d
/// Battery System: Primary Battery (DV)
#define USB_HID_USAGE__BATTERY_SYSTEM__PRIMARY_BATTERY 0x0085002e
/// Battery System: Charge Controller (DV)
#define USB_HID_USAGE__BATTERY_SYSTEM__CHARGE_CONTROLLER 0x0085002f
/// Battery System: Terminate Charge (Sel)
#define USB_HID_USAGE__BATTERY_SYSTEM__TERMINATE_CHARGE 0x00850040
/// Battery System: Terminate Discharge (Sel)
#define USB_HID_USAGE__BATTERY_SYSTEM__TERMINATE_DISCHARGE 0x00850041
/// Battery System: Below Remaining Capacity Limit (Sel)
#define USB_HID_USAGE__BATTERY_SYSTEM__BELOW_REMAINING_CAPACITY_LIMIT 0x00850042
/// Battery System: Remaining Time Limit Expired (Sel)
#define USB_HID_USAGE__BATTERY_SYSTEM__REMAINING_TIME_LIMIT_EXPIRED 0x00850043
/// Battery System: Charging (Sel)
#define USB_HID_USAGE__BATTERY_SYSTEM__CHARGING 0x00850044
/// Battery System: Discharging (Sel)
#define USB_HID_USAGE__BATTERY_SYSTEM__DISCHARGING 0x00850045
/// Battery System: Fully Charged (Sel)
#define USB_HID_USAGE__BATTERY_SYSTEM__FULLY_CHARGED 0x00850046
/// Battery System: Fully Discharged (Sel)
#define USB_HID_USAGE__BATTERY_SYSTEM__FULLY_DISCHARGED 0x00850047
/// Battery System: Conditioning Flag (DF)
#define USB_HID_USAGE__BATTERY_SYSTEM__CONDITIONING_FLAG 0x00850048
/// Battery System: At Rate OK (DF)
#define USB_HID_USAGE__BATTERY_SYSTEM__AT_RATE_OK 0x00850049
/// Battery System: Smart Battery Error Code (DV)
#define USB_HID_USAGE__BATTERY_SYSTEM__SMART_BATTERY_ERROR_CODE 0x0085004a
/// Battery System: Need Replacement (DF)
#define USB_HID_USAGE__BATTERY_SYSTEM__NEED_REPLACEMENT 0x0085004b
/// Battery System: At Rate Time To Full (DV)
#define USB_HID_USAGE__BATTERY_SYSTEM__AT_RATE_TIME_TO_FULL 0x00850060
/// Battery System: At Rate Time To Empty (DV)
#define USB_HID_USAGE__BATTERY_SYSTEM__AT_RATE_TIME_TO_EMPTY 0x00850061
/// Battery System: Average Current (DV)
#define USB_HID_USAGE__BATTERY_SYSTEM__AVERAGE_CURRENT 0x00850062
/// Battery System: Max Error (DV)
#define USB_HID_USAGE__BATTERY_SYSTEM__MAX_ERROR 0x00850063
/// Battery System: Relative State Of Charge (DV)
#define USB_HID_USAGE__BATTERY_SYSTEM__RELATIVE_STATE_OF_CHARGE 0x00850064
/// Battery System: Absolute State Of Charge (DV)
#define USB_HID_USAGE__BATTERY_SYSTEM__ABSOLUTE_STATE_OF_CHARGE 0x00850065
/// Battery System: Remaining Capacity (DV)
#define USB_HID_USAGE__BATTERY_SYSTEM__REMAINING_CAPACITY 0x00850066
/// Battery System: Full Charge Capacity (DV)
#define USB_HID_USAGE__BATTERY_SYSTEM__FULL_CHARGE_CAPACITY 0x00850067
/// Battery System: Run Time To Empty (DV)
#define USB_HID_USAGE__BATTERY_SYSTEM__RUN_TIME_TO_EMPTY 0x00850068
/// Battery System: Average Time To Empty (DV)
#define USB_HID_USAGE__BATTERY_SYSTEM__AVERAGE_TIME_TO_EMPTY 0x00850069
/// Battery System: Average Time To Full (DV)
#define USB_HID_USAGE__BATTERY_SYSTEM__AVERAGE_TIME_TO_FULL 0x0085006a
/// Battery System: Cycle Count (DV)
#define USB_HID_USAGE__BATTERY_SYSTEM__CYCLE_COUNT 0x0085006b
/// Battery System: Battery Pack Model Level (SV)
#define USB_HID_USAGE__BATTERY_SYSTEM__BATTERY_PACK_MODEL_LEVEL 0x00850080
/// Battery System: Internal Charge Controller (SF)
#define USB_HID_USAGE__BATTERY_SYSTEM__INTERNAL_CHARGE_CONTROLLER 0x00850081
/// Battery System: Primary Battery Support (SF)
#define USB_HID_USAGE__BATTERY_SYSTEM__PRIMARY_BATTERY_SUPPORT 0x00850082
/// Battery System: Design Capacity (SV)
#define USB_HID_USAGE__BATTERY_SYSTEM__DESIGN_CAPACITY 0x00850083
/// Battery System: Specification Info (SV)
#define USB_HID_USAGE__BATTERY_SYSTEM__SPECIFICATION_INFO 0x00850084
/// Battery System: Manufacture Date (SV)
#define USB_HID_USAGE__BATTERY_SYSTEM__MANUFACTURE_DATE 0x00850085
/// Battery System: Serial Number (SV)
#define USB_HID_USAGE__BATTERY_SYSTEM__SERIAL_NUMBER 0x00850086
/// Battery System: iManufacturer Name (SV)
#define USB_HID_USAGE__BATTERY_SYSTEM__IMANUFACTURER_NAME 0x00850087
/// Battery System: iDevice Name (SV)
#define USB_HID_USAGE__BATTERY_SYSTEM__IDEVICE_NAME 0x00850088
/// Battery System: iDevice Chemistry (SV)
#define USB_HID_USAGE__BATTERY_SYSTEM__IDEVICE_CHEMISTRY 0x00850089
/// Battery System: Manufacturer Data (SV)
#define USB_HID_USAGE__BATTERY_SYSTEM__MANUFACTURER_DATA 0x0085008a
/// Battery System: Rechargable (SV)
#define USB_HID_USAGE__BATTERY_SYSTEM__RECHARGABLE 0x0085008b
/// Battery System: Warning Capacity Limit (SV)
#define USB_HID_USAGE__BATTERY_SYSTEM__WARNING_CAPACITY_LIMIT 0x0085008c
/// Battery System: Capacity Granularity 1 (SV)
#define USB_HID_USAGE__BATTERY_SYSTEM__CAPACITY_GRANULARITY_1 0x0085008d
/// Battery System: Capacity Granularity 2 (SV)
#define USB_HID_USAGE__BATTERY_SYSTEM__CAPACITY_GRANULARITY_2 0x0085008e
/// Battery System: iOEM Information (SV)
#define USB_HID_USAGE__BATTERY_SYSTEM__IOEM_INFORMATION 0x0085008f
/// Battery System: Inhibit Charge (DF)
#define USB_HID_USAGE__BATTERY_SYSTEM__INHIBIT_CHARGE 0x008500c0
/// Battery System: Enable Polling (DF)
#define USB_HID_USAGE__BATTERY_SYSTEM__ENABLE_POLLING 0x008500c1
/// Battery System: Reset To Zero (DF)
#define USB_HID_USAGE__BATTERY_SYSTEM__RESET_TO_ZERO 0x008500c2
/// Battery System: AC Present (DV)
#define USB_HID_USAGE__BATTERY_SYSTEM__AC_PRESENT 0x008500d0
/// Battery System: Battery Present (DV)
#define USB_HID_USAGE__BATTERY_SYSTEM__BATTERY_PRESENT 0x008500d1
/// Battery System: Power Fail (DV)
#define USB_HID_USAGE__BATTERY_SYSTEM__POWER_FAIL 0x008500d2
/// Battery System: Alarm Inhibited (DV)
#define USB_HID_USAGE__BATTERY_SYSTEM__ALARM_INHIBITED 0x008500d3
/// Battery System: Thermistor Under Range (DV)
#define USB_HID_USAGE__BATTERY_SYSTEM__THERMISTOR_UNDER_RANGE 0x008500d4
/// Battery System: Thermistor Hot (DV)
#define USB_HID_USAGE__BATTERY_SYSTEM__THERMISTOR_HOT 0x008500d5
/// Battery System: Thermistor Cold (DV)
#define USB_HID_USAGE__BATTERY_SYSTEM__THERMISTOR_COLD 0x008500d6
/// Battery System: Thermistor Over Range (DV)
#define USB_HID_USAGE__BATTERY_SYSTEM__THERMISTOR_OVER_RANGE 0x008500d7
/// Battery System: Voltage Out Of Range (DV)
#define USB_HID_USAGE__BATTERY_SYSTEM__VOLTAGE_OUT_OF_RANGE 0x008500d8
/// Battery System: Current Out Of Range (DV)
#define USB_HID_USAGE__BATTERY_SYSTEM__CURRENT_OUT_OF_RANGE 0x008500d9
/// Battery System: Current Not Regulated (DV)
#define USB_HID_USAGE__BATTERY_SYSTEM__CURRENT_NOT_REGULATED 0x008500da
/// Battery System: Voltage Not Regulated (DV)
#define USB_HID_USAGE__BATTERY_SYSTEM__VOLTAGE_NOT_REGULATED 0x008500db
/// Battery System: Master Mode (DV)
#define USB_HID_USAGE__BATTERY_SYSTEM__MASTER_MODE 0x008500dc
/// Battery System: Charger Selector Support (SF)
#define USB_HID_USAGE__BATTERY_SYSTEM__CHARGER_SELECTOR_SUPPORT 0x008500f0
/// Battery System: Charger Spec (SV)
#define USB_HID_USAGE__BATTERY_SYSTEM__CHARGER_SPEC 0x008500f1
/// Battery System: Level 2 (SF)
#define USB_HID_USAGE__BATTERY_SYSTEM__LEVEL_2 0x008500f2
/// Battery System: Level 3 (SF)
#define USB_HID_USAGE__BATTERY_SYSTEM__LEVEL_3 0x008500f3
static const struct usb_hid_usage_info {
uint32_t usage;
const char *name;
} usb_hid_usages[] = {
{0x00840001, "iName"},
{0x00840002, "Present Status"},
{0x00840003, "Changed Status"},
{0x00840004, "UPS"},
{0x00840005, "Power Supply"},
{0x00840010, "Battery System"},
{0x00840011, "Battery System Id"},
{0x00840012, "Battery"},
{0x00840013, "Battery Id"},
{0x00840014, "Charger"},
{0x00840015, "Charger Id"},
{0x00840016, "Power Converter"},
{0x00840017, "Power Converter Id"},
{0x00840018, "Outlet System"},
{0x00840019, "Outlet System Id"},
{0x0084001a, "Input"},
{0x0084001b, "Input Id"},
{0x0084001c, "Output"},
{0x0084001d, "Output Id"},
{0x0084001e, "Flow"},
{0x0084001f, "Flow Id"},
{0x00840020, "Outlet"},
{0x00840021, "Outlet Id"},
{0x00840022, "Gang"},
{0x00840023, "Gang Id"},
{0x00840024, "Power Summary"},
{0x00840025, "Power Summary Id"},
{0x00840030, "Voltage"},
{0x00840031, "Current"},
{0x00840032, "Frequency"},
{0x00840033, "Apparent Power"},
{0x00840034, "Active Power"},
{0x00840035, "Percent Load"},
{0x00840036, "Temperature"},
{0x00840037, "Humidity"},
{0x00840038, "Bad Count"},
{0x00840040, "Config Voltage"},
{0x00840041, "Config Current"},
{0x00840042, "Config Frequency"},
{0x00840043, "Config Apparent Power"},
{0x00840044, "Config Active Power"},
{0x00840045, "Config Percent Load"},
{0x00840046, "Config Temperature"},
{0x00840047, "Config Humidity"},
{0x00840050, "Switch On Control"},
{0x00840051, "Switch Off Control"},
{0x00840052, "Toggle Control"},
{0x00840053, "Low Voltage Transfer"},
{0x00840054, "High Voltage Transfer"},
{0x00840055, "Delay Before Reboot"},
{0x00840056, "Delay Before Startup"},
{0x00840057, "Delay Before Shutdown"},
{0x00840058, "Test"},
{0x00840059, "Module Reset"},
{0x0084005a, "Audible Alarm Control"},
{0x00840060, "Present"},
{0x00840061, "Good"},
{0x00840062, "Internal Failure"},
{0x00840063, "Voltag Out Of Range"},
{0x00840064, "Frequency Out Of Range"},
{0x00840065, "Overload"},
{0x00840066, "Over Charged"},
{0x00840067, "Over Temperature"},
{0x00840068, "Shutdown Requested"},
{0x00840069, "Shutdown Imminent"},
{0x0084006b, "Switch On/Off"},
{0x0084006c, "Switchable"},
{0x0084006d, "Used"},
{0x0084006e, "Boost"},
{0x0084006f, "Buck"},
{0x00840070, "Initialized"},
{0x00840071, "Tested"},
{0x00840072, "Awaiting Power"},
{0x00840073, "Communication Lost"},
{0x008400fd, "iManufacturer"},
{0x008400fe, "iProduct"},
{0x008400ff, "iSerialNumber"},
{0x00850001, "Smart Battery Battery Mode"},
{0x00850002, "Smart Battery Battery Status"},
{0x00850003, "Smart Battery Alarm Warning"},
{0x00850004, "Smart Battery Charger Mode"},
{0x00850005, "Smart Battery Charger Status"},
{0x00850006, "Smart Battery Charger Spec Info"},
{0x00850007, "Smart Battery Selector State"},
{0x00850008, "Smart Battery Selector Presets"},
{0x00850009, "Smart Battery Selector Info"},
{0x00850010, "Optional Mfg Function 1"},
{0x00850011, "Optional Mfg Function 2"},
{0x00850012, "Optional Mfg Function 3"},
{0x00850013, "Optional Mfg Function 4"},
{0x00850014, "Optional Mfg Function 5"},
{0x00850015, "Connection To SM Bus"},
{0x00850016, "Output Connection"},
{0x00850017, "Charger Connection"},
{0x00850018, "Battery Insertion"},
{0x00850019, "Use Next"},
{0x0085001a, "OK To Use"},
{0x0085001b, "Battery Supported"},
{0x0085001c, "Selector Revision"},
{0x0085001d, "Charging Indicator"},
{0x00850028, "Manufacturer Access"},
{0x00850029, "Remaining Capacity Limit"},
{0x0085002a, "Remaining Time Limit"},
{0x0085002b, "At Rate"},
{0x0085002c, "Capacity Mode"},
{0x0085002d, "Broadcast To Charger"},
{0x0085002e, "Primary Battery"},
{0x0085002f, "Charge Controller"},
{0x00850040, "Terminate Charge"},
{0x00850041, "Terminate Discharge"},
{0x00850042, "Below Remaining Capacity Limit"},
{0x00850043, "Remaining Time Limit Expired"},
{0x00850044, "Charging"},
{0x00850045, "Discharging"},
{0x00850046, "Fully Charged"},
{0x00850047, "Fully Discharged"},
{0x00850048, "Conditioning Flag"},
{0x00850049, "At Rate OK"},
{0x0085004a, "Smart Battery Error Code"},
{0x0085004b, "Need Replacement"},
{0x00850060, "At Rate Time To Full"},
{0x00850061, "At Rate Time To Empty"},
{0x00850062, "Average Current"},
{0x00850063, "Max Error"},
{0x00850064, "Relative State Of Charge"},
{0x00850065, "Absolute State Of Charge"},
{0x00850066, "Remaining Capacity"},
{0x00850067, "Full Charge Capacity"},
{0x00850068, "Run Time To Empty"},
{0x00850069, "Average Time To Empty"},
{0x0085006a, "Average Time To Full"},
{0x0085006b, "Cycle Count"},
{0x00850080, "Battery Pack Model Level"},
{0x00850081, "Internal Charge Controller"},
{0x00850082, "Primary Battery Support"},
{0x00850083, "Design Capacity"},
{0x00850084, "Specification Info"},
{0x00850085, "Manufacture Date"},
{0x00850086, "Serial Number"},
{0x00850087, "iManufacturer Name"},
{0x00850088, "iDevice Name"},
{0x00850089, "iDevice Chemistry"},
{0x0085008a, "Manufacturer Data"},
{0x0085008b, "Rechargable"},
{0x0085008c, "Warning Capacity Limit"},
{0x0085008d, "Capacity Granularity 1"},
{0x0085008e, "Capacity Granularity 2"},
{0x0085008f, "iOEM Information"},
{0x008500c0, "Inhibit Charge"},
{0x008500c1, "Enable Polling"},
{0x008500c2, "Reset To Zero"},
{0x008500d0, "AC Present"},
{0x008500d1, "Battery Present"},
{0x008500d2, "Power Fail"},
{0x008500d3, "Alarm Inhibited"},
{0x008500d4, "Thermistor Under Range"},
{0x008500d5, "Thermistor Hot"},
{0x008500d6, "Thermistor Cold"},
{0x008500d7, "Thermistor Over Range"},
{0x008500d8, "Voltage Out Of Range"},
{0x008500d9, "Current Out Of Range"},
{0x008500da, "Current Not Regulated"},
{0x008500db, "Voltage Not Regulated"},
{0x008500dc, "Master Mode"},
{0x008500f0, "Charger Selector Support"},
{0x008500f1, "Charger Spec"},
{0x008500f2, "Level 2"},
{0x008500f3, "Level 3"},
};
static const struct usb_hid_usage_range_info {
uint32_t usage_start;
uint32_t usage_end;
const char *name_prefix;
} usb_hid_usages_ranged[] = {
};
static int
usb_hid_usage_pages_cmp(const void *a, const void *b)
{
const uint16_t *key = a;
const struct usb_hid_usage_page_info *entry = b;
return (*key > entry->usage_page) - (*key < entry->usage_page);
}
static int
usb_hid_usages_cmp(const void *a, const void *b)
{
const uint32_t *key = a;
const struct usb_hid_usage_info *entry = b;
return (*key > entry->usage) - (*key < entry->usage);
}
static int
usb_hid_usages_ranged_cmp(const void *a, const void *b)
{
const uint32_t *key = a;
const struct usb_hid_usage_range_info *entry = b;
return (*key > entry->usage_end) - (*key < entry->usage_start);
}
static const char *
usb_hid_usage_page_to_string(uint16_t usage_page)
{
const struct usb_hid_usage_page_info *entry = bsearch(
&usage_page, usb_hid_usage_pages,
sizeof usb_hid_usage_pages / sizeof usb_hid_usage_pages[0],
sizeof usb_hid_usage_pages[0], usb_hid_usage_pages_cmp);
if (entry)
return entry->name;
static char buffer[32];
snprintf(buffer, sizeof buffer, "%04x", usage_page);
return buffer;
}
static const char *
usb_hid_usage_to_string(uint32_t usage)
{
const struct usb_hid_usage_info *info = bsearch(
&usage, usb_hid_usages,
sizeof usb_hid_usages / sizeof usb_hid_usages[0],
sizeof usb_hid_usages[0], usb_hid_usages_cmp);
if (info)
return info->name;
static char buffer[64];
const struct usb_hid_usage_range_info *range = bsearch(
&usage, usb_hid_usages_ranged,
sizeof usb_hid_usages_ranged / sizeof usb_hid_usages_ranged[0],
sizeof usb_hid_usages_ranged[0], usb_hid_usages_ranged_cmp);
if (range) {
snprintf(buffer, sizeof buffer,
"%s %u", range->name_prefix, (unsigned) (usage & 0xffff));
return buffer;
}
snprintf(buffer, sizeof buffer, "%04x", (unsigned) (usage & 0xffff));
return buffer;
}
static const char *
usb_hid_usage_to_string_full(uint32_t usage)
{
static char buffer[128];
snprintf(buffer, sizeof buffer, "%s: %s",
usb_hid_usage_page_to_string(usage >> 16),
usb_hid_usage_to_string(usage));
return buffer;
}
+1003
View File
File diff suppressed because it is too large Load Diff