Compare commits

..

No commits in common. "master" and "v1.0.0" have entirely different histories.

12 changed files with 117 additions and 846 deletions

View File

@ -1,5 +1,5 @@
cmake_minimum_required (VERSION 3.12) cmake_minimum_required (VERSION 3.10)
project (usb-drivers VERSION 1.1.0 project (usb-drivers VERSION 1.0.0
DESCRIPTION "User space USB drivers" LANGUAGES C) DESCRIPTION "User space USB drivers" LANGUAGES C)
# Moar warnings # Moar warnings
@ -34,21 +34,10 @@ endif ()
# Dependencies # Dependencies
set (CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/liberty/cmake) set (CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/liberty/cmake)
include (IconUtils)
find_package (PkgConfig REQUIRED) find_package (PkgConfig REQUIRED)
pkg_check_modules (libusb libusb-1.0) 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 (WIN32)
pkg_search_module (hidapi hidapi hidapi-hidraw hidapi-libusb)
else ()
find_package (hidapi)
set (hidapi_INCLUDE_DIRS)
set (hidapi_LIBRARY_DIRS)
set (hidapi_LIBRARIES hidapi::hidapi)
endif ()
option (WITH_LIBUSB "Compile with libusb-based utilities" ${libusb_FOUND}) option (WITH_LIBUSB "Compile with libusb-based utilities" ${libusb_FOUND})
option (WITH_HIDAPI "Compile with hidapi-based utilities" ${hidapi_FOUND}) option (WITH_HIDAPI "Compile with hidapi-based utilities" ${hidapi_FOUND})
@ -96,6 +85,7 @@ endif ()
if (WITH_HIDAPI AND WIN32) if (WITH_HIDAPI AND WIN32)
list (APPEND targets_gui eizoctltray) list (APPEND targets_gui eizoctltray)
include (IconUtils)
set (icon_png_list) set (icon_png_list)
foreach (icon_size 16 32 48) foreach (icon_size 16 32 48)
icon_to_png (eizoctltray ${PROJECT_SOURCE_DIR}/eizoctltray.svg icon_to_png (eizoctltray ${PROJECT_SOURCE_DIR}/eizoctltray.svg
@ -107,6 +97,7 @@ if (WITH_HIDAPI AND WIN32)
set (icon_ico ${PROJECT_BINARY_DIR}/eizoctltray.ico) set (icon_ico ${PROJECT_BINARY_DIR}/eizoctltray.ico)
icon_for_win32 (${icon_ico} "${icon_png_list}" "${icon_png}") icon_for_win32 (${icon_ico} "${icon_png_list}" "${icon_png}")
list (APPEND icon_ico_list )
set_property (SOURCE eizoctltray.rc set_property (SOURCE eizoctltray.rc
APPEND PROPERTY OBJECT_DEPENDS ${icon_ico}) APPEND PROPERTY OBJECT_DEPENDS ${icon_ico})
@ -117,78 +108,47 @@ if (WITH_HIDAPI AND WIN32)
target_link_directories (eizoctltray PUBLIC ${hidapi_LIBRARY_DIRS}) target_link_directories (eizoctltray PUBLIC ${hidapi_LIBRARY_DIRS})
target_link_libraries (eizoctltray ${hidapi_LIBRARIES} powrprof) target_link_libraries (eizoctltray ${hidapi_LIBRARIES} powrprof)
endif () 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 # Generate documentation from help output
if (NOT WIN32 AND NOT CMAKE_CROSSCOMPILING) if (NOT CMAKE_CROSSCOMPILING)
set (HELP2ADOC "${PROJECT_SOURCE_DIR}/liberty/tools/help2adoc.awk") find_program (HELP2MAN_EXECUTABLE help2man)
set (ASCIIMAN "${PROJECT_SOURCE_DIR}/liberty/tools/asciiman.awk") if (NOT HELP2MAN_EXECUTABLE)
message (FATAL_ERROR "help2man not found")
endif ()
foreach (target ${targets}) foreach (target ${targets})
set (page_adoc "${PROJECT_BINARY_DIR}/${target}.1.adoc") set (page_output "${PROJECT_BINARY_DIR}/${target}.1")
set (page_roff "${PROJECT_BINARY_DIR}/${target}.1") list (APPEND project_MAN_PAGES "${page_output}")
list (APPEND project_MAN_PAGES "${page_roff}") add_custom_command (OUTPUT ${page_output}
COMMAND ${HELP2MAN_EXECUTABLE} -N
# $<TARGET_FILE:tgt> could be used, if we didn't have to escape it. "${PROJECT_BINARY_DIR}/${target}" -o ${page_output}
string (REPLACE "\\" "\\\\" DEPENDS ${target}
target_path "${PROJECT_BINARY_DIR}/${target}") COMMENT "Generating man page for ${target}" VERBATIM)
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 () endforeach ()
add_custom_target (docs ALL DEPENDS ${project_MAN_PAGES}) add_custom_target (docs ALL DEPENDS ${project_MAN_PAGES})
endif () endif ()
# The files to be installed # The files to be installed
if (NOT WIN32) include (GNUInstallDirs)
include (GNUInstallDirs)
# These should be accessible by users, but need to touch system devices. # These should be accessible by users, but need to touch system devices.
# Use the setuid bit, for simplicity. # Use the setuid bit, for simplicity.
set (SETUID "SETUID" CACHE STRING "Set this empty on permission issues") set (SETUID "SETUID" CACHE STRING "Set this empty on permission issues")
install (TARGETS ${targets} DESTINATION ${CMAKE_INSTALL_BINDIR} install (TARGETS ${targets} DESTINATION ${CMAKE_INSTALL_BINDIR}
PERMISSIONS PERMISSIONS
OWNER_WRITE OWNER_READ OWNER_EXECUTE OWNER_WRITE OWNER_READ OWNER_EXECUTE
GROUP_READ GROUP_EXECUTE GROUP_READ GROUP_EXECUTE
WORLD_READ WORLD_EXECUTE WORLD_READ WORLD_EXECUTE
${SETUID}) ${SETUID})
install (TARGETS ${targets_gui} DESTINATION ${CMAKE_INSTALL_BINDIR}) install (TARGETS ${targets_gui} DESTINATION ${CMAKE_INSTALL_BINDIR})
install (FILES LICENSE DESTINATION ${CMAKE_INSTALL_DOCDIR}) 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 ()
set (CPACK_SET_DESTDIR TRUE) foreach (page ${project_MAN_PAGES})
else () string (REGEX MATCH "\\.([0-9])$" manpage_suffix "${page}")
install (TARGETS ${targets} ${targets_gui} DESTINATION .) install (FILES "${page}"
endif () DESTINATION "${CMAKE_INSTALL_MANDIR}/man${CMAKE_MATCH_1}")
endforeach ()
# CPack # CPack
set (CPACK_PACKAGE_VENDOR "Premysl Eric Janouch") set (CPACK_PACKAGE_VENDOR "Premysl Eric Janouch")
@ -202,4 +162,5 @@ set (CPACK_SOURCE_GENERATOR "TGZ;ZIP")
set (CPACK_SOURCE_IGNORE_FILES "/\\\\.git;/build;/CMakeLists.txt.user") set (CPACK_SOURCE_IGNORE_FILES "/\\\\.git;/build;/CMakeLists.txt.user")
set (CPACK_SOURCE_PACKAGE_FILE_NAME "${PROJECT_NAME}-${PROJECT_VERSION}") set (CPACK_SOURCE_PACKAGE_FILE_NAME "${PROJECT_NAME}-${PROJECT_VERSION}")
set (CPACK_SET_DESTDIR TRUE)
include (CPack) include (CPack)

View File

@ -1,4 +1,4 @@
Copyright (c) 2013, 2024 - 2025, Přemysl Eric Janouch <p@janouch.name> Copyright (c) 2013, 2024, Přemysl Eric Janouch <p@janouch.name>
Permission to use, copy, modify, and/or distribute this software for any Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted. purpose with or without fee is hereby granted.

16
NEWS
View File

@ -1,16 +0,0 @@
Unreleased
* eizoctl: added a --quiet option to suppress information and/or errors
* 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

View File

@ -1,7 +1,6 @@
USB drivers USB drivers
=========== ===========
:compact-option: :compact-option:
:source-highlighter: chroma
_usb-drivers_ is a collection of utilities to control various hardware over USB. _usb-drivers_ is a collection of utilities to control various hardware over USB.
@ -20,66 +19,11 @@ and may not run at the same time, as it would contend for device access.
eizoctltray eizoctltray
~~~~~~~~~~~ ~~~~~~~~~~~
_eizoctltray_ is a derived Windows/macOS utility that can stay in the systray. _eizoctltray_ is a derived Windows utility that can stay in the systray.
When holding the Shift or Control keys while switching signal inputs, When holding the Shift or Control keys while switching singnal inputs,
it will also suspend or power off the system, respectively. it will also suspend or power off the system, respectively.
image:eizoctltray-win.png["eizoctltray on Windows with expanded menu", 343, 278] image::eizoctltray.png["eizoctltray with expanded context menu", 343, 229]
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 elksmart-comm
~~~~~~~~~~~~~ ~~~~~~~~~~~~~
@ -108,13 +52,13 @@ Regular releases are sporadic. git master should be stable enough.
You can get a package with the latest development version You can get a package with the latest development version
as a https://git.janouch.name/p/nixexprs[Nix derivation]. as a https://git.janouch.name/p/nixexprs[Nix derivation].
Windows/macOS binaries can be downloaded from Windows binaries can be downloaded from
https://git.janouch.name/p/usb-drivers/releases[the Releases page on Gitea]. https://git.janouch.name/p/usb-drivers/releases[the Releases page on Gitea].
Building Building
-------- --------
Build dependencies: Build dependencies:
CMake, pkg-config, liberty (included) + CMake, pkg-config, liberty (included), help2man +
Runtime dependencies: Runtime dependencies:
libusb-1.0 (elksmart-comm, razer-bw-te-ctl), hidapi >= 0.14 (eizoctl) libusb-1.0 (elksmart-comm, razer-bw-te-ctl), hidapi >= 0.14 (eizoctl)
@ -133,29 +77,6 @@ Or you can try telling CMake to make a package for you. For Debian it is:
$ cpack -G DEB $ cpack -G DEB
# dpkg -i usb-drivers-*.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 Contributing and Support
------------------------ ------------------------
Use https://git.janouch.name/p/usb-drivers to report bugs, request features, Use https://git.janouch.name/p/usb-drivers to report bugs, request features,

View File

@ -1,240 +0,0 @@
// 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)
}
}
}

