Compare commits
38 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
d48653a270
|
|||
|
0f22f385fd
|
|||
|
dfcab40ca4
|
|||
|
0a1cf529de
|
|||
|
7fc34885a7
|
|||
|
5a5409c4a0
|
|||
|
fa4bfd80df
|
|||
|
2b9d655656
|
|||
|
74af8e884c
|
|||
|
8b5aff8081
|
|||
|
f2e586906f
|
|||
|
2c30ddfa3d
|
|||
|
97b86c168e
|
|||
|
41e928da52
|
|||
|
ae195bf855
|
|||
|
5035750647
|
|||
|
8eb738b093
|
|||
|
c6f47a0981
|
|||
|
c8f3b9ba38
|
|||
|
d8b01cdaee
|
|||
|
135f336a7c
|
|||
|
b4c1817c10
|
|||
|
32ea934947
|
|||
|
55984bc7ef
|
|||
|
dab190e857
|
|||
|
2594f8467d
|
|||
|
9039db44f6
|
|||
|
89cab6fa39
|
|||
|
aea9c334e0
|
|||
|
e53cddb030
|
|||
|
8832ba2227
|
|||
|
7bd6993b59
|
|||
|
8717f425f4
|
|||
|
7e008c154b
|
|||
|
9d619115be
|
|||
|
02da76e958
|
|||
|
be50fd4b8f
|
|||
|
781fd392fe
|
+110
-37
@@ -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,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.
|
||||
|
||||
@@ -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
@@ -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
@@ -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
|
||||
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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, ¤t);
|
||||
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, ¤t);
|
||||
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 |
Binary file not shown.
|
Before Width: | Height: | Size: 7.7 KiB |
+239
-41
@@ -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
@@ -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.
Executable
+153
@@ -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
@@ -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;
|
||||
}
|
||||
Reference in New Issue
Block a user