483
eizoctl.c
View File

@ -4,7 +4,7 @@
* This program stays independent of the liberty library * This program stays independent of the liberty library
* in order to build on Windows. * in order to build on Windows.
* *
* Copyright (c) 2025, Přemysl Eric Janouch <p@janouch.name> * Copyright (c) 2024, Přemysl Eric Janouch <p@janouch.name>
* *
* Permission to use, copy, modify, and/or distribute this software for any * Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted. * purpose with or without fee is hereby granted.
@ -18,14 +18,6 @@
* CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * 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 <stdarg.h>
#include <stdbool.h> #include <stdbool.h>
#include <stdint.h> #include <stdint.h>
@ -49,9 +41,7 @@
#define hid_init hidapi_hid_init #define hid_init hidapi_hid_init
#endif #endif
#if defined __MINGW_GNU_PRINTF #if defined __GNUC__
#define ATTRIBUTE_PRINTF(x, y) __MINGW_GNU_PRINTF((x), (y))
#elif defined __GNUC__
#define ATTRIBUTE_PRINTF(x, y) __attribute__((format(printf, x, y))) #define ATTRIBUTE_PRINTF(x, y) __attribute__((format(printf, x, y)))
#else #else
#define ATTRIBUTE_PRINTF(x, y) #define ATTRIBUTE_PRINTF(x, y)
@ -813,7 +803,7 @@ eizo_port_by_name(const char *name)
return index; return index;
} }
static const char * static char *
eizo_port_to_name(uint16_t port) eizo_port_to_name(uint16_t port)
{ {
const char *stem = NULL; const char *stem = NULL;
@ -821,14 +811,14 @@ eizo_port_to_name(uint16_t port)
if (group && group < sizeof g_port_names / sizeof g_port_names[0]) if (group && group < sizeof g_port_names / sizeof g_port_names[0])
stem = g_port_names[group][0]; stem = g_port_names[group][0];
static char buf[32] = ""; static char buffer[32] = "";
if (!stem) if (!stem)
snprintf(buf, sizeof buf, "%x", port); snprintf(buffer, sizeof buffer, "%x", port);
else if (!number) else if (!number)
snprintf(buf, sizeof buf, "%s", stem); snprintf(buffer, sizeof buffer, "%s", stem);
else else
snprintf(buf, sizeof buf, "%s %d", stem, (number + 1)); snprintf(buffer, sizeof buffer, "%s %d", stem, number);
return buf; return buffer;
} }
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
@ -890,22 +880,8 @@ eizo_get_input_port(struct eizo_monitor *m, uint16_t *port)
return true; 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 static uint16_t
eizo_resolve_port_by_name(struct eizo_monitor *m, const char *port) eizo_resolve_port(struct eizo_monitor *m, const char *port)
{ {
uint8_t usb_c_index = 0; uint8_t usb_c_index = 0;
if (eizo_port_by_name_in_group(port, g_port_names_usb_c, &usb_c_index)) { if (eizo_port_by_name_in_group(port, g_port_names_usb_c, &usb_c_index)) {
@ -917,26 +893,6 @@ eizo_resolve_port_by_name(struct eizo_monitor *m, const char *port)
return eizo_port_by_name(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 static bool
eizo_set_input_port(struct eizo_monitor *m, uint16_t port) eizo_set_input_port(struct eizo_monitor *m, uint16_t port)
{ {
@ -964,40 +920,8 @@ eizo_restart(struct eizo_monitor *m)
// --- Main -------------------------------------------------------------------- // --- 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 static bool
eizo_watch(struct eizo_monitor *m, print_fn output, print_fn error) eizo_watch(struct eizo_monitor *m)
{ {
uint8_t buf[1024] = {}; uint8_t buf[1024] = {};
int res = 0; int res = 0;
@ -1007,85 +931,58 @@ eizo_watch(struct eizo_monitor *m, print_fn output, print_fn error)
if (buf[0] != EIZO_REPORT_ID_GET && if (buf[0] != EIZO_REPORT_ID_GET &&
buf[0] != EIZO_REPORT_ID_GET_LONG) { buf[0] != EIZO_REPORT_ID_GET_LONG) {
error("Unknown report ID: %02x\n", buf[0]); printf("Unknown report ID\n");
continue; continue;
} }
struct catbuf message = {{0}, 0};
uint16_t page = peek_u16le(&buf[1]), id = peek_u16le(&buf[3]); uint16_t page = peek_u16le(&buf[1]), id = peek_u16le(&buf[3]);
uint32_t usage = page << 16 | id; uint32_t usage = page << 16 | id;
catf(&message, "%08x", usage); printf("%08x", usage);
const struct parser_report *r = eizo_monitor_subreport(m, usage); const struct parser_report *r = eizo_monitor_subreport(m, usage);
if (!r) { if (!r) {
output(catf(&message, " unknown usage\n")); printf(" unknown usage\n");
continue; continue;
} }
size_t rlen = r->report_size / 8 * r->report_count; size_t rlen = r->report_size / 8 * r->report_count;
if ((size_t) res < 7 + rlen) { if ((size_t) res < 7 + rlen) {
output(catf(&message, " received data too short\n")); printf(" received data too short\n");
continue; continue;
} }
if (r->report_size == 16) if (r->report_size == 16)
for (size_t i = 0; i + 1 < rlen; i += 2) for (size_t i = 0; i + 1 < rlen; i += 2)
catf(&message, " %04x", peek_u16le(&buf[7 + i])); printf(" %04x", peek_u16le(&buf[7 + i]));
else else
for (size_t i = 0; i < rlen; i++) for (size_t i = 0; i < rlen; i++)
catf(&message, " %02x", buf[7 + i]); printf(" %02x", buf[7 + i]);
output(catf(&message, "\n")); printf("\n");
} }
} }
static const char *usage = "Usage: %s OPTION...\n\n" typedef void (*print_fn)(const char *format, ...) ATTRIBUTE_PRINTF(1, 2);
" -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 static int
run(int argc, char *argv[], print_fn output, print_fn error) run(int argc, char *argv[], print_fn output, print_fn error, bool verbose)
{ {
const char *name = argv[0]; const char *name = argv[0];
const char *usage = "Usage: %s [--brightness [+-]BRIGHTNESS] [--input NAME]"
" [--restart] [--events]\n";
static struct option opts[] = { static struct option opts[] = {
{"list", no_argument, NULL, 'l'},
{"serial", required_argument, NULL, 's'},
{"brightness", required_argument, NULL, 'b'},
{"input", required_argument, NULL, 'i'}, {"input", required_argument, NULL, 'i'},
{"brightness", required_argument, NULL, 'b'},
{"restart", no_argument, NULL, 'r'}, {"restart", no_argument, NULL, 'r'},
{"events", no_argument, NULL, 'e'}, {"events", no_argument, NULL, 'e'},
{"quiet", no_argument, NULL, 'q'},
{"help", no_argument, NULL, 'h'}, {"help", no_argument, NULL, 'h'},
{"version", no_argument, NULL, 'V'}, {"version", no_argument, NULL, 'V'},
{} {}
}; };
int quiet = 0;
double brightness = NAN; double brightness = NAN;
bool list = false, relative = false, restart = false, events = false; bool relative = false, restart = false, events = false;
const char *serial = NULL, *port = NULL; const char *port = NULL;
int c = 0; int c = 0;
while ((c = getopt_long(argc, argv, "ls:b:i:reqhV", opts, NULL)) != -1) while ((c = getopt_long(argc, argv, "b:i:h", opts, NULL)) != -1)
switch (c) { switch (c) {
case 'l':
list = true;
break;
case 's':
serial = optarg;
break;
case 'b': case 'b':
relative = *optarg == '+' || *optarg == '-'; relative = *optarg == '+' || *optarg == '-';
if (sscanf(optarg, "%lf", &brightness) && isfinite(brightness)) if (sscanf(optarg, "%lf", &brightness) && isfinite(brightness))
@ -1102,9 +999,6 @@ run(int argc, char *argv[], print_fn output, print_fn error)
case 'e': case 'e':
events = true; events = true;
break; break;
case 'q':
quiet++;
break;
case 'h': case 'h':
output(usage, name); output(usage, name);
return 0; return 0;
@ -1130,11 +1024,9 @@ run(int argc, char *argv[], print_fn output, print_fn error)
error("%ls\n", hid_error(NULL)); error("%ls\n", hid_error(NULL));
return 1; 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; struct hid_device_info *devs = hid_enumerate(USB_VID_EIZO, 0), *p = devs;
for (; p; p = p->next) { for (; p; p = p->next) {
struct eizo_monitor m = {}; struct eizo_monitor m = {};
@ -1143,15 +1035,6 @@ run(int argc, char *argv[], print_fn output, print_fn error)
continue; 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)) { if (isfinite(brightness)) {
double prev = 0.; double prev = 0.;
if (!eizo_get_brightness(&m, &prev)) { if (!eizo_get_brightness(&m, &prev)) {
@ -1160,25 +1043,25 @@ run(int argc, char *argv[], print_fn output, print_fn error)
double next = relative ? brightness + prev : brightness; double next = relative ? brightness + prev : brightness;
if (!eizo_set_brightness(&m, next)) if (!eizo_set_brightness(&m, next))
error("Failed to set brightness: %s\n", m.error); error("Failed to set brightness: %s\n", m.error);
else else if (verbose)
output("%s %s: brightness: %.2f -> %.2f\n", output("%s %s: brightness: %.2f -> %.2f\n",
m.product, m.serial, prev, next); m.product, m.serial, prev, next);
} }
} }
if (port) { if (port) {
uint16_t prev = 0; uint16_t prev = 0;
uint16_t next = eizo_resolve_port_by_name(&m, port); uint16_t next = eizo_resolve_port(&m, port);
if (!eizo_get_input_port(&m, &prev)) { if (!eizo_get_input_port(&m, &prev)) {
error("Failed to get input port: %s\n", m.error); error("Failed to get input port: %s\n", m.error);
} else if (!strcmp(port, "?")) { } else if (!strcmp(port, "?")) {
output("%s %s: input: %s\n", output("%s %s: input: %s\n",
m.product, m.serial, eizo_resolve_port_to_name(&m, prev)); m.product, m.serial, eizo_port_to_name(prev));
} else if (!next) { } else if (!next) {
error("Failed to resolve port name: %s\n", port); error("Failed to resolve port name: %s\n", port);
} else { } else {
if (!eizo_set_input_port(&m, next)) if (!eizo_set_input_port(&m, next))
error("Failed to set input port: %s\n", m.error); error("Failed to set input port: %s\n", m.error);
else else if (verbose)
output("%s %s: input: %s -> %s\n", output("%s %s: input: %s -> %s\n",
m.product, m.serial, eizo_port_to_name(prev), port); m.product, m.serial, eizo_port_to_name(prev), port);
} }
@ -1186,17 +1069,16 @@ run(int argc, char *argv[], print_fn output, print_fn error)
if (restart) { if (restart) {
if (!eizo_restart(&m)) if (!eizo_restart(&m))
error("Failed to restart: %s\n", m.error); error("Failed to restart: %s\n", m.error);
else else if (verbose)
output("%s %s: restart\n", m.product, m.serial); output("%s %s: restart\n", m.product, m.serial);
} }
if (events) { if (events) {
if (quiet) if (!verbose)
error("Watching events is not possible in this mode\n"); error("Watching events is not possible in this mode\n");
else if (!eizo_watch(&m, output, error)) else if (!eizo_watch(&m))
error("%s\n", m.error); error("%s\n", m.error);
} }
next:
eizo_monitor_close(&m); eizo_monitor_close(&m);
} }
hid_free_enumeration(devs); hid_free_enumeration(devs);
@ -1228,11 +1110,11 @@ stdio_error(const char *format, ...)
int int
main(int argc, char *argv[]) main(int argc, char *argv[])
{ {
return run(argc, argv, stdio_output, stdio_error); return run(argc, argv, stdio_output, stdio_error, true);
} }
// --- Windows ----------------------------------------------------------------- // --- Windows -----------------------------------------------------------------
#elif defined _WIN32 #else
#define WIN32_LEAN_AND_MEAN #define WIN32_LEAN_AND_MEAN
#include <windows.h> #include <windows.h>
@ -1271,8 +1153,8 @@ message_output(const char *format, ...)
wchar_t *message = message_printf(format, ap); wchar_t *message = message_printf(format, ap);
va_end(ap); va_end(ap);
if (message) { if (message) {
MessageBox(NULL, message, MessageBox(
L"Message", MB_ICONINFORMATION | MB_OK | MB_APPLMODAL); NULL, message, NULL, MB_ICONINFORMATION | MB_OK | MB_APPLMODAL);
free(message); free(message);
} }
} }
@ -1326,15 +1208,36 @@ append_monitor(struct eizo_monitor *m, HMENU menu, UINT_PTR base)
AppendMenu(menu, flags_darker, base + IDM_DARKER, L"Darker"); AppendMenu(menu, flags_darker, base + IDM_DARKER, L"Darker");
AppendMenu(menu, MF_SEPARATOR, 0, NULL); AppendMenu(menu, MF_SEPARATOR, 0, NULL);
uint16_t ports[16] = {0}, current = 0; uint16_t ports[16] = {0};
eizo_get_input_ports(m, ports, sizeof ports / sizeof ports[0] - 1); 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;
(void) eizo_get_input_port(m, &current); (void) eizo_get_input_port(m, &current);
if (!ports[0]) if (!ports[0])
ports[0] = current; 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++) { for (size_t i = 0; ports[i]; i++) {
snwprintf(buf, sizeof buf, L"%s", uint8_t usb_c = 0;
eizo_resolve_port_to_name(m, ports[i])); 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);
UINT flags = MF_STRING; UINT flags = MF_STRING;
if (ports[i] == current) if (ports[i] == current)
@ -1487,7 +1390,7 @@ wWinMain(
char *mb = mbargv[i + 1] = calloc(len, sizeof *mb); char *mb = mbargv[i + 1] = calloc(len, sizeof *mb);
wcstombs(mb, argv[i], len); wcstombs(mb, argv[i], len);
} }
return run(argc + 1, mbargv, message_output, message_error); return run(argc + 1, mbargv, message_output, message_error, false);
} }
LocalFree(argv); LocalFree(argv);
@ -1539,262 +1442,4 @@ wWinMain(
return msg.wParam; return msg.wParam;
} }
// --- macOS -------------------------------------------------------------------
#elif defined __APPLE__
#include <AppKit/AppKit.h>
#include <AppKit/NSStatusBar.h>
#include <Foundation/Foundation.h>
static void message_output(const char *format, ...) ATTRIBUTE_PRINTF(1, 2);
static void message_error(const char *format, ...) ATTRIBUTE_PRINTF(1, 2);
static void
message_output(const char *format, ...)
{
va_list ap;
va_start(ap, format);
NSString *message = [[NSString alloc]
initWithFormat:[NSString stringWithUTF8String: format] arguments:ap];
va_end(ap);
NSAlert *alert = [NSAlert new];
[alert setMessageText:message];
[alert setAlertStyle:NSAlertStyleInformational];
// XXX: How to make the OK button the first responder?
[alert addButtonWithTitle:@"OK"];
[NSApp activate];
[alert.window makeKeyAndOrderFront:nil];
[alert runModal];
}
static void
message_error(const char *format, ...)
{
va_list ap;
va_start(ap, format);
NSString *message = [[NSString alloc]
initWithFormat:[NSString stringWithUTF8String: format] arguments:ap];
va_end(ap);
NSAlert *alert = [NSAlert new];
[alert setMessageText:message];
[alert setAlertStyle:NSAlertStyleCritical];
[alert addButtonWithTitle:@"OK"];
[NSApp activate];
[alert.window makeKeyAndOrderFront:nil];
[alert runModal];
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
/// Monitor provides reference counting, and enables use of NSArray.
@interface Monitor : NSObject
@property (assign, nonatomic) struct eizo_monitor *monitor;
- (instancetype)initWithMonitor:(struct eizo_monitor *)monitor;
@end
@implementation Monitor
- (instancetype)initWithMonitor:(struct eizo_monitor *)monitor {
if (self = [super init]) {
_monitor = monitor;
}
return self;
}
- (void)dealloc {
if (_monitor) {
eizo_monitor_close(_monitor);
free(_monitor);
_monitor = NULL;
}
}
@end
@interface ApplicationDelegate
: NSObject <NSApplicationDelegate, NSMenuDelegate>
@property (strong, nonatomic) NSStatusItem *statusItem;
@property (strong, nonatomic) NSMutableArray<Monitor *> *monitors;
@end
@implementation ApplicationDelegate
- (Monitor *)getMonitorFrom:(NSControl *)control {
NSInteger index = control.tag / 0x1000;
if (!self.monitors || index < 0 || index >= self.monitors.count)
return nil;
return self.monitors[index];
}
- (void)setBrightness:(NSControl *)sender {
Monitor *m = [self getMonitorFrom:sender];
if (!m)
return;
eizo_set_brightness(m.monitor, sender.doubleValue);
}
- (void)setInputPort:(NSControl *)sender {
Monitor *m = [self getMonitorFrom:sender];
NSUInteger input = sender.tag % 0x1000;
if (!m)
return;
eizo_set_input_port(m.monitor, input);
NSEventModifierFlags mods = [NSEvent modifierFlags];
if (mods & NSEventModifierFlagShift) {
NSTask *task = [[NSTask alloc] init];
task.launchPath = @"/usr/bin/pmset";
task.arguments = @[@"sleepnow"];
[task launch];
}
}
- (void)appendMonitor:(Monitor *)m toMenu:(NSMenu *)menu base:(NSInteger)base {
NSMenuItem *titleItem = [NSMenuItem new];
titleItem.attributedTitle = [[NSAttributedString alloc]
initWithString:[NSString stringWithFormat:@"%s %s",
m.monitor->product, m.monitor->serial]
attributes:@{ NSFontAttributeName: [NSFont boldSystemFontOfSize:0] }];
[menu addItem:titleItem];
[menu addItem:[NSMenuItem separatorItem]];
[menu addItem:[NSMenuItem sectionHeaderWithTitle:@"Brightness"]];
double brightness = 0;
(void) eizo_get_brightness(m.monitor, &brightness);
// XXX: So, while having a slider is strictly more useful,
// this is not something you're supposed to do in AppKit, if only because:
// - It does not respond to keyboard.
// - Positioning it properly is dark magic.
NSSlider *slider = [NSSlider
sliderWithValue:brightness minValue:0. maxValue:1.
target:self action:@selector(setBrightness:)];
slider.tag = base;
slider.continuous = true;
NSView *sliderView = [[NSView alloc]
initWithFrame:NSMakeRect(0, 0, 200., slider.knobThickness + 2.)];
[sliderView addSubview:slider];
slider.translatesAutoresizingMaskIntoConstraints = false;
[NSLayoutConstraint activateConstraints:@[
[slider.leftAnchor
constraintEqualToAnchor:sliderView.leftAnchor constant:+23.],
[slider.rightAnchor
constraintEqualToAnchor:sliderView.rightAnchor constant:-6.],
[slider.centerYAnchor
constraintEqualToAnchor:sliderView.centerYAnchor]
]];
NSMenuItem *brightnessItem = [[NSMenuItem alloc]
initWithTitle:@"" action:nil keyEquivalent:@""];
brightnessItem.view = sliderView;
[menu addItem:brightnessItem];
[menu addItem:[NSMenuItem separatorItem]];
[menu addItem:[NSMenuItem sectionHeaderWithTitle:@"Input ports"]];
uint16_t ports[16] = {0}, current = 0;
eizo_get_input_ports(m.monitor, ports, sizeof ports / sizeof ports[0] - 1);
(void) eizo_get_input_port(m.monitor, &current);
if (!ports[0])
ports[0] = current;
for (size_t i = 0; ports[i]; i++) {
NSString *title = [NSString stringWithUTF8String:
eizo_resolve_port_to_name(m.monitor, ports[i])];
NSMenuItem *inputPortItem = [[NSMenuItem alloc]
initWithTitle:title action:@selector(setInputPort:)
keyEquivalent:@""];
inputPortItem.tag = base + ports[i];
if (ports[i] == current)
inputPortItem.state = NSControlStateValueOn;
[menu addItem:inputPortItem];
}
}
- (void)showMenu {
struct hid_device_info *devs = hid_enumerate(USB_VID_EIZO, 0);
NSMutableArray<Monitor *> *monitors = [NSMutableArray array];
NSMenu *menu = [NSMenu new];
[menu setDelegate:self];
for (struct hid_device_info *p = devs; p; p = p->next) {
struct eizo_monitor *m = calloc(1, sizeof *m);
if (!m)
continue;
if (!eizo_monitor_open(m, p)) {
message_error("%s", m->error);
free(m);
continue;
}
Monitor *monitor = [[Monitor alloc] initWithMonitor:m];
[self appendMonitor:monitor toMenu:menu base:0x1000 * monitors.count];
[menu addItem:[NSMenuItem separatorItem]];
[monitors addObject:monitor];
}
if (!monitors.count) {
NSMenuItem *item = [[NSMenuItem alloc]
initWithTitle:@"No monitors found" action:nil keyEquivalent:@""];
item.enabled = false;
[menu addItem:item];
}
[menu addItem:[NSMenuItem separatorItem]];
[menu addItem:[[NSMenuItem alloc]
initWithTitle:@"Quit" action:@selector(terminate:) keyEquivalent:@"q"]];
self.monitors = monitors;
// XXX: Unfortunately, this is not how menus should behave,
// but we really want to generate the menu on demand.
self.statusItem.menu = menu;
[self.statusItem.button performClick:nil];
self.statusItem.menu = nil;
}
- (void)menuDidClose:(NSMenu *)menu {
// Close and free up the devices as soon as possible, but no sooner.
dispatch_async(dispatch_get_main_queue(), ^{
self.monitors = nil;
});
}
- (void)applicationDidFinishLaunching:(NSNotification *)notification {
NSStatusBar *systemBar = [NSStatusBar systemStatusBar];
self.statusItem = [systemBar statusItemWithLength:NSSquareStatusItemLength];
if (!self.statusItem.button)
return;
// Not bothering with templates,
// the icon would need to have a hole through it to look better.
NSImage *image = [NSApp applicationIconImage];
// One would expect the status bar to pick a reasonable size
// automatically, but that is not what happens.
image.size = NSMakeSize(systemBar.thickness, systemBar.thickness);
self.statusItem.button.image = image;
self.statusItem.button.action = @selector(showMenu);
}
@end
int
main(int argc, char *argv[])
{
@autoreleasepool {
if (argc > 1)
return run(argc, argv, message_output, message_error);
NSApplication *app = [NSApplication sharedApplication];
ApplicationDelegate *delegate = [ApplicationDelegate new];
app.delegate = delegate;
[app setActivationPolicy:NSApplicationActivationPolicyAccessory];
[app run];
}
return 0;
}
#endif #endif

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.7 KiB

BIN
eizoctltray.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.7 KiB

View File

@ -196,7 +196,7 @@ compress_value(unsigned value, struct str *encoded)
} }
static void 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]; unsigned counts[len];
memset(counts, 0, sizeof counts); memset(counts, 0, sizeof counts);
@ -527,15 +527,15 @@ send_identify(libusb_device_handle *device, struct error **e)
#if 0 #if 0
// The EKX4S does not respond to this request. // The EKX4S does not respond to this request.
static uint8_t c_serial[] = { -5, -5, -5, -5 }; 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))) c_serial, sizeof c_serial, &len, 100)))
return error_set(e, "serial/send: %s", libusb_strerror(result)); return error_set (e, "serial/send: %s", libusb_strerror (result));
if ((result = libusb_bulk_transfer(device, g.endpoint_in, if ((result = libusb_bulk_transfer (device, g.endpoint_in,
buffer, sizeof buffer, &len, 100))) 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 || if (len < (int) sizeof c_serial ||
memcmp(buffer, c_serial, sizeof c_serial)) memcmp (buffer, c_serial, sizeof c_serial))
return error_set(e, "serial retrieval failed"); return error_set (e, "serial retrieval failed");
#endif #endif
return true; return true;
} }

@ -1 +1 @@
Subproject commit 9268fb8eba4a60499809965b3b69c2eb7e3798e7 Subproject commit 492815c8fc38ad6e333b2f1c5094a329e3076155

View File

@ -198,8 +198,8 @@ parse_options(int argc, char *argv[],
}; };
if (argc == 1) { if (argc == 1) {
show_usage(argv[0]); show_usage (argv[0]);
exit(EXIT_FAILURE); exit (EXIT_FAILURE);
} }
int c; int c;
@ -256,7 +256,7 @@ parse_options(int argc, char *argv[],
!strcasecmp(optarg, "on") || !strcasecmp(optarg, "on") ||
!strcasecmp(optarg, "yes")) { !strcasecmp(optarg, "yes")) {
new_config->gaming_mode = true; new_config->gaming_mode = true;
} else if (!strcasecmp(optarg, "false") || } else if (!strcasecmp (optarg, "false") ||
!strcasecmp(optarg, "off") || !strcasecmp(optarg, "off") ||
!strcasecmp(optarg, "no")) { !strcasecmp(optarg, "no")) {
new_config->gaming_mode = false; new_config->gaming_mode = false;
@ -362,7 +362,7 @@ main(int argc, char *argv[])
if ((result = apply_options(device, &options, &new_config))) if ((result = apply_options(device, &options, &new_config)))
FAIL(error_4, "operation failed: %s\n", FAIL(error_4, "operation failed: %s\n",
libusb_error_name(result)); libusb_error_name (result));
error_4: error_4:
if ((result = libusb_release_interface(device, BW_CTL_IFACE))) if ((result = libusb_release_interface(device, BW_CTL_IFACE)))
FAIL(error_3, "couldn't release interface: %s\n", FAIL(error_3, "couldn't release interface: %s\n",