├── .github └── workflows │ ├── ci.yaml │ └── codeql.yaml ├── .gitignore ├── CMakeLists.txt ├── COPYING ├── README.md ├── Scripts ├── clightd.service ├── i2c_clightd.conf ├── org.clightd.clightd.conf ├── org.clightd.clightd.policy └── org.clightd.clightd.service ├── TODO.md ├── protocol ├── CMakeLists.txt ├── org_kde_kwin_dpms.xml ├── wlr-gamma-control-unstable-v1.xml ├── wlr-output-power-management-unstable-v1.xml └── wlr-screencopy-unstable-v1.xml └── src ├── commons.h ├── main.c ├── modules ├── backlight.c ├── backlight.h ├── backlight2.c ├── backlight_plugins │ ├── ddc.c │ └── sysfs.c ├── bus.c ├── dpms.c ├── dpms.h ├── dpms_plugins │ ├── drm.c │ ├── kwin_wl.c │ ├── wl.c │ └── xorg.c ├── gamma.c ├── gamma.h ├── gamma_plugins │ ├── drm.c │ ├── wl.c │ └── xorg.c ├── idle.c ├── keyboard.c ├── screen.c ├── screen.h ├── screen_plugins │ ├── fb.c │ ├── wl.c │ └── xorg.c ├── sensor.c ├── sensor.h ├── sensors │ ├── als.c │ ├── als.h │ ├── camera.c │ ├── camera.h │ ├── custom.c │ ├── pipewire.c │ └── yoctolight.c └── signal.c └── utils ├── bus_utils.c ├── bus_utils.h ├── drm_utils.c ├── drm_utils.h ├── polkit.c ├── polkit.h ├── udev.c ├── udev.h ├── wl_utils.c ├── wl_utils.h ├── xorg_utils.c └── xorg_utils.h /.github/workflows/ci.yaml: -------------------------------------------------------------------------------- 1 | name: CI Build 2 | on: 3 | pull_request: 4 | push: 5 | branches: 6 | - master 7 | workflow_dispatch: 8 | 9 | jobs: 10 | build-ubuntu-amd64: 11 | name: build-ubuntu-amd64 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: actions/checkout@v3 15 | - name: Check out libmodule 16 | uses: actions/checkout@v3 17 | with: 18 | repository: fededp/libmodule 19 | ref: '5.0.1' 20 | path: libmodule 21 | - name: Install deps 22 | run: | 23 | sudo apt update 24 | sudo apt install -y --no-install-recommends build-essential pkg-config cmake libsystemd-dev libxrandr-dev libxext-dev policykit-1 libpolkit-gobject-1-dev libjpeg-dev libusb-dev libwayland-dev libdrm-dev libddcutil-dev libusb-1.0-0-dev libudev-dev libpipewire-0.3-dev libdbus-1-dev libiio-dev 25 | - name: Install libmodule 26 | run: | 27 | cd libmodule 28 | mkdir build && cd build 29 | cmake -G "Unix Makefiles" -DCMAKE_INSTALL_PREFIX=/usr -DCMAKE_INSTALL_LIBDIR=lib -DCMAKE_BUILD_TYPE="Release" .. 30 | make 31 | sudo make install 32 | - name: Create build configurations 33 | run: | 34 | mkdir build build-no-gamma build-no-dpms build-no-ddc build-no-screen build-no-yoctolight build-no-pipewire build-no-extras 35 | cd build && cmake -DCMAKE_BUILD_TYPE=Debug -DENABLE_GAMMA=1 -DENABLE_DPMS=1 -DENABLE_DDC=1 -DENABLE_SCREEN=1 -DENABLE_YOCTOLIGHT=1 -DENABLE_PIPEWIRE=1 .. 36 | cd ../build-no-gamma && cmake -DCMAKE_BUILD_TYPE=Debug -DENABLE_DPMS=1 -DENABLE_DDC=1 -DENABLE_SCREEN=1 -DENABLE_YOCTOLIGHT=1 -DENABLE_PIPEWIRE=1 .. 37 | cd ../build-no-dpms && cmake -DCMAKE_BUILD_TYPE=Debug -DENABLE_GAMMA=1 -DENABLE_DDC=1 -DENABLE_SCREEN=1 -DENABLE_YOCTOLIGHT=1 -DENABLE_PIPEWIRE=1 .. 38 | cd ../build-no-ddc && cmake -DCMAKE_BUILD_TYPE=Debug -DENABLE_GAMMA=1 -DENABLE_DPMS=1 -DENABLE_SCREEN=1 -DENABLE_YOCTOLIGHT=1 -DENABLE_PIPEWIRE=1 .. 39 | cd ../build-no-screen && cmake -DCMAKE_BUILD_TYPE=Debug -DENABLE_GAMMA=1 -DENABLE_DPMS=1 -DENABLE_DDC=1 -DENABLE_YOCTOLIGHT=1 -DENABLE_PIPEWIRE=1 .. 40 | cd ../build-no-yoctolight && cmake -DCMAKE_BUILD_TYPE=Debug -DENABLE_GAMMA=1 -DENABLE_DPMS=1 -DENABLE_DDC=1 -DENABLE_SCREEN=1 -DENABLE_PIPEWIRE=1 .. 41 | cd ../build-no-pipewire && cmake -DCMAKE_BUILD_TYPE=Debug -DENABLE_GAMMA=1 -DENABLE_DPMS=1 -DENABLE_DDC=1 -DENABLE_SCREEN=1 -DENABLE_YOCTOLIGHT=1 .. 42 | cd ../build-no-extras && cmake -DCMAKE_BUILD_TYPE=Debug .. 43 | - name: Build 44 | run: | 45 | cd build && make 46 | cd ../build-no-gamma && make 47 | cd ../build-no-dpms && make 48 | cd ../build-no-ddc && make 49 | cd ../build-no-screen && make 50 | cd ../build-no-yoctolight && make 51 | cd ../build-no-pipewire && make 52 | cd ../build-no-extras && make 53 | -------------------------------------------------------------------------------- /.github/workflows/codeql.yaml: -------------------------------------------------------------------------------- 1 | # For most projects, this workflow file will not need changing; you simply need 2 | # to commit it to your repository. 3 | # 4 | # You may wish to alter this file to override the set of languages analyzed, 5 | # or to provide custom queries or build logic. 6 | # 7 | # ******** NOTE ******** 8 | # We have attempted to detect the languages in your repository. Please check 9 | # the `language` matrix defined below to confirm you have the correct set of 10 | # supported CodeQL languages. 11 | # 12 | name: "CodeQL" 13 | 14 | on: 15 | push: 16 | branches: [ "master" ] 17 | pull_request: 18 | branches: [ "master" ] 19 | 20 | jobs: 21 | analyze: 22 | name: Analyze 23 | runs-on: ubuntu-latest 24 | permissions: 25 | actions: read 26 | contents: read 27 | security-events: write 28 | 29 | strategy: 30 | fail-fast: false 31 | matrix: 32 | language: [ 'cpp' ] 33 | 34 | steps: 35 | - name: Checkout repository 36 | uses: actions/checkout@v3 37 | with: 38 | fetch-depth: 0 39 | 40 | - name: Check out libmodule 41 | uses: actions/checkout@v3 42 | with: 43 | repository: fededp/libmodule 44 | ref: '5.0.1' 45 | path: libmodule 46 | 47 | - name: Initialize CodeQL 48 | uses: github/codeql-action/init@v2 49 | with: 50 | languages: ${{ matrix.language }} 51 | 52 | - name: Update base image 53 | run: sudo apt update -y 54 | 55 | - name: Install build dependencies 56 | run: sudo DEBIAN_FRONTEND=noninteractive apt install build-essential pkg-config cmake libsystemd-dev libxrandr-dev libxext-dev policykit-1 libpolkit-gobject-1-dev libjpeg-dev libusb-dev libwayland-dev libdrm-dev libddcutil-dev libusb-1.0-0-dev libudev-dev libpipewire-0.3-dev libdbus-1-dev libiio-dev -y 57 | 58 | - name: Install libmodule 59 | run: | 60 | cd libmodule 61 | mkdir build && cd build 62 | cmake -G "Unix Makefiles" -DCMAKE_INSTALL_PREFIX=/usr -DCMAKE_INSTALL_LIBDIR=lib -DCMAKE_BUILD_TYPE="Release" .. 63 | make 64 | sudo make install 65 | 66 | - name: Build project 67 | run: | 68 | mkdir build && cd build 69 | cmake -DCMAKE_BUILD_TYPE=Debug -DENABLE_GAMMA=1 -DENABLE_DPMS=1 -DENABLE_DDC=1 -DENABLE_SCREEN=1 -DENABLE_YOCTOLIGHT=1 -DENABLE_PIPEWIRE=1 .. 70 | make 71 | 72 | - name: Perform CodeQL Analysis 73 | uses: github/codeql-action/analyze@v2 74 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.kdev4 2 | *.o 3 | .kdev4/ 4 | build/ 5 | clightd 6 | Debian/ 7 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.14) 2 | 3 | project(clightd VERSION 5.10 LANGUAGES C) 4 | 5 | set(CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake") 6 | 7 | include(GNUInstallDirs) 8 | find_package(PkgConfig) 9 | 10 | execute_process( 11 | COMMAND sh -c "test -d .git && git log -1 --format=%h" 12 | WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR} 13 | OUTPUT_VARIABLE GIT_HASH 14 | OUTPUT_STRIP_TRAILING_WHITESPACE 15 | ) 16 | 17 | if(GIT_HASH) 18 | set(VERSION "${PROJECT_VERSION}-${GIT_HASH}") 19 | else() 20 | set(VERSION "${PROJECT_VERSION}") 21 | endif() 22 | 23 | # Create program target 24 | file(GLOB SOURCES src/*.c src/utils/*.c src/modules/*.c src/modules/sensors/*.c src/modules/backlight_plugins/*.c) 25 | 26 | add_executable(${PROJECT_NAME} ${SOURCES}) 27 | target_include_directories(${PROJECT_NAME} PRIVATE 28 | # Internal headers 29 | "${CMAKE_CURRENT_SOURCE_DIR}/src" 30 | "${CMAKE_CURRENT_SOURCE_DIR}/src/utils" 31 | "${CMAKE_CURRENT_SOURCE_DIR}/src/modules" 32 | ) 33 | target_compile_definitions(${PROJECT_NAME} PRIVATE 34 | -D_GNU_SOURCE 35 | -DVERSION="${VERSION}" 36 | ) 37 | set_property(TARGET ${PROJECT_NAME} PROPERTY C_STANDARD 99) 38 | 39 | # Required dependencies 40 | pkg_check_modules(REQ_LIBS REQUIRED libudev libmodule>=5.0.0 libjpeg libiio) 41 | pkg_check_modules(POLKIT REQUIRED polkit-gobject-1) 42 | pkg_check_modules(DBUS REQUIRED dbus-1) 43 | pkg_search_module(LOGIN_LIBS REQUIRED libelogind libsystemd>=221) 44 | target_link_libraries(${PROJECT_NAME} 45 | m 46 | ${REQ_LIBS_LIBRARIES} 47 | ${LOGIN_LIBS_LIBRARIES} 48 | ) 49 | target_include_directories(${PROJECT_NAME} PRIVATE 50 | "${REQ_LIBS_INCLUDE_DIRS}" 51 | "${LOGIN_LIBS_INCLUDE_DIRS}" 52 | ) 53 | list(APPEND COMBINED_LDFLAGS ${REQ_LIBS_LDFLAGS}) 54 | list(APPEND COMBINED_LDFLAGS ${LOGIN_LIBS_LDFLAGS}) 55 | 56 | # Optional dependencies 57 | 58 | # Needed to eventually build wayland protocols 59 | add_subdirectory(protocol) 60 | 61 | # Helper macro for dealing correctly with optional pkg-config dependencies. 62 | # There are a number of issues when using pkg-config with cmake (as compared to 63 | # using the native dependency handling in CMake). 64 | macro(optional_dep name modules description) 65 | option(ENABLE_${name} 66 | "Enable support for ${description} (defaults to not use it)" 67 | OFF) 68 | if(${ENABLE_${name}}) 69 | pkg_check_modules(${name}_LIBS REQUIRED ${modules}) 70 | message(STATUS "${name} support enabled") 71 | target_compile_definitions(${PROJECT_NAME} PRIVATE ${name}_PRESENT) 72 | # We can't use target_link_libraries, it will not proper handle 73 | # non-standard library paths, since pkg-config returns -Lpath -llib 74 | # instead of -l/path/lib. 75 | list(APPEND COMBINED_LDFLAGS ${${name}_LIBS_LDFLAGS}) 76 | # The actual libraries need to be listed at the end of the link command, 77 | # so this is also needed. 78 | target_link_libraries(${PROJECT_NAME} ${${name}_LIBS_LIBRARIES}) 79 | target_include_directories(${PROJECT_NAME} 80 | PRIVATE 81 | ${${name}_LIBS_INCLUDE_DIRS}) 82 | set(WITH_${name} 1) 83 | 84 | # Check if optional parameters for wayland protocols were passed 85 | set(extra_macro_args ${ARGN}) 86 | list(LENGTH extra_macro_args num_extra_args) 87 | if (${num_extra_args} GREATER 0) 88 | # Add various plugins 89 | list(GET extra_macro_args 0 src_proto) 90 | file(GLOB EXTRA_SRCS ${src_proto}/*) 91 | target_sources(${PROJECT_NAME} PRIVATE ${EXTRA_SRCS}) 92 | 93 | # Remove the item so that we can cycle on wl protocol only 94 | list(REMOVE_ITEM extra_macro_args "${src_proto}") 95 | 96 | # Generate protocol source and header files 97 | # and add them to target_sources + target_include_directories 98 | foreach(wl_proto ${extra_macro_args}) 99 | WAYLAND_ADD_PROTOCOL_CLIENT(${wl_proto}) 100 | endforeach() 101 | endif() 102 | else() 103 | message(STATUS "${name} support disabled") 104 | endif() 105 | endmacro() 106 | 107 | optional_dep(GAMMA "x11;xrandr;libdrm;wayland-client" "Gamma correction" src/modules/gamma_plugins protocol/wlr-gamma-control-unstable-v1.xml) 108 | optional_dep(DPMS "x11;xext;libdrm;wayland-client" "DPMS" src/modules/dpms_plugins protocol/org_kde_kwin_dpms.xml;protocol/wlr-output-power-management-unstable-v1.xml) 109 | optional_dep(SCREEN "x11" "screen emitted brightness" src/modules/screen_plugins protocol/wlr-screencopy-unstable-v1.xml) 110 | optional_dep(DDC "ddcutil>=0.9.5" "external monitor backlight") 111 | optional_dep(YOCTOLIGHT "libusb-1.0" "Yoctolight usb als devices support") 112 | optional_dep(PIPEWIRE "libpipewire-0.3" "Enable pipewire camera sensor support") 113 | 114 | # Add libdrm versions macros in any case, quietly 115 | pkg_check_modules(LIBDRM QUIET libdrm) 116 | if(LIBDRM_FOUND) 117 | string(REPLACE "." ";" VERSION_LIST ${LIBDRM_VERSION}) 118 | list(GET VERSION_LIST 0 LIBDRM_VERSION_MAJ) 119 | list(GET VERSION_LIST 1 LIBDRM_VERSION_MIN) 120 | list(GET VERSION_LIST 2 LIBDRM_VERSION_PATCH) 121 | target_compile_definitions(${PROJECT_NAME} PRIVATE LIBDRM_VERSION_MAJ=${LIBDRM_VERSION_MAJ}) 122 | target_compile_definitions(${PROJECT_NAME} PRIVATE LIBDRM_VERSION_MIN=${LIBDRM_VERSION_MIN}) 123 | target_compile_definitions(${PROJECT_NAME} PRIVATE LIBDRM_VERSION_PATCH=${LIBDRM_VERSION_PATCH}) 124 | endif() 125 | 126 | # Convert ld flag list from list to space separated string. 127 | string(REPLACE ";" " " COMBINED_LDFLAGS "${COMBINED_LDFLAGS}") 128 | 129 | # Set the LDFLAGS target property 130 | set_target_properties( 131 | ${PROJECT_NAME} PROPERTIES 132 | LINK_FLAGS "${COMBINED_LDFLAGS}" 133 | ) 134 | 135 | # Installation of targets (must be before file configuration to work) 136 | install(TARGETS ${PROJECT_NAME} 137 | RUNTIME DESTINATION "${CMAKE_INSTALL_FULL_LIBEXECDIR}") 138 | 139 | set(SCRIPT_DIR "${CMAKE_CURRENT_SOURCE_DIR}/Scripts") 140 | 141 | #### Installation of files #### 142 | 143 | # Only install systemd service in systemd environment 144 | pkg_check_modules(SYSTEMD_BASE systemd) 145 | if(SYSTEMD_BASE_FOUND) 146 | # Use polkitd.service on ubuntu 16.04 147 | if(EXISTS /lib/systemd/system/polkitd.service) 148 | set(POLKIT_NAME "polkitd") 149 | else() 150 | set(POLKIT_NAME "polkit") 151 | endif() 152 | 153 | if(WITH_DDC) 154 | set(AFTER "After=systemd-modules-load.service") 155 | endif() 156 | 157 | # Properly configure clightd systemd service to use correct dep on polkit.service 158 | configure_file(${SCRIPT_DIR}/clightd.service clightd.service @ONLY) 159 | 160 | # This can be overridden by cmdline 161 | if(NOT SYSTEMD_SERVICE_DIR) 162 | # Fetch it from systemd 163 | pkg_get_variable(SYSTEMD_SERVICE_DIR systemd systemdsystemunitdir) 164 | endif() 165 | 166 | install(FILES ${CMAKE_CURRENT_BINARY_DIR}/clightd.service 167 | DESTINATION ${SYSTEMD_SERVICE_DIR}) 168 | 169 | set(SYSTEMD_SERVICE "SystemdService=clightd.service") 170 | endif() 171 | 172 | # Install dbus service 173 | configure_file(${SCRIPT_DIR}/org.clightd.clightd.service 174 | org.clightd.clightd.service 175 | @ONLY) 176 | pkg_get_variable(SYSTEM_BUS_DIR dbus-1 system_bus_services_dir) 177 | install(FILES ${CMAKE_CURRENT_BINARY_DIR}/org.clightd.clightd.service 178 | DESTINATION ${SYSTEM_BUS_DIR}) 179 | 180 | # Install polkit policy 181 | pkg_get_variable(POLKIT_ACTION_DIR polkit-gobject-1 actiondir) 182 | install(FILES ${SCRIPT_DIR}/org.clightd.clightd.policy 183 | DESTINATION ${POLKIT_ACTION_DIR}) 184 | 185 | # When using DDC, try to install an i2c kernel module loading script 186 | if(WITH_DDC) 187 | # Users can provide a MODULE_LOAD_DIR 188 | if(NOT MODULE_LOAD_DIR) 189 | if(SYSTEMD_BASE_FOUND) 190 | # Fetch it from systemd 191 | pkg_get_variable(MODULE_LOAD_DIR systemd modulesloaddir) 192 | else() 193 | # No luck. 194 | message(WARNING "Automatic loading of i2c module disabled.") 195 | endif() 196 | endif() 197 | if(MODULE_LOAD_DIR) 198 | install(FILES ${SCRIPT_DIR}/i2c_clightd.conf 199 | DESTINATION "${MODULE_LOAD_DIR}") 200 | endif() 201 | endif() 202 | 203 | # Install dbus conf 204 | if(NOT DBUS_CONFIG_DIR) 205 | pkg_get_variable(DBUS_SYSTEM_CONF_DIR dbus-1 sysconfdir) 206 | set(DBUS_CONFIG_DIR "${DBUS_SYSTEM_CONF_DIR}/dbus-1/system.d/" CACHE PATH "dbus config directory") 207 | message(STATUS "Using default value for dbus config dir: ${DBUS_CONFIG_DIR}") 208 | endif() 209 | install(FILES ${SCRIPT_DIR}/org.clightd.clightd.conf 210 | DESTINATION ${DBUS_CONFIG_DIR}) 211 | 212 | # Install /etc/clightd/sensors.d/ folder, needed by custom sensor 213 | install (DIRECTORY DESTINATION ${CMAKE_INSTALL_FULL_SYSCONFDIR}/clightd/sensors.d) 214 | 215 | # 216 | # Packaging support 217 | # 218 | SET(CPACK_SET_DESTDIR "on") 219 | set(CPACK_PACKAGE_NAME ${PROJECT_NAME}) 220 | set(CPACK_PACKAGE_VERSION ${PROJECT_VERSION}) 221 | set(CPACK_PACKAGE_VERSION_MAJOR ${PROJECT_VERSION_MAJOR}) 222 | set(CPACK_PACKAGE_VERSION_MINOR ${PROJECT_VERSION_MINOR}) 223 | set(CPACK_PACKAGE_VERSION_PATCH ${PROJECT_VERSION_PATCH}) 224 | 225 | # 226 | # Metadata common to all packaging systems 227 | # 228 | set(CPACK_PACKAGE_CONTACT "Federico Di Pierro ") 229 | set(CPACK_PACKAGE_DESCRIPTION "Clightd offers a bus interface that lets you easily set screen brightness, gamma temperature and get ambient brightness through webcam frames capture or ALS devices.") 230 | set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "Backlight-related bus API.") 231 | 232 | # 233 | # RPM Specific configuration 234 | # 235 | set(CPACK_RPM_PACKAGE_LICENSE "GPL") 236 | set(CPACK_RPM_PACKAGE_URL "https://github.com/FedeDP/Clightd") 237 | set(CPACK_RPM_PACKAGE_GROUP "System Environment/Daemons") 238 | set(CPACK_RPM_EXCLUDE_FROM_AUTO_FILELIST_ADDITION "${DBUS_SYSTEM_CONF_DIR}/dbus-1" "${DBUS_CONFIG_DIR}" "${CMAKE_INSTALL_PREFIX}" "${CMAKE_INSTALL_LIBDIR}" "${SYSTEM_BUS_DIR}" "${POLKIT_ACTION_DIR}") 239 | set(CPACK_RPM_PACKAGE_DESCRIPTION ${CPACK_PACKAGE_DESCRIPTION}) 240 | set(CPACK_RPM_PACKAGE_REQUIRES "libmodule >= 5.0.0") 241 | set(CPACK_RPM_FILE_NAME RPM-DEFAULT) 242 | 243 | # 244 | # DEB Specific configuration 245 | # 246 | set(CPACK_DEBIAN_PACKAGE_HOMEPAGE "https://github.com/FedeDP/Clightd") 247 | set(CPACK_DEBIAN_PACKAGE_SECTION "admin") 248 | set(CPACK_DEBIAN_FILE_NAME DEB-DEFAULT) 249 | set(CPACK_DEBIAN_PACKAGE_SHLIBDEPS ON) 250 | set(CPACK_DEBIAN_PACKAGE_DEPENDS "libmodule (>=5.0.0)") 251 | 252 | # Common deps 253 | if(WITH_DDC) 254 | set(CPACK_DEBIAN_PACKAGE_DEPENDS "${CPACK_DEBIAN_PACKAGE_DEPENDS}, ddcutil (>=0.9.5)") 255 | set(CPACK_RPM_PACKAGE_REQUIRES "${CPACK_RPM_PACKAGE_REQUIRES} ddcutil >= 0.9.5") 256 | endif() 257 | 258 | include(CPack) 259 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Clightd [![CI Build](https://github.com/FedeDP/clightd/actions/workflows/ci.yaml/badge.svg)](https://github.com/FedeDP/clightd/actions/workflows/ci.yaml) [![CodeQL](https://github.com/FedeDP/clightd/actions/workflows/codeql.yaml/badge.svg)](https://github.com/FedeDP/clightd/actions/workflows/codeql.yaml) 2 | 3 | [![Packaging status](https://repology.org/badge/vertical-allrepos/clightd.svg)](https://repology.org/project/clightd/versions) 4 | 5 | Clightd is a bus interface that lets you easily set/get screen brightness, gamma temperature and display dpms state. 6 | Moreover, it enables getting ambient brightness through webcam frames capture or ALS devices. 7 | 8 | It works on both X, Wayland and tty. 9 | On wayland specific protocols need to be implemented by your compositor; have a look at {Gamma, Dpms, Screen} wiki pages. 10 | 11 | Clightd is used as a backend by [Clight](https://github.com/FedeDP/Clight). 12 | 13 | **For a guide on how to build, features and lots of other infos, refer to [Clightd Wiki Pages](https://github.com/FedeDP/Clightd/wiki).** 14 | **Note that Wiki pages will always refer to master branch.** 15 | *For any other info, please feel free to open an issue.* 16 | 17 | ## Arch AUR packages 18 | Clightd is available on AUR as both stable or devel package: https://aur.archlinux.org/packages/?K=clightd . 19 | Note that devel package may break in some weird ways during development. Use it at your own risk. 20 | 21 | ## License 22 | This software is distributed with GPL license, see [COPYING](https://github.com/FedeDP/Clightd/blob/master/COPYING) file for more informations. 23 | -------------------------------------------------------------------------------- /Scripts/clightd.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=Bus service to manage various screen related properties (gamma, dpms, backlight) 3 | Requires=@POLKIT_NAME@.service 4 | @AFTER@ 5 | 6 | [Service] 7 | Type=dbus 8 | BusName=org.clightd.clightd 9 | User=root 10 | # Default backlight vcp code; update if needed. 11 | # Moreover, you can also specify per-monitor BL VCP value, 12 | # using CLIGHTD_BL_VCP_$mon_id, where mon_id is the monitor identifier 13 | # as seen by Clightd; you can explore them using: 14 | # $ busctl call org.clightd.clightd /org/clightd/clightd/Backlight2 org.clightd.clightd.Backlight2 Get 15 | Environment=CLIGHTD_BL_VCP=0x10 16 | # Default value for SYSFS backlight support, for eg: internal laptop screens. 17 | # Set to 0 to disable. 18 | Environment=CLIGHTD_BL_SYSFS_ENABLED=1 19 | # Default value for DDC backlight support, for external screens that support DDC protocol. 20 | # Set to 0 to disable. 21 | Environment=CLIGHTD_BL_DDC_ENABLED=1 22 | # Default value for emulated backlight support, for screens that do not support 23 | # DDC protocol, Clightd will use {xorg,wl,drm} emulation (like xrandr tool). 24 | # Set to 0 to disable. 25 | Environment=CLIGHTD_BL_EMULATED_ENABLED=1 26 | # Some Xorg output name do not match 1:1 with drm nodes. 27 | # Clightd internally relies on drm nodes, 28 | # therefore, you can set the env variable to a comma-separated list 29 | # of XorgName->drmNodeName mapping, like: 30 | # CLIGHTD_XORG_TO_DRM=HDMI-A-0:HDMI-A-1,HDMI-A-4:HDMI-A-7 31 | Environment=CLIGHTD_XORG_TO_DRM= 32 | # Default pipewire runtime dir watched by Clightd 33 | Environment=CLIGHTD_PIPEWIRE_RUNTIME_DIR=/run/user/1000/ 34 | ExecStart=@CMAKE_INSTALL_FULL_LIBEXECDIR@/clightd 35 | Restart=on-failure 36 | RestartSec=5 37 | 38 | [Install] 39 | WantedBy=multi-user.target 40 | -------------------------------------------------------------------------------- /Scripts/i2c_clightd.conf: -------------------------------------------------------------------------------- 1 | # Load i2c-dev at boot; put this in /etc/modules-load.d 2 | i2c-dev 3 | -------------------------------------------------------------------------------- /Scripts/org.clightd.clightd.conf: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /Scripts/org.clightd.clightd.policy: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | no 9 | no 10 | yes 11 | 12 | 13 | 14 | 15 | 16 | no 17 | no 18 | yes 19 | 20 | 21 | 22 | 23 | 24 | no 25 | no 26 | yes 27 | 28 | 29 | 30 | 31 | 32 | no 33 | no 34 | yes 35 | 36 | 37 | 38 | 39 | 40 | no 41 | no 42 | yes 43 | 44 | 45 | 46 | 47 | 48 | no 49 | no 50 | yes 51 | 52 | 53 | 54 | 55 | 56 | no 57 | no 58 | yes 59 | 60 | 61 | 62 | 63 | 64 | no 65 | no 66 | yes 67 | 68 | 69 | 70 | 71 | 72 | no 73 | no 74 | yes 75 | 76 | 77 | 78 | 79 | -------------------------------------------------------------------------------- /Scripts/org.clightd.clightd.service: -------------------------------------------------------------------------------- 1 | [D-BUS Service] 2 | Name=org.clightd.clightd 3 | Exec=@CMAKE_INSTALL_FULL_LIBEXECDIR@/clightd 4 | User=root 5 | @SYSTEMD_SERVICE@ 6 | -------------------------------------------------------------------------------- /TODO.md: -------------------------------------------------------------------------------- 1 | ## 5.10 2 | 3 | - [ ] fix https://github.com/FedeDP/Clightd/issues/108 4 | - [ ] try to improve emulated backlight backend? 5 | 6 | ## 5.x 7 | 8 | ### Pipewire 9 | - [ ] Fix set_camera_setting() impl -> how to get current value? how to set a new value? 10 | - [ ] Check pipewire 0.3.60 v4l2 changes 11 | 12 | ### Backlight 13 | - [ ] investigate emulated_backlight bugginess 14 | - [ ] Keep it up to date with possible ddcutil api changes 15 | 16 | ## 6.0 (api break) 17 | 18 | ### Backlight 19 | - [ ] Drop old backlight API 20 | - [ ] Move Backlight2 to Backlight 21 | - [ ] Drop {Lower,Raise,Set}All from clightd polkit policy 22 | 23 | ### Generic 24 | - [ ] Drop is_smooth options for gamma 25 | - [ ] avoid returning boolean where it does not make sense 26 | - [ ] Rename Wl to Wlr (wlroots) in interfaces 27 | 28 | ## Ideas 29 | - [ ] follow ddcci kernel driver and in case, drop ddcutil and add the kernel driver as clightd opt-dep 30 | -------------------------------------------------------------------------------- /protocol/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Stolen from: https://github.com/giucam/orbital/blob/512c1b3b20a32cf67ba326e8acce3c1c32e11fa2/cmake/Wayland.cmake, thanks! 2 | function(WAYLAND_ADD_PROTOCOL_CLIENT protocol) 3 | find_program(WAYLAND_SCANNER_EXECUTABLE NAMES wayland-scanner REQUIRED) 4 | 5 | get_filename_component(proto_name ${protocol} NAME_WLE) 6 | get_filename_component(proto_fullpath ${protocol} ABSOLUTE) 7 | message(STATUS "Enabled '${proto_name}' wayland client protocol") 8 | 9 | set(_client_header "${CMAKE_CURRENT_BINARY_DIR}/${proto_name}-client-protocol.h") 10 | set(_code "${CMAKE_CURRENT_BINARY_DIR}/${proto_name}-protocol.c") 11 | 12 | add_custom_command( 13 | OUTPUT "${_client_header}" "${_code}" 14 | COMMAND ${WAYLAND_SCANNER_EXECUTABLE} client-header < ${proto_fullpath} > ${_client_header} 15 | COMMAND ${WAYLAND_SCANNER_EXECUTABLE} private-code < ${proto_fullpath} > ${_code} 16 | DEPENDS ${proto_fullpath} VERBATIM 17 | ) 18 | 19 | target_sources(${PROJECT_NAME} PRIVATE ${_code}) 20 | target_include_directories(${PROJECT_NAME} PRIVATE ${CMAKE_CURRENT_BINARY_DIR}) 21 | endfunction() 22 | -------------------------------------------------------------------------------- /protocol/org_kde_kwin_dpms.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 9 | 10 | The Dpms manager allows to get a org_kde_kwin_dpms for a given wl_output. 11 | The org_kde_kwin_dpms provides the currently used VESA Display Power Management 12 | Signaling state (see https://en.wikipedia.org/wiki/VESA_Display_Power_Management_Signaling ). 13 | In addition it allows to request a state change. A compositor is not obliged to honor it 14 | and will normally automatically switch back to on state. 15 | 16 | 17 | 18 | Factory request to get the org_kde_kwin_dpms for a given wl_output. 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | This interface provides information about the VESA DPMS state for a wl_output. 27 | It gets created through the request get on the org_kde_kwin_dpms_manager interface. 28 | 29 | On creating the resource the server will push whether DPSM is supported for the output, 30 | the currently used DPMS state and notifies the client through the done event once all 31 | states are pushed. Whenever a state changes the set of changes is committed with the 32 | done event. 33 | 34 | 35 | 36 | This event gets pushed on binding the resource and indicates whether the wl_output 37 | supports DPMS. There are operation modes of a Wayland server where DPMS might not 38 | make sense (e.g. nested compositors). 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | This mode gets pushed on binding the resource and provides the currently used 51 | DPMS mode. It also gets pushed if DPMS is not supported for the wl_output, in that 52 | case the value will be On. 53 | 54 | The event is also pushed whenever the state changes. 55 | 56 | 57 | 58 | 59 | 60 | This event gets pushed on binding the resource once all other states are pushed. 61 | 62 | In addition it gets pushed whenever a state changes to tell the client that all 63 | state changes have been pushed. 64 | 65 | 66 | 67 | 68 | Requests that the compositor puts the wl_output into the passed mode. The compositor 69 | is not obliged to change the state. In addition the compositor might leave the mode 70 | whenever it seems suitable. E.g. the compositor might return to On state on user input. 71 | 72 | The client should not assume that the mode changed after requesting a new mode. 73 | Instead the client should listen for the mode event. 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | -------------------------------------------------------------------------------- /protocol/wlr-gamma-control-unstable-v1.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Copyright © 2015 Giulio camuffo 5 | Copyright © 2018 Simon Ser 6 | 7 | Permission to use, copy, modify, distribute, and sell this 8 | software and its documentation for any purpose is hereby granted 9 | without fee, provided that the above copyright notice appear in 10 | all copies and that both that copyright notice and this permission 11 | notice appear in supporting documentation, and that the name of 12 | the copyright holders not be used in advertising or publicity 13 | pertaining to distribution of the software without specific, 14 | written prior permission. The copyright holders make no 15 | representations about the suitability of this software for any 16 | purpose. It is provided "as is" without express or implied 17 | warranty. 18 | 19 | THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS 20 | SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND 21 | FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY 22 | SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 23 | WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN 24 | AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, 25 | ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF 26 | THIS SOFTWARE. 27 | 28 | 29 | 30 | This protocol allows a privileged client to set the gamma tables for 31 | outputs. 32 | 33 | Warning! The protocol described in this file is experimental and 34 | backward incompatible changes may be made. Backward compatible changes 35 | may be added together with the corresponding interface version bump. 36 | Backward incompatible changes are done by bumping the version number in 37 | the protocol and interface names and resetting the interface version. 38 | Once the protocol is to be declared stable, the 'z' prefix and the 39 | version number in the protocol and interface names are removed and the 40 | interface version number is reset. 41 | 42 | 43 | 44 | 45 | This interface is a manager that allows creating per-output gamma 46 | controls. 47 | 48 | 49 | 50 | 51 | Create a gamma control that can be used to adjust gamma tables for the 52 | provided output. 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | All objects created by the manager will still remain valid, until their 61 | appropriate destroy request has been called. 62 | 63 | 64 | 65 | 66 | 67 | 68 | This interface allows a client to adjust gamma tables for a particular 69 | output. 70 | 71 | The client will receive the gamma size, and will then be able to set gamma 72 | tables. At any time the compositor can send a failed event indicating that 73 | this object is no longer valid. 74 | 75 | There must always be at most one gamma control object per output, which 76 | has exclusive access to this particular output. When the gamma control 77 | object is destroyed, the gamma table is restored to its original value. 78 | 79 | 80 | 81 | 82 | Advertise the size of each gamma ramp. 83 | 84 | This event is sent immediately when the gamma control object is created. 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | Set the gamma table. The file descriptor can be memory-mapped to provide 96 | the raw gamma table, which contains successive gamma ramps for the red, 97 | green and blue channels. Each gamma ramp is an array of 16-byte unsigned 98 | integers which has the same length as the gamma size. 99 | 100 | The file descriptor data must have the same length as three times the 101 | gamma size. 102 | 103 | 104 | 105 | 106 | 107 | 108 | This event indicates that the gamma control is no longer valid. This 109 | can happen for a number of reasons, including: 110 | - The output doesn't support gamma tables 111 | - Setting the gamma tables failed 112 | - Another client already has exclusive gamma control for this output 113 | - The compositor has transferred gamma control to another client 114 | 115 | Upon receiving this event, the client should destroy this object. 116 | 117 | 118 | 119 | 120 | 121 | Destroys the gamma control object. If the object is still valid, this 122 | restores the original gamma tables. 123 | 124 | 125 | 126 | 127 | -------------------------------------------------------------------------------- /protocol/wlr-output-power-management-unstable-v1.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Copyright © 2019 Purism SPC 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a 7 | copy of this software and associated documentation files (the "Software"), 8 | to deal in the Software without restriction, including without limitation 9 | the rights to use, copy, modify, merge, publish, distribute, sublicense, 10 | and/or sell copies of the Software, and to permit persons to whom the 11 | Software is furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice (including the next 14 | paragraph) shall be included in all copies or substantial portions of the 15 | Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 20 | THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 22 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 23 | DEALINGS IN THE SOFTWARE. 24 | 25 | 26 | 27 | This protocol allows clients to control power management modes 28 | of outputs that are currently part of the compositor space. The 29 | intent is to allow special clients like desktop shells to power 30 | down outputs when the system is idle. 31 | 32 | To modify outputs not currently part of the compositor space see 33 | wlr-output-management. 34 | 35 | Warning! The protocol described in this file is experimental and 36 | backward incompatible changes may be made. Backward compatible changes 37 | may be added together with the corresponding interface version bump. 38 | Backward incompatible changes are done by bumping the version number in 39 | the protocol and interface names and resetting the interface version. 40 | Once the protocol is to be declared stable, the 'z' prefix and the 41 | version number in the protocol and interface names are removed and the 42 | interface version number is reset. 43 | 44 | 45 | 46 | 47 | This interface is a manager that allows creating per-output power 48 | management mode controls. 49 | 50 | 51 | 52 | 53 | Create an output power management mode control that can be used to 54 | adjust the power management mode for a given output. 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | All objects created by the manager will still remain valid, until their 63 | appropriate destroy request has been called. 64 | 65 | 66 | 67 | 68 | 69 | 70 | This object offers requests to set the power management mode of 71 | an output. 72 | 73 | 74 | 75 | 77 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | Set an output's power save mode to the given mode. The mode change 88 | is effective immediately. If the output does not support the given 89 | mode a failed event is sent. 90 | 91 | 92 | 93 | 94 | 95 | 96 | Report the power management mode change of an output. 97 | 98 | The mode event is sent after an output changed its power 99 | management mode. The reason can be a client using set_mode or the 100 | compositor deciding to change an output's mode. 101 | This event is also sent immediately when the object is created 102 | so the client is informed about the current power management mode. 103 | 104 | 106 | 107 | 108 | 109 | 110 | This event indicates that the output power management mode control 111 | is no longer valid. This can happen for a number of reasons, 112 | including: 113 | - The output doesn't support power management 114 | - Another client already has exclusive power management mode control 115 | for this output 116 | - The output disappeared 117 | 118 | Upon receiving this event, the client should destroy this object. 119 | 120 | 121 | 122 | 123 | 124 | Destroys the output power management mode control object. 125 | 126 | 127 | 128 | 129 | 130 | -------------------------------------------------------------------------------- /protocol/wlr-screencopy-unstable-v1.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Copyright © 2018 Simon Ser 5 | Copyright © 2019 Andri Yngvason 6 | 7 | Permission is hereby granted, free of charge, to any person obtaining a 8 | copy of this software and associated documentation files (the "Software"), 9 | to deal in the Software without restriction, including without limitation 10 | the rights to use, copy, modify, merge, publish, distribute, sublicense, 11 | and/or sell copies of the Software, and to permit persons to whom the 12 | Software is furnished to do so, subject to the following conditions: 13 | 14 | The above copyright notice and this permission notice (including the next 15 | paragraph) shall be included in all copies or substantial portions of the 16 | Software. 17 | 18 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 21 | THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 23 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 24 | DEALINGS IN THE SOFTWARE. 25 | 26 | 27 | 28 | This protocol allows clients to ask the compositor to copy part of the 29 | screen content to a client buffer. 30 | 31 | Warning! The protocol described in this file is experimental and 32 | backward incompatible changes may be made. Backward compatible changes 33 | may be added together with the corresponding interface version bump. 34 | Backward incompatible changes are done by bumping the version number in 35 | the protocol and interface names and resetting the interface version. 36 | Once the protocol is to be declared stable, the 'z' prefix and the 37 | version number in the protocol and interface names are removed and the 38 | interface version number is reset. 39 | 40 | 41 | 42 | 43 | This object is a manager which offers requests to start capturing from a 44 | source. 45 | 46 | 47 | 48 | 49 | Capture the next frame of an entire output. 50 | 51 | 52 | 54 | 55 | 56 | 57 | 58 | 59 | Capture the next frame of an output's region. 60 | 61 | The region is given in output logical coordinates, see 62 | xdg_output.logical_size. The region will be clipped to the output's 63 | extents. 64 | 65 | 66 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | All objects created by the manager will still remain valid, until their 78 | appropriate destroy request has been called. 79 | 80 | 81 | 82 | 83 | 84 | 85 | This object represents a single frame. 86 | 87 | When created, a series of buffer events will be sent, each representing a 88 | supported buffer type. The "buffer_done" event is sent afterwards to 89 | indicate that all supported buffer types have been enumerated. The client 90 | will then be able to send a "copy" request. If the capture is successful, 91 | the compositor will send a "flags" followed by a "ready" event. 92 | 93 | For objects version 2 or lower, wl_shm buffers are always supported, ie. 94 | the "buffer" event is guaranteed to be sent. 95 | 96 | If the capture failed, the "failed" event is sent. This can happen anytime 97 | before the "ready" event. 98 | 99 | Once either a "ready" or a "failed" event is received, the client should 100 | destroy the frame. 101 | 102 | 103 | 104 | 105 | Provides information about wl_shm buffer parameters that need to be 106 | used for this frame. This event is sent once after the frame is created 107 | if wl_shm buffers are supported. 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | Copy the frame to the supplied buffer. The buffer must have a the 118 | correct size, see zwlr_screencopy_frame_v1.buffer and 119 | zwlr_screencopy_frame_v1.linux_dmabuf. The buffer needs to have a 120 | supported format. 121 | 122 | If the frame is successfully copied, a "flags" and a "ready" events are 123 | sent. Otherwise, a "failed" event is sent. 124 | 125 | 126 | 127 | 128 | 129 | 131 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | Provides flags about the frame. This event is sent once before the 142 | "ready" event. 143 | 144 | 145 | 146 | 147 | 148 | 149 | Called as soon as the frame is copied, indicating it is available 150 | for reading. This event includes the time at which presentation happened 151 | at. 152 | 153 | The timestamp is expressed as tv_sec_hi, tv_sec_lo, tv_nsec triples, 154 | each component being an unsigned 32-bit value. Whole seconds are in 155 | tv_sec which is a 64-bit value combined from tv_sec_hi and tv_sec_lo, 156 | and the additional fractional part in tv_nsec as nanoseconds. Hence, 157 | for valid timestamps tv_nsec must be in [0, 999999999]. The seconds part 158 | may have an arbitrary offset at start. 159 | 160 | After receiving this event, the client should destroy the object. 161 | 162 | 164 | 166 | 168 | 169 | 170 | 171 | 172 | This event indicates that the attempted frame copy has failed. 173 | 174 | After receiving this event, the client should destroy the object. 175 | 176 | 177 | 178 | 179 | 180 | Destroys the frame. This request can be sent at any time by the client. 181 | 182 | 183 | 184 | 185 | 186 | 187 | Same as copy, except it waits until there is damage to copy. 188 | 189 | 190 | 191 | 192 | 193 | 194 | This event is sent right before the ready event when copy_with_damage is 195 | requested. It may be generated multiple times for each copy_with_damage 196 | request. 197 | 198 | The arguments describe a box around an area that has changed since the 199 | last copy request that was derived from the current screencopy manager 200 | instance. 201 | 202 | The union of all regions received between the call to copy_with_damage 203 | and a ready event is the total damage since the prior ready event. 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | Provides information about linux-dmabuf buffer parameters that need to 215 | be used for this frame. This event is sent once after the frame is 216 | created if linux-dmabuf buffers are supported. 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | This event is sent once after all buffer events have been sent. 226 | 227 | The client should proceed to create a buffer of one of the supported 228 | types, and send a "copy" request. 229 | 230 | 231 | 232 | 233 | -------------------------------------------------------------------------------- /src/commons.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #ifndef USE_STACK_T // TODO: drop when ugprading to libmodule6.0.0 8 | #include 9 | #endif 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | 22 | #define SIZE(x) (sizeof(x) / sizeof(*x)) 23 | 24 | #define _ctor_ __attribute__((constructor (101))) // Used for plugins registering (sensor, gamma, dpms, screen) and libusb/libpipewire init 25 | #define _dtor_ __attribute__((destructor (101))) // Used for libusb and libpipewire dtor 26 | 27 | /* Used by dpms, gamma and screen*/ 28 | #define UNSUPPORTED INT_MIN 29 | #define WRONG_PLUGIN INT_MIN + 1 30 | #define COMPOSITOR_NO_PROTOCOL INT_MIN + 2 31 | 32 | #define UDEV_ACTION_ADD "add" 33 | #define UDEV_ACTION_RM "remove" 34 | #define UDEV_ACTION_CHANGE "change" 35 | 36 | #ifndef USE_STACK_T 37 | extern sd_bus *bus; 38 | #endif 39 | extern struct udev *udev; 40 | -------------------------------------------------------------------------------- /src/main.c: -------------------------------------------------------------------------------- 1 | /* BEGIN_COMMON_COPYRIGHT_HEADER 2 | * 3 | * clightd: C bus interface for linux to change screen brightness and capture frames from webcam device. 4 | * https://github.com/FedeDP/Clight/tree/master/clightd 5 | * 6 | * Copyright (C) 2019 Federico Di Pierro 7 | * 8 | * This file is part of clightd. 9 | * clightd is free software: you can redistribute it and/or modify 10 | * it under the terms of the GNU General Public License as published by 11 | * the Free Software Foundation, either version 3 of the License, or 12 | * (at your option) any later version. 13 | * 14 | * This program is distributed in the hope that it will be useful, 15 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 16 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 17 | * GNU General Public License for more details. 18 | * 19 | * You should have received a copy of the GNU General Public License 20 | * along with this program. If not, see . 21 | * 22 | * END_COMMON_COPYRIGHT_HEADER */ 23 | 24 | #include 25 | 26 | sd_bus *bus = NULL; 27 | struct udev *udev = NULL; 28 | 29 | static const char bus_interface[] = "org.clightd.clightd"; 30 | 31 | /* Every module needs it; let's init it before any module */ 32 | void modules_pre_start(void) { 33 | udev = udev_new(); 34 | } 35 | 36 | static void check_opts(int argc, char *argv[]) { 37 | for (int i = 1; i < argc; i++) { 38 | if (!strcmp(argv[i], "-v") || !strcmp(argv[i], "--version")) { 39 | printf("Clightd: dbus API to easily set screen backlight, gamma temperature and get ambient brightness through webcam frames capture or ALS devices.\n"); 40 | printf("* Current version: %s\n", VERSION); 41 | printf("* https://github.com/FedeDP/Clightd\n"); 42 | printf("* Copyright (C) 2022 Federico Di Pierro \n"); 43 | exit(EXIT_SUCCESS); 44 | } else { 45 | fprintf(stderr, "Unrecognized option: %s.\n", argv[i]); 46 | exit(EXIT_FAILURE); 47 | } 48 | } 49 | } 50 | 51 | int main(int argc, char *argv[]) { 52 | check_opts(argc, argv); 53 | int r = sd_bus_request_name(bus, bus_interface, 0); 54 | if (r < 0) { 55 | fprintf(stderr, "Failed to acquire service name: %s\n", strerror(-r)); 56 | } else { 57 | r = modules_loop(); 58 | sd_bus_release_name(bus, bus_interface); 59 | } 60 | udev_unref(udev); 61 | return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS; 62 | } 63 | -------------------------------------------------------------------------------- /src/modules/backlight.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | static inline void *fetch_bl(sd_bus_message *m); 7 | 8 | /* Exposed */ 9 | static int method_setallbrightness(sd_bus_message *m, void *userdata, sd_bus_error *ret_error); 10 | static int method_getallbrightness(sd_bus_message *m, void *userdata, sd_bus_error *ret_error); 11 | static int method_raiseallbrightness(sd_bus_message *m, void *userdata, sd_bus_error *ret_error); 12 | static int method_lowerallbrightness(sd_bus_message *m, void *userdata, sd_bus_error *ret_error); 13 | static int method_setbrightness1(sd_bus_message *m, void *userdata, sd_bus_error *ret_error); 14 | static int method_getbrightness1(sd_bus_message *m, void *userdata, sd_bus_error *ret_error); 15 | static int method_raisebrightness1(sd_bus_message *m, void *userdata, sd_bus_error *ret_error); 16 | static int method_lowerbrightness1(sd_bus_message *m, void *userdata, sd_bus_error *ret_error); 17 | 18 | static const char object_path[] = "/org/clightd/clightd/Backlight"; 19 | static const char bus_interface[] = "org.clightd.clightd.Backlight"; 20 | static const sd_bus_vtable vtable[] = { 21 | SD_BUS_VTABLE_START(0), 22 | SD_BUS_METHOD("SetAll", "d(bdu)s", "b", method_setallbrightness, SD_BUS_VTABLE_UNPRIVILEGED), 23 | SD_BUS_METHOD("GetAll", "s", "a(sd)", method_getallbrightness, SD_BUS_VTABLE_UNPRIVILEGED), 24 | SD_BUS_METHOD("RaiseAll", "d(bdu)s", "b", method_raiseallbrightness, SD_BUS_VTABLE_UNPRIVILEGED), 25 | SD_BUS_METHOD("LowerAll", "d(bdu)s", "b", method_lowerallbrightness, SD_BUS_VTABLE_UNPRIVILEGED), 26 | SD_BUS_METHOD("Set", "d(bdu)s", "b", method_setbrightness1, SD_BUS_VTABLE_UNPRIVILEGED), 27 | SD_BUS_METHOD("Get", "s", "(sd)", method_getbrightness1, SD_BUS_VTABLE_UNPRIVILEGED), 28 | SD_BUS_METHOD("Raise", "d(bdu)s", "b", method_raisebrightness1, SD_BUS_VTABLE_UNPRIVILEGED), 29 | SD_BUS_METHOD("Lower", "d(bdu)s", "b", method_lowerbrightness1, SD_BUS_VTABLE_UNPRIVILEGED), 30 | SD_BUS_SIGNAL("Changed", "sd", 0), 31 | SD_BUS_VTABLE_END 32 | }; 33 | 34 | extern int method_setbrightness(sd_bus_message *m, void *userdata, sd_bus_error *ret_error); 35 | extern int method_getbrightness(sd_bus_message *m, void *userdata, sd_bus_error *ret_error); 36 | extern int method_raisebrightness(sd_bus_message *m, void *userdata, sd_bus_error *ret_error); 37 | extern int method_lowerbrightness(sd_bus_message *m, void *userdata, sd_bus_error *ret_error); 38 | extern map_ret_code get_backlight(void *userdata, const char *key, void *data); 39 | extern map_t *bls; 40 | 41 | MODULE("BACKLIGHT"); 42 | 43 | static void module_pre_start(void) { 44 | 45 | } 46 | 47 | static bool check(void) { 48 | return true; 49 | } 50 | 51 | static bool evaluate(void) { 52 | return true; 53 | } 54 | 55 | static void init(void) { 56 | int r = sd_bus_add_object_vtable(bus, 57 | NULL, 58 | object_path, 59 | bus_interface, 60 | vtable, 61 | NULL); 62 | if (r < 0) { 63 | m_log("Failed to issue method call: %s\n", strerror(-r)); 64 | } 65 | } 66 | 67 | static void receive(const msg_t *msg, const void *userdata) { 68 | 69 | } 70 | 71 | static void destroy(void) { 72 | 73 | } 74 | 75 | static int method_setallbrightness(sd_bus_message *m, void *userdata, sd_bus_error *ret_error) { 76 | return method_setbrightness(m, NULL, ret_error); 77 | } 78 | 79 | static int method_getallbrightness(sd_bus_message *m, void *userdata, sd_bus_error *ret_error) { 80 | return method_getbrightness(m, NULL, ret_error); 81 | } 82 | 83 | static int method_raiseallbrightness(sd_bus_message *m, void *userdata, sd_bus_error *ret_error) { 84 | return method_raisebrightness(m, NULL, ret_error); 85 | } 86 | 87 | static int method_lowerallbrightness(sd_bus_message *m, void *userdata, sd_bus_error *ret_error) { 88 | return method_lowerbrightness(m, NULL, ret_error); 89 | } 90 | 91 | static inline void *fetch_bl(sd_bus_message *m) { 92 | void *bl = NULL; 93 | const char *sn = NULL; 94 | // Do not check for errors: method_getbrightness1 has only a "s" signature! 95 | sd_bus_message_skip(m, "d(bdu)"); 96 | int r = sd_bus_message_read(m, "s", &sn); 97 | if (r >= 0) { 98 | bl = map_get(bls, sn); 99 | sd_bus_message_rewind(m, true); 100 | } 101 | return bl; 102 | } 103 | 104 | static int method_setbrightness1(sd_bus_message *m, void *userdata, sd_bus_error *ret_error) { 105 | void *bl = fetch_bl(m); 106 | if (!bl) { 107 | return -ENOENT; 108 | } 109 | return method_setbrightness(m, bl, ret_error); 110 | } 111 | 112 | static int method_getbrightness1(sd_bus_message *m, void *userdata, sd_bus_error *ret_error) { 113 | void *bl = fetch_bl(m); 114 | if (!bl) { 115 | return -ENOENT; 116 | } 117 | const char *sn = NULL; 118 | sd_bus_message_read(m, "s", &sn); 119 | double pct = 0.0; 120 | get_backlight(&pct, NULL, bl); 121 | return sd_bus_reply_method_return(m, "(sd)", sn, pct); 122 | } 123 | 124 | static int method_raisebrightness1(sd_bus_message *m, void *userdata, sd_bus_error *ret_error) { 125 | void *bl = fetch_bl(m); 126 | if (!bl) { 127 | return -ENOENT; 128 | } 129 | return method_raisebrightness(m, bl, ret_error); 130 | } 131 | 132 | static int method_lowerbrightness1(sd_bus_message *m, void *userdata, sd_bus_error *ret_error) { 133 | void *bl = fetch_bl(m); 134 | if (!bl) { 135 | return -ENOENT; 136 | } 137 | return method_lowerbrightness(m, bl, ret_error); 138 | } 139 | -------------------------------------------------------------------------------- /src/modules/backlight.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "commons.h" 4 | #include 5 | 6 | #define _BL_PLUGINS \ 7 | X(SYSFS, 0) \ 8 | X(DDC, 1) \ 9 | 10 | enum backlight_plugins { 11 | #define X(name, val) name = val, 12 | _BL_PLUGINS 13 | #undef X 14 | BL_NUM 15 | }; 16 | 17 | typedef struct { 18 | double target_pct; 19 | double step; 20 | unsigned int wait; 21 | } smooth_params_t; 22 | 23 | typedef struct { 24 | smooth_params_t params; 25 | int fd; 26 | } smooth_t; 27 | 28 | struct _bl_plugin; 29 | 30 | typedef struct bl { 31 | bool is_internal; 32 | bool is_ddc; 33 | bool is_emulated; 34 | void *dev; // differs between struct udev_device (internal devices) and DDCA_Display_Ref for external ones 35 | int max; // cached device max backlight value 36 | char obj_path[100]; 37 | const char *sn; 38 | sd_bus_slot *slot; 39 | smooth_t *smooth; // when != NULL -> smoothing 40 | uint64_t cookie; 41 | struct _bl_plugin *plugin; 42 | } bl_t; 43 | 44 | typedef struct _bl_plugin { 45 | const char *name; 46 | bool (*load_env)(void); // returns true to actually enable the plugin 47 | void (*load_devices)(void); 48 | int (*get_monitor)(void); 49 | void (*receive)(void); 50 | int (*set)(bl_t *dev, int value); 51 | int (*get)(bl_t *dev); 52 | void (*free_device)(bl_t *dev); 53 | void (*dtor)(void); 54 | } bl_plugin; 55 | 56 | #define BACKLIGHT(name) \ 57 | static bool load_env(void); \ 58 | static void load_devices(void); \ 59 | static int get_monitor(void); \ 60 | static void receive(void); \ 61 | static int set(bl_t *dev, int value); \ 62 | static int get(bl_t *dev); \ 63 | static void free_device(bl_t *dev); \ 64 | static void dtor(void); \ 65 | static void _ctor_ register_backlight_plugin(void) { \ 66 | static bl_plugin self = { name, load_env, load_devices, get_monitor, receive, set, get, free_device, dtor }; \ 67 | bl_register_new(&self); \ 68 | } 69 | 70 | void bl_register_new(bl_plugin *plugin); 71 | int store_device(bl_t *bl, enum backlight_plugins plenum); 72 | void emit_signals(bl_t *bl, double pct); 73 | 74 | extern map_t *bls; 75 | -------------------------------------------------------------------------------- /src/modules/backlight_plugins/ddc.c: -------------------------------------------------------------------------------- 1 | #ifdef DDC_PRESENT 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include "gamma.h" 8 | #include "backlight.h" 9 | 10 | #define STR(x) _STR(x) 11 | #define _STR(x) #x 12 | #define ID_MAX_LEN 32 13 | #define BL_VCP_ENV "CLIGHTD_BL_VCP" 14 | #define BL_DDC_ENV "CLIGHTD_BL_DDC_ENABLED" 15 | #define BL_EMULATED_ENV "CLIGHTD_BL_EMULATED_ENABLED" 16 | #define DRM_SUBSYSTEM "drm" 17 | 18 | static void add_new_external_display(const char *id, DDCA_Display_Info *dinfo, DDCA_Any_Vcp_Value *valrec); 19 | static void get_ddc_id(char *id, const DDCA_Display_Info *dinfo); 20 | static int get_emulated_id(char *id, int i2c_node); 21 | static void update_external_devices(void); 22 | 23 | BACKLIGHT("DDC"); 24 | 25 | static struct udev_monitor *drm_mon; 26 | static DDCA_Vcp_Feature_Code br_code = 0x10; 27 | static bool ddc_backlight_enabled = true; 28 | static bool emulated_backlight_enabled = false; 29 | static uint64_t curr_cookie; 30 | 31 | static bool load_env(void) { 32 | if (getenv(BL_VCP_ENV)) { 33 | br_code = strtol(getenv(BL_VCP_ENV), NULL, 16); 34 | printf("Overridden default vcp code: 0x%x\n", br_code); 35 | } 36 | if (getenv(BL_DDC_ENV)) { 37 | ddc_backlight_enabled = strtol(getenv(BL_DDC_ENV), NULL, 10); 38 | printf("Overridden default DDC backlight mode: %d.\n", ddc_backlight_enabled); 39 | } 40 | if (getenv(BL_EMULATED_ENV)) { 41 | emulated_backlight_enabled = strtol(getenv(BL_EMULATED_ENV), NULL, 10); 42 | printf("Overridden default emulated backlight mode: %d.\n", emulated_backlight_enabled); 43 | } 44 | #ifndef GAMMA_PRESENT 45 | printf("Gamma was not built in. Force-disable emulated backlight support.\n"); 46 | emulated_backlight_enabled = false; 47 | #endif 48 | return ddc_backlight_enabled || emulated_backlight_enabled; 49 | } 50 | 51 | static void load_devices(void) { 52 | DDCA_Display_Info_List *dlist = NULL; 53 | ddca_get_display_info_list2(true, &dlist); 54 | if (!dlist) { 55 | return; 56 | } 57 | for (int ndx = 0; ndx < dlist->ct; ndx++) { 58 | DDCA_Display_Info *dinfo = &dlist->info[ndx]; 59 | DDCA_Display_Ref dref = dinfo->dref; 60 | DDCA_Display_Handle dh = NULL; 61 | if (ddca_open_display2(dref, false, &dh)) { 62 | continue; 63 | } 64 | DDCA_Any_Vcp_Value *valrec; 65 | char id[ID_MAX_LEN]; 66 | int ret = ddca_get_any_vcp_value_using_explicit_type(dh, br_code, DDCA_NON_TABLE_VCP_VALUE, &valrec); 67 | if (ret == 0) { 68 | if (ddc_backlight_enabled) { 69 | get_ddc_id(id, dinfo); 70 | add_new_external_display(id, dinfo, valrec); 71 | } 72 | ddca_free_any_vcp_value(valrec); 73 | } else if (emulated_backlight_enabled && dinfo->path.io_mode == DDCA_IO_I2C) { 74 | if (get_emulated_id(id, dinfo->path.path.i2c_busno) == 0) { 75 | // Skip internal laptop displays 76 | if (!strcasestr(id, "eDP") && !strcasestr(id, "LVDS")) { 77 | add_new_external_display(id, dinfo, NULL); 78 | } 79 | } 80 | } 81 | ddca_close_display(dh); 82 | } 83 | ddca_free_display_info_list(dlist); 84 | } 85 | 86 | static int get_monitor(void) { 87 | return init_udev_monitor(DRM_SUBSYSTEM, &drm_mon); 88 | } 89 | 90 | static void receive(void) { 91 | struct udev_device *dev = udev_monitor_receive_device(drm_mon); 92 | if (dev) { 93 | update_external_devices(); 94 | udev_device_unref(dev); 95 | } 96 | } 97 | 98 | static int set(bl_t *dev, int value) { 99 | if (dev->is_emulated) { 100 | return set_gamma_brightness(dev->sn, (double)value / dev->max); 101 | } 102 | int ret = -1; 103 | DDCA_Display_Handle dh = NULL; 104 | if (!ddca_open_display2(dev->dev, false, &dh)) { 105 | DDCA_Vcp_Feature_Code specific_br_code; 106 | char specific_br_env[64]; 107 | snprintf(specific_br_env, sizeof(specific_br_env), BL_VCP_ENV"_%s", dev->sn); 108 | if (getenv(specific_br_env)) { 109 | specific_br_code = strtol(getenv(specific_br_env), NULL, 16); 110 | } else { 111 | specific_br_code = br_code; 112 | } 113 | int8_t new_sh = (value >> 8) & 0xff; 114 | int8_t new_sl = value & 0xff; 115 | ret = ddca_set_non_table_vcp_value(dh, specific_br_code, new_sh, new_sl); 116 | ddca_close_display(dh); 117 | } 118 | return ret; 119 | } 120 | 121 | static int get(bl_t *dev) { 122 | if (dev->is_emulated) { 123 | return get_gamma_brightness(dev->sn) * dev->max; 124 | } 125 | int value = 0; 126 | DDCA_Display_Handle dh = NULL; 127 | if (ddca_open_display2(dev->dev, false, &dh) == 0) { 128 | DDCA_Any_Vcp_Value *valrec = NULL; 129 | if (!ddca_get_any_vcp_value_using_explicit_type(dh, br_code, DDCA_NON_TABLE_VCP_VALUE, &valrec)) { 130 | value = VALREC_CUR_VAL(valrec); 131 | ddca_free_any_vcp_value(valrec); 132 | } 133 | ddca_close_display(dh); 134 | } 135 | return value; 136 | } 137 | 138 | static void free_device(bl_t *dev) { 139 | if (dev->is_emulated) { 140 | clean_gamma_brightness(dev->sn); 141 | } 142 | } 143 | 144 | static void dtor(void) { 145 | udev_monitor_unref(drm_mon); 146 | } 147 | 148 | static void add_new_external_display(const char *id, DDCA_Display_Info *dinfo, DDCA_Any_Vcp_Value *valrec) { 149 | bl_t *d = map_get(bls, id); 150 | if (!d) { 151 | d = calloc(1, sizeof(bl_t)); 152 | if (d) { 153 | d->dev = dinfo->dref; 154 | d->sn = strdup(id); 155 | d->cookie = curr_cookie; 156 | if (valrec != NULL) { 157 | d->max = VALREC_MAX_VAL(valrec); 158 | d->is_ddc = true; 159 | } else { 160 | d->max = 100; // perc 161 | d->is_emulated = true; 162 | } 163 | if (store_device(d, DDC) == 0) { 164 | /* Not on first load */ 165 | if (d->cookie > 0) { 166 | sd_bus_emit_object_added(bus, d->obj_path); 167 | } 168 | } 169 | } 170 | } else { 171 | // Update cookie and dref 172 | d->cookie = curr_cookie; 173 | d->dev = dinfo->dref; 174 | } 175 | } 176 | 177 | static void get_ddc_id(char *id, const DDCA_Display_Info *dinfo) { 178 | if ((dinfo->sn[0] == '\0') || !strcasecmp(dinfo->sn, "Unspecified")) { 179 | switch(dinfo->path.io_mode) { 180 | case DDCA_IO_I2C: 181 | snprintf(id, ID_MAX_LEN, "/dev/i2c-%d", dinfo->path.path.i2c_busno); 182 | break; 183 | case DDCA_IO_USB: 184 | snprintf(id, ID_MAX_LEN, "/dev/usb/hiddev%d", dinfo->path.path.hiddev_devno); 185 | break; 186 | default: 187 | snprintf(id, ID_MAX_LEN, "%d", dinfo->dispno); 188 | break; 189 | } 190 | } else { 191 | strncpy(id, dinfo->sn, ID_MAX_LEN); 192 | } 193 | } 194 | 195 | static int get_emulated_id(char *id, int i2c_node) { 196 | int ret = -ENOENT; 197 | glob_t gl = {0}; 198 | if (glob("/sys/class/drm/card*-*", GLOB_NOSORT | GLOB_ERR, NULL, &gl) == 0) { 199 | for (int i = 0; i < gl.gl_pathc; i++) { 200 | char path[PATH_MAX + 1]; 201 | snprintf(path, sizeof(path), "%s/ddc/i2c-dev/i2c-%d", gl.gl_pathv[i], i2c_node); 202 | if (access(path, F_OK) == 0) { 203 | sscanf(gl.gl_pathv[i], "/sys/class/drm/card%*d-%"STR(ID_MAX_LEN)"s", id); 204 | ret = 0; 205 | break; 206 | } 207 | } 208 | globfree(&gl); 209 | } 210 | return ret; 211 | } 212 | 213 | static void update_external_devices(void) { 214 | #if DDCUTIL_VMAJOR >= 1 && DDCUTIL_VMINOR >= 2 215 | /* 216 | * Algo: increment current cookie, 217 | * then rededect all displays. 218 | * Then, store any new external monitor with new cookie, 219 | * or update cookie and dref for still existent ones. 220 | * Finally, remove any non-existent monitor 221 | * (look for external monitors whose cookie is != of current cookie) 222 | */ 223 | curr_cookie++; 224 | ddca_redetect_displays(); 225 | load_devices(); 226 | for (map_itr_t *itr = map_itr_new(bls); itr; itr = map_itr_next(itr)) { 227 | bl_t *d = map_itr_get_data(itr); 228 | if (!d->is_internal && d->cookie != curr_cookie) { 229 | sd_bus_emit_object_removed(bus, d->obj_path); 230 | map_itr_remove(itr); 231 | } 232 | } 233 | #endif 234 | } 235 | 236 | #endif 237 | -------------------------------------------------------------------------------- /src/modules/backlight_plugins/sysfs.c: -------------------------------------------------------------------------------- 1 | #include "backlight.h" 2 | #include 3 | 4 | #define BL_SUBSYSTEM "backlight" 5 | #define BL_SYSFS_ENV "CLIGHTD_BL_SYSFS_ENABLED" 6 | 7 | static int store_internal_device(struct udev_device *dev, void *userdata); 8 | 9 | BACKLIGHT("Sysfs"); 10 | 11 | static struct udev_monitor *bl_mon; 12 | 13 | static bool load_env(void) { 14 | bool bl_backlight_enabled = true; 15 | if (getenv(BL_SYSFS_ENV)) { 16 | bl_backlight_enabled = strtol(getenv(BL_SYSFS_ENV), NULL, 10); 17 | printf("Overridden default sysfs backlight mode: %d.\n", bl_backlight_enabled); 18 | } 19 | return bl_backlight_enabled; 20 | } 21 | 22 | static void load_devices(void) { 23 | udev_devices_foreach(BL_SUBSYSTEM, NULL, store_internal_device, NULL); 24 | } 25 | 26 | static int get_monitor(void) { 27 | return init_udev_monitor(BL_SUBSYSTEM, &bl_mon); 28 | } 29 | 30 | static void receive(void) { 31 | /* From udev monitor, consume! */ 32 | struct udev_device *dev = udev_monitor_receive_device(bl_mon); 33 | if (dev) { 34 | // Ok, the event was from internal monitor 35 | const char *id = udev_device_get_sysname(dev); 36 | const char *action = udev_device_get_action(dev); 37 | if (action) { 38 | bl_t *bl = map_get(bls, id); 39 | if (!strcmp(action, UDEV_ACTION_CHANGE) && bl) { 40 | // Load cached value 41 | int old_bl_value = atoi(udev_device_get_sysattr_value(bl->dev, "brightness")); 42 | /* Keep our device ref in sync! */ 43 | udev_device_unref(bl->dev); 44 | bl->dev = udev_device_ref(dev); 45 | 46 | int val = atoi(udev_device_get_sysattr_value(dev, "brightness")); 47 | if (val != old_bl_value) { 48 | const double pct = (double)val / bl->max; 49 | emit_signals(bl, pct); 50 | } 51 | } else if (!strcmp(action, UDEV_ACTION_ADD) && !bl) { 52 | store_internal_device(dev, &bl); 53 | } else if (!strcmp(action, UDEV_ACTION_RM) && bl) { 54 | sd_bus_emit_object_removed(bus, bl->obj_path); 55 | map_remove(bls, id); 56 | } 57 | } 58 | udev_device_unref(dev); 59 | } 60 | } 61 | 62 | static int set(bl_t *dev, int value) { 63 | char val[15] = {0}; 64 | snprintf(val, sizeof(val) - 1, "%d", value); 65 | return udev_device_set_sysattr_value(dev->dev, "brightness", val); 66 | } 67 | 68 | static int get(bl_t *dev) { 69 | return atoi(udev_device_get_sysattr_value(dev->dev, "brightness")); 70 | } 71 | 72 | static void free_device(bl_t *dev) { 73 | udev_device_unref(dev->dev); 74 | } 75 | 76 | static void dtor(void) { 77 | udev_monitor_unref(bl_mon); 78 | } 79 | 80 | static int store_internal_device(struct udev_device *dev, void *userdata) { 81 | int ret = -ENOMEM; 82 | const int max = atoi(udev_device_get_sysattr_value(dev, "max_brightness")); 83 | const char *id = udev_device_get_sysname(dev); 84 | bl_t *d = calloc(1, sizeof(bl_t)); 85 | if (d) { 86 | d->is_internal = true; 87 | d->dev = udev_device_ref(dev); 88 | d->max = max; 89 | d->sn = strdup(id); 90 | // Unused. But receive() callback expects brightness value to be cached 91 | udev_device_get_sysattr_value(dev, "brightness"); 92 | ret = store_device(d, SYSFS); 93 | if (ret == 0 && userdata != NULL) { 94 | /* Not on first load (userdata != NULL only true when called by receive()) */ 95 | sd_bus_emit_object_added(bus, d->obj_path); 96 | } 97 | } 98 | return ret; 99 | } 100 | -------------------------------------------------------------------------------- /src/modules/bus.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | static int get_version( sd_bus *b, const char *path, const char *interface, const char *property, 4 | sd_bus_message *reply, void *userdata, sd_bus_error *error); 5 | 6 | static const char object_path[] = "/org/clightd/clightd"; 7 | static const char bus_interface[] = "org.clightd.clightd"; 8 | static const sd_bus_vtable vtable[] = { 9 | SD_BUS_VTABLE_START(0), 10 | SD_BUS_PROPERTY("Version", "s", get_version, 0, SD_BUS_VTABLE_PROPERTY_CONST), 11 | SD_BUS_VTABLE_END 12 | }; 13 | 14 | MODULE("BUS"); 15 | 16 | static void module_pre_start(void) { 17 | sd_bus_default_system(&bus); 18 | } 19 | 20 | static bool check(void) { 21 | return true; 22 | } 23 | 24 | static bool evaluate(void) { 25 | return true; 26 | } 27 | 28 | static void init(void) { 29 | int r = sd_bus_add_object_vtable(bus, 30 | NULL, 31 | object_path, 32 | bus_interface, 33 | vtable, 34 | NULL); 35 | if (r < 0) { 36 | m_log("Failed to issue method call: %s\n", strerror(-r)); 37 | } else { 38 | /* Process initial messages */ 39 | receive(NULL, NULL); 40 | int fd = sd_bus_get_fd(bus); 41 | m_register_fd(dup(fd), true, NULL); 42 | } 43 | } 44 | 45 | static void receive(const msg_t *msg, const void *userdata) { 46 | if (!msg || !msg->is_pubsub) { 47 | int r; 48 | do { 49 | r = sd_bus_process(bus, NULL); 50 | if (r < 0) { 51 | m_log("Failed to process bus: %s\n", strerror(-r)); 52 | if (r == -ENOTCONN || r == -ECONNRESET) { 53 | modules_quit(r); 54 | } 55 | } 56 | } while (r > 0); 57 | } 58 | } 59 | 60 | static void destroy(void) { 61 | sd_bus_flush_close_unref(bus); 62 | } 63 | 64 | static int get_version( sd_bus *b, const char *path, const char *interface, const char *property, 65 | sd_bus_message *reply, void *userdata, sd_bus_error *error) { 66 | return sd_bus_message_append(reply, "s", VERSION); 67 | } 68 | -------------------------------------------------------------------------------- /src/modules/dpms.c: -------------------------------------------------------------------------------- 1 | #ifdef DPMS_PRESENT 2 | 3 | #include "dpms.h" 4 | #include "polkit.h" 5 | #include "bus_utils.h" 6 | 7 | static int method_getdpms(sd_bus_message *m, void *userdata, sd_bus_error *ret_error); 8 | static int method_setdpms(sd_bus_message *m, void *userdata, sd_bus_error *ret_error); 9 | 10 | static dpms_plugin *plugins[DPMS_NUM]; 11 | static const char object_path[] = "/org/clightd/clightd/Dpms"; 12 | static const char bus_interface[] = "org.clightd.clightd.Dpms"; 13 | static const sd_bus_vtable vtable[] = { 14 | SD_BUS_VTABLE_START(0), 15 | SD_BUS_METHOD("Get", "ss", "i", method_getdpms, SD_BUS_VTABLE_UNPRIVILEGED), 16 | SD_BUS_METHOD("Set", "ssi", "b", method_setdpms, SD_BUS_VTABLE_UNPRIVILEGED), 17 | SD_BUS_SIGNAL("Changed", "si", 0), 18 | SD_BUS_VTABLE_END 19 | }; 20 | 21 | MODULE("DPMS"); 22 | 23 | static void module_pre_start(void) { 24 | 25 | } 26 | 27 | static bool check(void) { 28 | return true; 29 | } 30 | 31 | static bool evaluate(void) { 32 | return true; 33 | } 34 | 35 | static void init(void) { 36 | int r = sd_bus_add_object_vtable(bus, 37 | NULL, 38 | object_path, 39 | bus_interface, 40 | vtable, 41 | NULL); 42 | for (int i = 0; i < DPMS_NUM && !r; i++) { 43 | if (plugins[i]) { 44 | snprintf(plugins[i]->obj_path, sizeof(plugins[i]->obj_path) - 1, "%s/%s", object_path, plugins[i]->name); 45 | r += sd_bus_add_object_vtable(bus, 46 | NULL, 47 | plugins[i]->obj_path, 48 | bus_interface, 49 | vtable, 50 | plugins[i]); 51 | } 52 | } 53 | if (r < 0) { 54 | m_log("Failed to issue method call: %s\n", strerror(-r)); 55 | } 56 | } 57 | 58 | static void receive(const msg_t *msg, const void *userdata) { 59 | 60 | } 61 | 62 | static void destroy(void) { 63 | 64 | } 65 | 66 | void dpms_register_new(dpms_plugin *plugin) { 67 | const char *plugins_names[] = { 68 | #define X(name, val) #name, 69 | _DPMS_PLUGINS 70 | #undef X 71 | }; 72 | 73 | int i; 74 | for (i = 0; i < DPMS_NUM; i++) { 75 | if (strcasestr(plugins_names[i], plugin->name)) { 76 | break; 77 | } 78 | } 79 | 80 | if (i < DPMS_NUM) { 81 | plugins[i] = plugin; 82 | printf("Registered '%s' dpms plugin.\n", plugin->name); 83 | } else { 84 | printf("Dpms plugin '%s' not recognized. Not registering.\n", plugin->name); 85 | } 86 | } 87 | 88 | static int method_getdpms(sd_bus_message *m, void *userdata, sd_bus_error *ret_error) { 89 | const char *display = NULL, *env = NULL; 90 | 91 | /* Read the parameters */ 92 | int r = sd_bus_message_read(m, "ss", &display, &env); 93 | if (r < 0) { 94 | m_log("Failed to parse parameters: %s\n", strerror(-r)); 95 | return r; 96 | } 97 | 98 | bus_sender_fill_creds(m); // used by PW plugin 99 | 100 | /* 101 | * Note: this is freed by drm plugin if it is an empty string 102 | * to get a default drm device. 103 | */ 104 | const char *dpy = strdup(display); 105 | dpms_plugin *plugin = userdata; 106 | int dpms_state = WRONG_PLUGIN; 107 | if (!plugin) { 108 | for (int i = 0; i < DPMS_NUM && dpms_state == WRONG_PLUGIN; i++) { 109 | dpms_state = plugins[i]->get(&dpy, env); 110 | } 111 | } else { 112 | dpms_state = plugin->get(&dpy, env); 113 | } 114 | free((char *)dpy); 115 | 116 | if (dpms_state < 0) { 117 | switch (dpms_state) { 118 | case COMPOSITOR_NO_PROTOCOL: 119 | sd_bus_error_set_const(ret_error, SD_BUS_ERROR_FAILED, "Compositor does not support required wayland protocols."); 120 | break; 121 | case WRONG_PLUGIN: 122 | sd_bus_error_set_const(ret_error, SD_BUS_ERROR_FAILED, "No plugin available for your configuration."); 123 | break; 124 | default: 125 | sd_bus_error_set_const(ret_error, SD_BUS_ERROR_FAILED, "Failed to get dpms level."); 126 | break; 127 | } 128 | return -EACCES; 129 | } 130 | 131 | m_log("Current dpms state: %d.\n", dpms_state); 132 | return sd_bus_reply_method_return(m, "i", dpms_state); 133 | } 134 | 135 | static int method_setdpms(sd_bus_message *m, void *userdata, sd_bus_error *ret_error) { 136 | const char *display = NULL, *env = NULL; 137 | int level; 138 | 139 | ASSERT_AUTH(); 140 | 141 | /* Read the parameters */ 142 | int r = sd_bus_message_read(m, "ssi", &display, &env, &level); 143 | if (r < 0) { 144 | m_log("Failed to parse parameters: %s\n", strerror(-r)); 145 | return r; 146 | } 147 | 148 | /* 0 -> DPMSModeOn, 3 -> DPMSModeOff */ 149 | if (level < 0 || level > 3) { 150 | sd_bus_error_set_const(ret_error, SD_BUS_ERROR_FAILED, "Wrong DPMS level value."); 151 | return -EINVAL; 152 | } 153 | 154 | bus_sender_fill_creds(m); // used by PW plugin 155 | 156 | /* 157 | * Note: this is freed by drm plugin if it is an empty string 158 | * to get a default drm device. 159 | */ 160 | const char *dpy = strdup(display); 161 | dpms_plugin *plugin = userdata; 162 | int err = WRONG_PLUGIN; 163 | if (!plugin) { 164 | for (int i = 0; i < DPMS_NUM && err == WRONG_PLUGIN; i++) { 165 | plugin = plugins[i]; 166 | err = plugin->set(&dpy, env, level); 167 | } 168 | } else { 169 | err = plugin->set(&dpy, env, level); 170 | } 171 | if (err) { 172 | switch (err) { 173 | case COMPOSITOR_NO_PROTOCOL: 174 | sd_bus_error_set_const(ret_error, SD_BUS_ERROR_FAILED, "Compositor does not support required wayland protocols."); 175 | break; 176 | case WRONG_PLUGIN: 177 | sd_bus_error_set_const(ret_error, SD_BUS_ERROR_FAILED, "No plugin available for your configuration."); 178 | break; 179 | default: 180 | sd_bus_error_set_const(ret_error, SD_BUS_ERROR_FAILED, "Failed to set dpms level."); 181 | break; 182 | } 183 | err = -EACCES; 184 | } else { 185 | m_log("New dpms state: %d.\n", level); 186 | sd_bus_emit_signal(bus, object_path, bus_interface, "Changed", "si", dpy, level); 187 | sd_bus_emit_signal(bus, plugin->obj_path, bus_interface, "Changed", "si", dpy, level); 188 | err = sd_bus_reply_method_return(m, "b", true); 189 | } 190 | free((char *)dpy); 191 | return err; 192 | } 193 | 194 | #endif 195 | -------------------------------------------------------------------------------- /src/modules/dpms.h: -------------------------------------------------------------------------------- 1 | #include "commons.h" 2 | 3 | #define _DPMS_PLUGINS \ 4 | X(XORG, 0) \ 5 | X(WL, 1) \ 6 | X(KWIN_WL, 2) \ 7 | X(DRM, 3) 8 | 9 | enum dpms_plugins { 10 | #define X(name, val) name = val, 11 | _DPMS_PLUGINS 12 | #undef X 13 | DPMS_NUM 14 | }; 15 | 16 | /* 17 | * set() and get() take double pointer as 18 | * drm plugin may override id. 19 | */ 20 | typedef struct { 21 | const char *name; 22 | int (*set)(const char **id, const char *env, int level); 23 | int (*get)(const char **id, const char *env); 24 | char obj_path[100]; 25 | } dpms_plugin; 26 | 27 | #define DPMS(name) \ 28 | static int get(const char **id, const char *env); \ 29 | static int set(const char **id, const char *env, int level); \ 30 | static void _ctor_ register_gamma_plugin(void) { \ 31 | static dpms_plugin self = { name, set, get }; \ 32 | dpms_register_new(&self); \ 33 | } 34 | 35 | void dpms_register_new(dpms_plugin *plugin); 36 | -------------------------------------------------------------------------------- /src/modules/dpms_plugins/drm.c: -------------------------------------------------------------------------------- 1 | #include "dpms.h" 2 | #include "drm_utils.h" 3 | 4 | static drmModeConnectorPtr get_active_connector(int fd, int connector_id); 5 | static drmModePropertyPtr drm_get_prop(int fd, drmModeConnectorPtr connector, const char *name); 6 | 7 | DPMS("Drm"); 8 | 9 | /* 10 | * state will be one of: 11 | * DPMS Extension Power Levels 12 | * 0 DPMSModeOn In use 13 | * 1 DPMSModeStandby Blanked, low power 14 | * 2 DPMSModeSuspend Blanked, lower power 15 | * 3 DPMSModeOff Shut off, awaiting activity 16 | * 17 | * Clightd returns -1 if dpms is disabled 18 | */ 19 | static int get(const char **card, const char *env) { 20 | int state = -1; 21 | drmModeConnectorPtr connector; 22 | 23 | int fd = drm_open_card(card); 24 | if (fd < 0) { 25 | return WRONG_PLUGIN; 26 | } 27 | 28 | drmModeRes *res = drmModeGetResources(fd); 29 | if (res) { 30 | for (int i = 0; i < res->count_connectors; i++) { 31 | connector = get_active_connector(fd, res->connectors[i]); 32 | if (!connector) { 33 | continue; 34 | } 35 | 36 | drmModePropertyPtr p = drm_get_prop(fd, connector, "DPMS"); 37 | if (p) { 38 | /* prop_id is 2, it means it is second prop */ 39 | state = (int)connector->prop_values[p->prop_id - 1]; 40 | drmModeFreeProperty(p); 41 | drmModeFreeConnector(connector); 42 | break; 43 | } 44 | } 45 | drmModeFreeResources(res); 46 | } else { 47 | perror("dpms drmModeGetResources"); 48 | } 49 | close(fd); 50 | return state; 51 | } 52 | 53 | static int set(const char **card, const char *env, int level) { 54 | int err = 0; 55 | drmModeConnectorPtr connector; 56 | drmModePropertyPtr prop; 57 | 58 | int fd = drm_open_card(card); 59 | if (fd < 0) { 60 | return WRONG_PLUGIN; 61 | } 62 | 63 | if (drmSetMaster(fd)) { 64 | perror("SetMaster"); 65 | err = -errno; 66 | goto end; 67 | } 68 | 69 | drmModeRes *res = drmModeGetResources(fd); 70 | if (res) { 71 | for (int i = 0; i < res->count_connectors; i++) { 72 | connector = get_active_connector(fd, res->connectors[i]); 73 | if (!connector) { 74 | continue; 75 | } 76 | 77 | prop = drm_get_prop(fd, connector, "DPMS"); 78 | if (!prop) { 79 | drmModeFreeConnector(connector); 80 | continue; 81 | } 82 | if (drmModeConnectorSetProperty(fd, connector->connector_id, prop->prop_id, level)) { 83 | perror("drmModeConnectorSetProperty"); 84 | } 85 | drmModeFreeProperty(prop); 86 | drmModeFreeConnector(connector); 87 | } 88 | drmModeFreeResources(res); 89 | } else { 90 | err = -errno; 91 | } 92 | 93 | if (drmDropMaster(fd)) { 94 | perror("DropMaster"); 95 | err = -errno; 96 | } 97 | 98 | end: 99 | if (fd > 0) { 100 | close(fd); 101 | } 102 | return err; 103 | } 104 | 105 | static drmModeConnectorPtr get_active_connector(int fd, int connector_id) { 106 | drmModeConnectorPtr connector = drmModeGetConnector(fd, connector_id); 107 | 108 | if (connector) { 109 | if (connector->connection == DRM_MODE_CONNECTED 110 | && connector->count_modes > 0 && connector->encoder_id != 0) { 111 | return connector; 112 | } 113 | drmModeFreeConnector(connector); 114 | } 115 | return NULL; 116 | } 117 | 118 | static drmModePropertyPtr drm_get_prop(int fd, drmModeConnectorPtr connector, const char *name) { 119 | drmModePropertyPtr props; 120 | 121 | for (int i = 0; i < connector->count_props; i++) { 122 | props = drmModeGetProperty(fd, connector->props[i]); 123 | if (!props) { 124 | continue; 125 | } 126 | if (!strcmp(props->name, name)) { 127 | return props; 128 | } 129 | drmModeFreeProperty(props); 130 | } 131 | return NULL; 132 | } 133 | -------------------------------------------------------------------------------- /src/modules/dpms_plugins/kwin_wl.c: -------------------------------------------------------------------------------- 1 | #include "org_kde_kwin_dpms-client-protocol.h" 2 | #include "wl_utils.h" 3 | #include "dpms.h" 4 | 5 | struct output { 6 | struct wl_output *wl_output; 7 | struct org_kde_kwin_dpms *dpms_control; 8 | uint32_t supported; 9 | uint32_t mode; 10 | bool done; 11 | struct wl_list link; 12 | }; 13 | 14 | static void registry_handle_global(void *data, struct wl_registry *registry, 15 | uint32_t name, const char *interface, uint32_t version); 16 | static void registry_handle_global_remove(void *data, 17 | struct wl_registry *registry, uint32_t name); 18 | static void dpms_control_handle_supported(void *data, struct org_kde_kwin_dpms *org_kde_kwin_dpms, uint32_t supported); 19 | static void dpms_control_handle_mode(void *data, struct org_kde_kwin_dpms *org_kde_kwin_dpms, uint32_t mode); 20 | static void dpms_control_handle_done(void *data, struct org_kde_kwin_dpms *org_kde_kwin_dpms); 21 | 22 | static int wl_init(const char *display, const char *env); 23 | static void wl_deinit(void); 24 | static void destroy_node(struct output *output); 25 | 26 | static struct wl_display *dpy; 27 | static struct wl_list outputs; 28 | static struct org_kde_kwin_dpms_manager *dpms_control_manager; 29 | static struct wl_registry *dpms_registry; 30 | 31 | static const struct wl_registry_listener registry_listener = { 32 | .global = registry_handle_global, 33 | .global_remove = registry_handle_global_remove, 34 | }; 35 | 36 | static const struct org_kde_kwin_dpms_listener dpms_listener = { 37 | .supported = dpms_control_handle_supported, 38 | .mode = dpms_control_handle_mode, 39 | .done = dpms_control_handle_done, 40 | }; 41 | 42 | DPMS("KWin_wl"); 43 | 44 | static void registry_handle_global(void *data, struct wl_registry *registry, 45 | uint32_t name, const char *interface, uint32_t version) { 46 | if (strcmp(interface, wl_output_interface.name) == 0) { 47 | struct output *output = calloc(1, sizeof(struct output)); 48 | output->wl_output = wl_registry_bind(registry, name, &wl_output_interface, 1); 49 | wl_list_insert(&outputs, &output->link); 50 | } else if (strcmp(interface, org_kde_kwin_dpms_manager_interface.name) == 0) { 51 | dpms_control_manager = wl_registry_bind(registry, name, &org_kde_kwin_dpms_manager_interface, 1); 52 | } 53 | } 54 | 55 | static void registry_handle_global_remove(void *data, 56 | struct wl_registry *registry, uint32_t name) { 57 | 58 | } 59 | 60 | static void dpms_control_handle_supported(void *data, struct org_kde_kwin_dpms *org_kde_kwin_dpms, uint32_t supported) { 61 | struct output *output = data; 62 | output->supported = supported; 63 | } 64 | 65 | static void dpms_control_handle_mode(void *data, struct org_kde_kwin_dpms *org_kde_kwin_dpms, uint32_t mode) { 66 | struct output *output = data; 67 | output->mode = mode; 68 | } 69 | 70 | static void dpms_control_handle_done(void *data, struct org_kde_kwin_dpms *org_kde_kwin_dpms) { 71 | struct output *output = data; 72 | output->done = true; 73 | } 74 | 75 | static int wl_init(const char *display, const char *env) { 76 | int ret = 0; 77 | dpy = fetch_wl_display(display, env); 78 | if (dpy == NULL) { 79 | ret = WRONG_PLUGIN; 80 | return ret; 81 | } 82 | wl_list_init(&outputs); 83 | dpms_registry = wl_display_get_registry(dpy); 84 | wl_registry_add_listener(dpms_registry, ®istry_listener, NULL); 85 | wl_display_roundtrip(dpy); 86 | 87 | if (dpms_control_manager == NULL) { 88 | fprintf(stderr, "compositor doesn't support '%s'\n", org_kde_kwin_dpms_manager_interface.name); 89 | ret = COMPOSITOR_NO_PROTOCOL; 90 | goto err; 91 | } 92 | 93 | struct output *output; 94 | struct output *tmp_output; 95 | wl_list_for_each(output, &outputs, link) { 96 | output->dpms_control = org_kde_kwin_dpms_manager_get(dpms_control_manager, output->wl_output); 97 | if (output->dpms_control) { 98 | org_kde_kwin_dpms_add_listener(output->dpms_control, &dpms_listener, output); 99 | } else { 100 | fprintf(stderr, "failed to receive KWin DPMS control manager\n"); 101 | ret = -errno; 102 | goto err; 103 | } 104 | } 105 | wl_display_roundtrip(dpy); 106 | 107 | /* Check that all outputs were inited correctly */ 108 | wl_list_for_each_safe(output, tmp_output, &outputs, link) { 109 | if (output->wl_output == NULL || output->dpms_control == NULL) { 110 | fprintf(stderr, "failed to create KWin DPMS output\n"); 111 | ret = -ENOMEM; 112 | break; 113 | } 114 | if (!output->supported) { 115 | wl_list_remove(&output->link); 116 | destroy_node(output); 117 | } 118 | } 119 | 120 | /* No supported output found */ 121 | if (wl_list_length(&outputs) == 0) { 122 | ret = UNSUPPORTED; 123 | } 124 | 125 | err: 126 | if (ret != 0) { 127 | wl_deinit(); 128 | } 129 | return ret; 130 | } 131 | 132 | static void wl_deinit(void) { 133 | struct output *output; 134 | struct output *tmp_output; 135 | wl_list_for_each_safe(output, tmp_output, &outputs, link) { 136 | wl_list_remove(&output->link); 137 | destroy_node(output); 138 | } 139 | if (dpms_registry) { 140 | wl_registry_destroy(dpms_registry); 141 | } 142 | if (dpms_control_manager) { 143 | org_kde_kwin_dpms_manager_destroy(dpms_control_manager); 144 | } 145 | // NOTE: dpy is disconnected on program exit to workaround 146 | // gamma protocol limitation that resets gamma as soon as display is disconnected. 147 | // See wl_utils.c 148 | } 149 | 150 | static void destroy_node(struct output *output) { 151 | if (output->wl_output) { 152 | wl_output_destroy(output->wl_output); 153 | } 154 | if (output->dpms_control) { 155 | org_kde_kwin_dpms_destroy(output->dpms_control); 156 | } 157 | free(output); 158 | } 159 | 160 | static int get(const char **display, const char *env) { 161 | int ret = wl_init(*display, env); 162 | if (ret == 0) { 163 | struct output *output; 164 | wl_list_for_each(output, &outputs, link) { 165 | // Only store first output mode 166 | ret = output->mode; 167 | break; 168 | } 169 | wl_deinit(); 170 | } 171 | return ret; 172 | } 173 | 174 | static int set(const char **display, const char *env, int level) { 175 | int ret = wl_init(*display, env); 176 | if (ret == 0) { 177 | struct output *output; 178 | wl_list_for_each(output, &outputs, link) { 179 | org_kde_kwin_dpms_set(output->dpms_control, level); 180 | } 181 | wl_display_roundtrip(dpy); 182 | wl_deinit(); 183 | } 184 | return ret; 185 | } 186 | -------------------------------------------------------------------------------- /src/modules/dpms_plugins/wl.c: -------------------------------------------------------------------------------- 1 | #include "wlr-output-power-management-unstable-v1-client-protocol.h" 2 | #include "wl_utils.h" 3 | #include "dpms.h" 4 | 5 | struct output { 6 | struct wl_output *wl_output; 7 | struct zwlr_output_power_v1 *dpms_control; 8 | uint32_t mode; 9 | bool failed; 10 | struct wl_list link; 11 | }; 12 | 13 | static void registry_handle_global(void *data, struct wl_registry *registry, 14 | uint32_t name, const char *interface, uint32_t version); 15 | static void registry_handle_global_remove(void *data, 16 | struct wl_registry *registry, uint32_t name); 17 | static void dpms_control_handle_mode(void *data, struct zwlr_output_power_v1 *zwlr_output_power_v1, uint32_t mode); 18 | static void dpms_control_handle_failed(void *data, struct zwlr_output_power_v1 *zwlr_output_power_v1); 19 | 20 | static int wl_init(const char *display, const char *env); 21 | static void wl_deinit(void); 22 | static void destroy_node(struct output *output); 23 | 24 | static struct wl_display *dpy; 25 | static struct wl_list outputs; 26 | static struct zwlr_output_power_manager_v1 *dpms_control_manager; 27 | static struct wl_registry *dpms_registry; 28 | 29 | static const struct wl_registry_listener registry_listener = { 30 | .global = registry_handle_global, 31 | .global_remove = registry_handle_global_remove, 32 | }; 33 | 34 | static const struct zwlr_output_power_v1_listener dpms_listener = { 35 | .failed = dpms_control_handle_failed, 36 | .mode = dpms_control_handle_mode, 37 | }; 38 | 39 | DPMS("Wl"); 40 | 41 | static void registry_handle_global(void *data, struct wl_registry *registry, 42 | uint32_t name, const char *interface, uint32_t version) { 43 | if (strcmp(interface, wl_output_interface.name) == 0) { 44 | struct output *output = calloc(1, sizeof(struct output)); 45 | output->wl_output = wl_registry_bind(registry, name, &wl_output_interface, 1); 46 | wl_list_insert(&outputs, &output->link); 47 | } else if (strcmp(interface, zwlr_output_power_manager_v1_interface.name) == 0) { 48 | dpms_control_manager = wl_registry_bind(registry, name, &zwlr_output_power_manager_v1_interface, 1); 49 | } 50 | } 51 | 52 | static void registry_handle_global_remove(void *data, 53 | struct wl_registry *registry, uint32_t name) { 54 | 55 | } 56 | 57 | static void dpms_control_handle_mode(void *data, struct zwlr_output_power_v1 *zwlr_output_power_v1, uint32_t mode) { 58 | struct output *output = data; 59 | output->mode = mode; 60 | } 61 | 62 | static void dpms_control_handle_failed(void *data, struct zwlr_output_power_v1 *zwlr_output_power_v1) { 63 | struct output *output = data; 64 | output->failed = true; 65 | } 66 | 67 | static int wl_init(const char *display, const char *env) { 68 | int ret = 0; 69 | dpy = fetch_wl_display(display, env); 70 | if (dpy == NULL) { 71 | ret = WRONG_PLUGIN; 72 | return ret; 73 | } 74 | wl_list_init(&outputs); 75 | dpms_registry = wl_display_get_registry(dpy); 76 | wl_registry_add_listener(dpms_registry, ®istry_listener, NULL); 77 | wl_display_roundtrip(dpy); 78 | 79 | if (dpms_control_manager == NULL) { 80 | fprintf(stderr, "compositor doesn't support wlr-output-power-management-unstable-v1\n"); 81 | ret = /*COMPOSITOR_NO_PROTOCOL*/ WRONG_PLUGIN; // Since we want DPMS to try kwin_wl afterwards 82 | goto err; 83 | } 84 | 85 | struct output *output; 86 | struct output *tmp_output; 87 | wl_list_for_each(output, &outputs, link) { 88 | output->dpms_control = zwlr_output_power_manager_v1_get_output_power(dpms_control_manager, output->wl_output); 89 | if (output->dpms_control) { 90 | zwlr_output_power_v1_add_listener(output->dpms_control, &dpms_listener, output); 91 | } else { 92 | fprintf(stderr, "failed to receive wlr DPMS control manager\n"); 93 | ret = -errno; 94 | goto err; 95 | } 96 | } 97 | wl_display_roundtrip(dpy); 98 | 99 | /* Check that all outputs were inited correctly */ 100 | wl_list_for_each_safe(output, tmp_output, &outputs, link) { 101 | if (output->wl_output == NULL || output->dpms_control == NULL) { 102 | fprintf(stderr, "failed to create wlr DPMS output\n"); 103 | ret = -ENOMEM; 104 | break; 105 | } 106 | if (output->failed) { 107 | wl_list_remove(&output->link); 108 | destroy_node(output); 109 | } 110 | } 111 | 112 | /* No supported output found */ 113 | if (wl_list_length(&outputs) == 0) { 114 | ret = UNSUPPORTED; 115 | } 116 | 117 | err: 118 | if (ret != 0) { 119 | wl_deinit(); 120 | } 121 | return ret; 122 | } 123 | 124 | static void wl_deinit(void) { 125 | struct output *output; 126 | struct output *tmp_output; 127 | wl_list_for_each_safe(output, tmp_output, &outputs, link) { 128 | wl_list_remove(&output->link); 129 | destroy_node(output); 130 | } 131 | if (dpms_registry) { 132 | wl_registry_destroy(dpms_registry); 133 | } 134 | if (dpms_control_manager) { 135 | zwlr_output_power_manager_v1_destroy(dpms_control_manager); 136 | } 137 | // NOTE: dpy is disconnected on program exit to workaround 138 | // gamma protocol limitation that resets gamma as soon as display is disconnected. 139 | // See wl_utils.c 140 | } 141 | 142 | static void destroy_node(struct output *output) { 143 | if (output->wl_output) { 144 | wl_output_destroy(output->wl_output); 145 | } 146 | if (output->dpms_control) { 147 | zwlr_output_power_v1_destroy(output->dpms_control); 148 | } 149 | free(output); 150 | } 151 | 152 | static int get(const char **display, const char *env) { 153 | int ret = wl_init(*display, env); 154 | if (ret == 0) { 155 | struct output *output; 156 | wl_list_for_each(output, &outputs, link) { 157 | // Only store first output mode 158 | // Negate the return because Clightd returns value 159 | // is 0 ON, 1 OFF. 160 | // See zwlr_output_power_v1_mode 161 | ret = !output->mode; 162 | break; 163 | } 164 | wl_deinit(); 165 | } 166 | return ret; 167 | } 168 | 169 | static int set(const char **display, const char *env, int level) { 170 | int ret = wl_init(*display, env); 171 | if (ret == 0) { 172 | struct output *output; 173 | wl_list_for_each(output, &outputs, link) { 174 | // Negate the level because Clightd uses value 175 | // is 0 ON, 1 OFF. 176 | // See zwlr_output_power_v1_mode 177 | zwlr_output_power_v1_set_mode(output->dpms_control, !level); 178 | } 179 | wl_display_roundtrip(dpy); 180 | wl_deinit(); 181 | } 182 | return ret; 183 | } 184 | -------------------------------------------------------------------------------- /src/modules/dpms_plugins/xorg.c: -------------------------------------------------------------------------------- 1 | #include "xorg_utils.h" 2 | #include 3 | #include "dpms.h" 4 | #include "bus_utils.h" 5 | 6 | DPMS("Xorg"); 7 | 8 | /* 9 | * info->power_level is one of: 10 | * DPMS Extension Power Levels 11 | * 0 DPMSModeOn In use 12 | * 1 DPMSModeStandby Blanked, low power 13 | * 2 DPMSModeSuspend Blanked, lower power 14 | * 3 DPMSModeOff Shut off, awaiting activity 15 | */ 16 | static int get(const char **display, const char *xauthority) { 17 | BOOL onoff; 18 | CARD16 s; 19 | int ret = WRONG_PLUGIN; 20 | 21 | Display *dpy = fetch_xorg_display(display, xauthority); 22 | if (dpy) { 23 | if (DPMSCapable(dpy)) { 24 | DPMSInfo(dpy, &s, &onoff); 25 | ret = s; 26 | } else { 27 | ret = UNSUPPORTED; 28 | } 29 | XCloseDisplay(dpy); 30 | } 31 | return ret; 32 | } 33 | 34 | static int set(const char **display, const char *xauthority, int dpms_level) { 35 | int ret = WRONG_PLUGIN; 36 | 37 | Display *dpy = fetch_xorg_display(display, xauthority); 38 | if (dpy) { 39 | if (DPMSCapable(dpy)) { 40 | DPMSEnable(dpy); 41 | DPMSForceLevel(dpy, dpms_level); 42 | XFlush(dpy); 43 | ret = 0; 44 | } else { 45 | ret = UNSUPPORTED; 46 | } 47 | XCloseDisplay(dpy); 48 | } 49 | return ret; 50 | } 51 | -------------------------------------------------------------------------------- /src/modules/gamma.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "commons.h" 4 | 5 | struct _gamma_cl; 6 | 7 | #define _GAMMA_PLUGINS \ 8 | X(XORG, 0) \ 9 | X(WL, 1) \ 10 | X(DRM, 2) 11 | 12 | enum gamma_plugins { 13 | #define X(name, val) name = val, 14 | _GAMMA_PLUGINS 15 | #undef X 16 | GAMMA_NUM 17 | }; 18 | 19 | struct _gamma_plugin; 20 | 21 | typedef struct _gamma_cl { 22 | unsigned int target_temp; 23 | bool is_smooth; 24 | unsigned int smooth_step; 25 | unsigned int smooth_wait; 26 | unsigned int current_temp; 27 | const char *display; 28 | const char *env; 29 | int fd; 30 | struct _gamma_plugin *plugin; 31 | void *priv; 32 | } gamma_client; 33 | 34 | /* 35 | * validate() takes double pointer as 36 | * drm plugin may override id. 37 | */ 38 | typedef struct _gamma_plugin { 39 | const char *name; 40 | int (*validate)(const char **id, const char *env, void **priv_data); 41 | int (*set)(void *priv_data, const int temp); 42 | int (*get)(void *priv_data); 43 | void (*dtor)(void *priv_data); 44 | char obj_path[100]; 45 | } gamma_plugin; 46 | 47 | #define GAMMA(name) \ 48 | static int validate(const char **id, const char *env, void **priv_data); \ 49 | static int set(void *priv_data, const int temp); \ 50 | static int get(void *priv_data); \ 51 | static void dtor(void *priv_data); \ 52 | static void _ctor_ register_gamma_plugin(void) { \ 53 | static gamma_plugin self = { name, validate, set, get, dtor }; \ 54 | gamma_register_new(&self); \ 55 | } 56 | 57 | void gamma_register_new(gamma_plugin *plugin); 58 | double clamp(double x, double min, double max); 59 | int get_temp(const unsigned short R, const unsigned short B); 60 | void fill_gamma_table(uint16_t *r, uint16_t *g, uint16_t *b, double br, uint32_t ramp_size, int temp); 61 | 62 | /* 63 | * Gamma brightness related (ie: emulated backlight). 64 | * When gamma is not enabled, these APIs won't do anything 65 | */ 66 | int set_gamma_brightness(const char *id, double brightness); 67 | double get_gamma_brightness(const char *id); 68 | int clean_gamma_brightness(const char *id); 69 | -------------------------------------------------------------------------------- /src/modules/gamma_plugins/drm.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include "gamma.h" 3 | #include "drm_utils.h" 4 | 5 | typedef struct { 6 | int fd; 7 | drmModeRes *res; 8 | } drm_gamma_priv; 9 | 10 | GAMMA("Drm"); 11 | 12 | static int validate(const char **id, const char *env, void **priv_data) { 13 | int ret = WRONG_PLUGIN; 14 | int fd = drm_open_card(id); 15 | if (fd < 0) { 16 | return ret; 17 | } 18 | 19 | drmModeRes *res = drmModeGetResources(fd); 20 | if (res && res->count_crtcs > 0) { 21 | *priv_data = malloc(sizeof(drm_gamma_priv)); 22 | drm_gamma_priv *priv = (drm_gamma_priv *)*priv_data; 23 | if (priv) { 24 | priv->fd = fd; 25 | priv->res = res; 26 | ret = 0; 27 | } else { 28 | ret = -ENOMEM; 29 | } 30 | } else { 31 | perror("gamma drmModeGetResources"); 32 | ret = UNSUPPORTED; 33 | } 34 | 35 | if (ret != 0) { 36 | if (res) { 37 | drmModeFreeResources(res); 38 | } 39 | if (fd != -1) { 40 | close(fd); 41 | } 42 | } 43 | return ret; 44 | } 45 | 46 | // Added for compatibility with libdrm<2.4.112 47 | #if LIBDRM_VERSION_MAJ <= 2 && LIBDRM_VERSION_MIN <= 4 && LIBDRM_VERSION_PATCH < 112 48 | const char *drmModeGetConnectorTypeName(uint32_t connector_type) { 49 | /* Keep the strings in sync with the kernel's drm_connector_enum_list in 50 | * drm_connector.c. */ 51 | switch (connector_type) { 52 | case DRM_MODE_CONNECTOR_Unknown: 53 | return "Unknown"; 54 | case DRM_MODE_CONNECTOR_VGA: 55 | return "VGA"; 56 | case DRM_MODE_CONNECTOR_DVII: 57 | return "DVI-I"; 58 | case DRM_MODE_CONNECTOR_DVID: 59 | return "DVI-D"; 60 | case DRM_MODE_CONNECTOR_DVIA: 61 | return "DVI-A"; 62 | case DRM_MODE_CONNECTOR_Composite: 63 | return "Composite"; 64 | case DRM_MODE_CONNECTOR_SVIDEO: 65 | return "SVIDEO"; 66 | case DRM_MODE_CONNECTOR_LVDS: 67 | return "LVDS"; 68 | case DRM_MODE_CONNECTOR_Component: 69 | return "Component"; 70 | case DRM_MODE_CONNECTOR_9PinDIN: 71 | return "DIN"; 72 | case DRM_MODE_CONNECTOR_DisplayPort: 73 | return "DP"; 74 | case DRM_MODE_CONNECTOR_HDMIA: 75 | return "HDMI-A"; 76 | case DRM_MODE_CONNECTOR_HDMIB: 77 | return "HDMI-B"; 78 | case DRM_MODE_CONNECTOR_TV: 79 | return "TV"; 80 | case DRM_MODE_CONNECTOR_eDP: 81 | return "eDP"; 82 | case DRM_MODE_CONNECTOR_VIRTUAL: 83 | return "Virtual"; 84 | case DRM_MODE_CONNECTOR_DSI: 85 | return "DSI"; 86 | case DRM_MODE_CONNECTOR_DPI: 87 | return "DPI"; 88 | case DRM_MODE_CONNECTOR_WRITEBACK: 89 | return "Writeback"; 90 | case DRM_MODE_CONNECTOR_SPI: 91 | return "SPI"; 92 | case DRM_MODE_CONNECTOR_USB: 93 | return "USB"; 94 | default: 95 | return NULL; 96 | } 97 | } 98 | #endif 99 | 100 | static double get_connector_br(drmModeConnectorPtr p) { 101 | char drm_name[32]; 102 | const char *conn_type_name = drmModeGetConnectorTypeName(p->connector_type); 103 | snprintf(drm_name, sizeof(drm_name), "%s-%u", conn_type_name ? conn_type_name : "Unknown", p->connector_type_id); 104 | return get_gamma_brightness(drm_name); 105 | } 106 | 107 | static int set(void *priv_data, const int temp) { 108 | drm_gamma_priv *priv = (drm_gamma_priv *)priv_data; 109 | 110 | int ret = 0; 111 | 112 | if (drmSetMaster(priv->fd)) { 113 | perror("SetMaster"); 114 | ret = -errno; 115 | goto end; 116 | } 117 | 118 | for (int i = 0; i < priv->res->count_connectors && !ret; i++) { 119 | drmModeConnectorPtr p = drmModeGetConnector(priv->fd, priv->res->connectors[i]); 120 | if (!p || p->connection != DRM_MODE_CONNECTED) { 121 | if (p) { 122 | drmModeFreeConnector(p); 123 | } 124 | continue; 125 | } 126 | drmModeEncoderPtr enc = drmModeGetEncoder(priv->fd, p->encoder_id); 127 | if (!enc) { 128 | continue; 129 | } 130 | drmModeCrtc *crtc_info = drmModeGetCrtc(priv->fd, enc->crtc_id); 131 | if (!crtc_info) { 132 | drmModeFreeEncoder(enc); 133 | continue; 134 | } 135 | const double br = get_connector_br(p); 136 | const int ramp_size = crtc_info->gamma_size; 137 | uint16_t *r = calloc(ramp_size, sizeof(uint16_t)); 138 | uint16_t *g = calloc(ramp_size, sizeof(uint16_t)); 139 | uint16_t *b = calloc(ramp_size, sizeof(uint16_t)); 140 | fill_gamma_table(r, g, b, br, ramp_size, temp); 141 | ret = drmModeCrtcSetGamma(priv->fd, enc->crtc_id, ramp_size, r, g, b); 142 | if (ret) { 143 | ret = -errno; 144 | perror("drmModeCrtcSetGamma"); 145 | } 146 | free(r); 147 | free(g); 148 | free(b); 149 | drmModeFreeCrtc(crtc_info); 150 | drmModeFreeEncoder(enc); 151 | drmModeFreeConnector(p); 152 | } 153 | 154 | if (drmDropMaster(priv->fd)) { 155 | perror("DropMaster"); 156 | } 157 | 158 | end: 159 | return ret; 160 | } 161 | 162 | static int get(void *priv_data) { 163 | drm_gamma_priv *priv = (drm_gamma_priv *)priv_data; 164 | 165 | int temp = -1; 166 | for (int i = 0; i < priv->res->count_connectors; i++) { 167 | drmModeConnectorPtr p = drmModeGetConnector(priv->fd, priv->res->connectors[i]); 168 | if (!p || p->connection != DRM_MODE_CONNECTED) { 169 | if (p) { 170 | drmModeFreeConnector(p); 171 | } 172 | continue; 173 | } 174 | drmModeEncoderPtr enc = drmModeGetEncoder(priv->fd, p->encoder_id); 175 | if (!enc) { 176 | drmModeFreeConnector(p); 177 | continue; 178 | } 179 | drmModeCrtc *crtc_info = drmModeGetCrtc(priv->fd, enc->crtc_id); 180 | const int ramp_size = crtc_info->gamma_size; 181 | 182 | uint16_t *red = calloc(ramp_size, sizeof(uint16_t)); 183 | uint16_t *green = calloc(ramp_size, sizeof(uint16_t)); 184 | uint16_t *blue = calloc(ramp_size, sizeof(uint16_t)); 185 | 186 | int r = drmModeCrtcGetGamma(priv->fd, enc->crtc_id, ramp_size, red, green, blue); 187 | if (r) { 188 | perror("drmModeCrtcSetGamma"); 189 | } else { 190 | const double br = get_connector_br(p); 191 | temp = get_temp(clamp(red[1] / br, 0, 255), clamp(blue[1] / br, 0, 255)); 192 | } 193 | 194 | free(red); 195 | free(green); 196 | free(blue); 197 | 198 | drmModeFreeCrtc(crtc_info); 199 | drmModeFreeEncoder(enc); 200 | drmModeFreeConnector(p); 201 | break; 202 | } 203 | return temp; 204 | } 205 | 206 | static void dtor(void *priv_data) { 207 | drm_gamma_priv *priv = (drm_gamma_priv *)priv_data; 208 | drmModeFreeResources(priv->res); 209 | close(priv->fd); 210 | free(priv_data); 211 | } 212 | -------------------------------------------------------------------------------- /src/modules/gamma_plugins/xorg.c: -------------------------------------------------------------------------------- 1 | #include "gamma.h" 2 | #include 3 | #include "bus_utils.h" 4 | #include "xorg_utils.h" 5 | #include 6 | 7 | #define XORG_DRM_MAP_ENV "CLIGHTD_XORG_TO_DRM" 8 | 9 | typedef struct { 10 | Display *dpy; 11 | XRRScreenResources *res; 12 | } xorg_gamma_priv; 13 | 14 | GAMMA("Xorg"); 15 | 16 | static int validate(const char **id, const char *env, void **priv_data) { 17 | int ret = WRONG_PLUGIN; 18 | 19 | Display *dpy = fetch_xorg_display(id, env); 20 | if (dpy) { 21 | Window root = DefaultRootWindow(dpy); 22 | XRRScreenResources *res = XRRGetScreenResourcesCurrent(dpy, root); 23 | if (res) { 24 | *priv_data = calloc(1, sizeof(xorg_gamma_priv)); 25 | xorg_gamma_priv *priv = (xorg_gamma_priv *)*priv_data; 26 | priv->dpy = dpy; 27 | priv->res = res; 28 | ret = 0; 29 | } else { 30 | ret = UNSUPPORTED; 31 | XCloseDisplay(dpy); 32 | } 33 | } 34 | return ret; 35 | } 36 | 37 | static double get_output_br(XRROutputInfo *info) { 38 | /* 39 | * Sometimes Xorg output name differs from 40 | * /sys/class/drm node 41 | * Eg: drm node is called HDMI-A-1 but xorg sees HDMI-A-0 :/ 42 | */ 43 | const char *to_drm_mapper = getenv(XORG_DRM_MAP_ENV); 44 | if (to_drm_mapper) { 45 | char map[1024]; 46 | snprintf(map, sizeof(map), "%s", to_drm_mapper); 47 | char *s = strtok(map, ","); 48 | while (s) { 49 | char *val = strchr(s, ':'); 50 | if (val && strlen(val) > 0) { 51 | *val = '\0'; 52 | val++; 53 | } else { 54 | fprintf(stderr, "Wrong %s format: %s\n", XORG_DRM_MAP_ENV, s); 55 | goto err; 56 | } 57 | if (strcmp(s, info->name) == 0) { 58 | return get_gamma_brightness(val); 59 | } 60 | s = strtok(NULL, ","); 61 | } 62 | } 63 | 64 | err: 65 | return get_gamma_brightness(info->name); 66 | } 67 | 68 | static int set(void *priv_data, const int temp) { 69 | xorg_gamma_priv *priv = (xorg_gamma_priv *)priv_data; 70 | 71 | for (int i = 0; i < priv->res->noutput; i++) { 72 | XRROutputInfo *info = XRRGetOutputInfo(priv->dpy, priv->res, priv->res->outputs[i]); 73 | if (!info || info->crtc == 0 || info->connection != RR_Connected) { 74 | if (info) { 75 | XRRFreeOutputInfo(info); 76 | } 77 | continue; 78 | } 79 | const double br = get_output_br(info); 80 | const int crtcxid = info->crtc; 81 | const int size = XRRGetCrtcGammaSize(priv->dpy, crtcxid); 82 | XRRCrtcGamma *crtc_gamma = XRRAllocGamma(size); 83 | fill_gamma_table(crtc_gamma->red, crtc_gamma->green, crtc_gamma->blue, br, size, temp); 84 | XRRSetCrtcGamma(priv->dpy, crtcxid, crtc_gamma); 85 | XFree(crtc_gamma); 86 | XRRFreeOutputInfo(info); 87 | } 88 | return 0; 89 | } 90 | 91 | static int get(void *priv_data) { 92 | xorg_gamma_priv *priv = (xorg_gamma_priv *)priv_data; 93 | 94 | int temp = -1; 95 | if (priv->res && priv->res->ncrtc > 0) { 96 | for (int i = 0; i < priv->res->noutput; i++) { 97 | XRROutputInfo *info = XRRGetOutputInfo(priv->dpy, priv->res, priv->res->outputs[i]); 98 | if (!info || info->crtc == 0 || info->connection != RR_Connected) { 99 | if (info) { 100 | XRRFreeOutputInfo(info); 101 | } 102 | continue; 103 | } 104 | const double br = get_output_br(info); 105 | const int crtcxid = info->crtc; 106 | XRRCrtcGamma *crtc_gamma = XRRGetCrtcGamma(priv->dpy, crtcxid); 107 | const int size = crtc_gamma->size; 108 | const int g = (65535.0 * (size - 1) / size) / 255; 109 | temp = get_temp(clamp(crtc_gamma->red[size - 1] / (g * br), 0, 255), clamp(crtc_gamma->blue[size - 1] / (g * br), 0, 255)); 110 | XFree(crtc_gamma); 111 | XRRFreeOutputInfo(info); 112 | break; 113 | } 114 | } 115 | return temp; 116 | } 117 | 118 | static void dtor(void *priv_data) { 119 | xorg_gamma_priv *priv = (xorg_gamma_priv *)priv_data; 120 | XRRFreeScreenResources(priv->res); 121 | XCloseDisplay(priv->dpy); 122 | free(priv_data); 123 | } 124 | -------------------------------------------------------------------------------- /src/modules/keyboard.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | #define KBD_SUBSYSTEM "leds" 8 | #define KBD_SYSNAME_MATCH "kbd_backlight" 9 | 10 | #define WITH_KBD_DEVICE(k, fn) \ 11 | struct udev_device *kbd_dev = udev_device_new_from_subsystem_sysname(udev, KBD_SUBSYSTEM, k->sysname); \ 12 | fn; \ 13 | udev_device_unref(kbd_dev); 14 | 15 | typedef struct { 16 | int max; 17 | char obj_path[100]; 18 | sd_bus_slot *slot; // vtable's slot 19 | const char *sysname; 20 | } kbd_t; 21 | 22 | static void dtor_kbd(void *data); 23 | static int kbd_new(struct udev_device *dev, void *userdata); 24 | static inline int set_value(kbd_t *k, const char *sysattr, int value); 25 | static map_ret_code set_brightness(void *userdata, const char *key, void *data); 26 | static map_ret_code set_timeout(void *userdata, const char *key, void *data); 27 | static map_ret_code append_backlight(void *userdata, const char *key, void *data); 28 | static map_ret_code append_timeout(void *userdata, const char *key, void *data); 29 | static int method_setkeyboard(sd_bus_message *m, void *userdata, sd_bus_error *ret_error); 30 | static int method_getkeyboard(sd_bus_message *m, void *userdata, sd_bus_error *ret_error); 31 | static int method_settimeout(sd_bus_message *m, void *userdata, sd_bus_error *ret_error); 32 | static int method_gettimeout(sd_bus_message *m, void *userdata, sd_bus_error *ret_error); 33 | static int fetch_timeout(kbd_t *k); 34 | 35 | MODULE("KEYBOARD"); 36 | 37 | static const char object_path[] = "/org/clightd/clightd/KbdBacklight"; 38 | static const char main_interface[] = "org.clightd.clightd.KbdBacklight"; 39 | static const sd_bus_vtable main_vtable[] = { 40 | SD_BUS_VTABLE_START(0), 41 | SD_BUS_METHOD("Set", "d", "b", method_setkeyboard, SD_BUS_VTABLE_UNPRIVILEGED), 42 | SD_BUS_METHOD("Get", NULL, "a(sd)", method_getkeyboard, SD_BUS_VTABLE_UNPRIVILEGED), 43 | SD_BUS_METHOD("SetTimeout", "i", "b", method_settimeout, SD_BUS_VTABLE_UNPRIVILEGED), 44 | SD_BUS_METHOD("GetTimeout", NULL, "a(si)", method_gettimeout, SD_BUS_VTABLE_UNPRIVILEGED), 45 | SD_BUS_SIGNAL("Changed", "sd", 0), 46 | SD_BUS_VTABLE_END 47 | }; 48 | static const char bus_interface[] = "org.clightd.clightd.KbdBacklight.Server"; 49 | static const sd_bus_vtable vtable[] = { 50 | SD_BUS_VTABLE_START(0), 51 | SD_BUS_METHOD("Set", "d", "b", method_setkeyboard, SD_BUS_VTABLE_UNPRIVILEGED), 52 | SD_BUS_METHOD("Get", NULL, "d", method_getkeyboard, SD_BUS_VTABLE_UNPRIVILEGED), 53 | SD_BUS_METHOD("SetTimeout", "i", "b", method_settimeout, SD_BUS_VTABLE_UNPRIVILEGED), 54 | SD_BUS_METHOD("GetTimeout", NULL, "i", method_gettimeout, SD_BUS_VTABLE_UNPRIVILEGED), 55 | SD_BUS_PROPERTY("MaxBrightness", "i", NULL, offsetof(kbd_t, max), SD_BUS_VTABLE_PROPERTY_CONST), 56 | SD_BUS_SIGNAL("Changed", "d", 0), 57 | SD_BUS_VTABLE_END 58 | }; 59 | 60 | static map_t *kbds; 61 | static struct udev_monitor *mon; 62 | 63 | static void module_pre_start(void) { 64 | 65 | } 66 | 67 | static bool check(void) { 68 | return true; 69 | } 70 | 71 | static bool evaluate(void) { 72 | return true; 73 | } 74 | 75 | static void init(void) { 76 | kbds = map_new(false, dtor_kbd); 77 | sd_bus_add_object_manager(bus, NULL, object_path); 78 | int r = sd_bus_add_object_vtable(bus, 79 | NULL, 80 | object_path, 81 | main_interface, 82 | main_vtable, 83 | NULL); 84 | if (r < 0) { 85 | m_log("Failed to issue method call: %s\n", strerror(-r)); 86 | } else { 87 | const udev_match match = { .sysname = "*"KBD_SYSNAME_MATCH }; 88 | udev_devices_foreach(KBD_SUBSYSTEM, &match, kbd_new, NULL); 89 | int fd = init_udev_monitor(KBD_SUBSYSTEM, &mon); 90 | m_register_fd(fd, false, NULL); 91 | } 92 | } 93 | 94 | static void receive(const msg_t *msg, const void *userdata) { 95 | if (msg->is_pubsub) { 96 | return; 97 | } 98 | struct udev_device *dev = udev_monitor_receive_device(mon); 99 | if (!dev) { 100 | return; 101 | } 102 | const char *key = udev_device_get_sysname(dev); 103 | if (strstr(key, KBD_SYSNAME_MATCH)) { 104 | const char *action = udev_device_get_action(dev); 105 | if (action) { 106 | kbd_t *k = map_get(kbds, key); 107 | if (!strcmp(action, UDEV_ACTION_ADD)) { 108 | if (!k) { 109 | kbd_new(dev, &k); 110 | if (k) { 111 | sd_bus_emit_object_added(bus, k->obj_path); 112 | } 113 | } 114 | } else if (!strcmp(action, UDEV_ACTION_RM)) { 115 | if (k) { 116 | sd_bus_emit_object_removed(bus, k->obj_path); 117 | map_remove(kbds, key); 118 | } 119 | } else if (!strcmp(action, UDEV_ACTION_CHANGE)) { 120 | // Changed event! 121 | /* Note: it seems like "change" udev signal is never triggered for kbd backlight though */ 122 | if (k) { 123 | int curr = atoi(udev_device_get_sysattr_value(dev, "brightness")); 124 | const double pct = (double)curr / k->max; 125 | 126 | // Emit on global object 127 | sd_bus_emit_signal(bus, object_path, main_interface, "Changed", "sd", udev_device_get_sysname(dev), pct); 128 | // Emit on specific object too! 129 | sd_bus_emit_signal(bus, k->obj_path, bus_interface, "Changed", "d", pct); 130 | } 131 | } 132 | } 133 | } 134 | udev_device_unref(dev); 135 | } 136 | 137 | static void destroy(void) { 138 | udev_monitor_unref(mon); 139 | map_free(kbds); 140 | } 141 | 142 | static void dtor_kbd(void *data) { 143 | kbd_t *k = (kbd_t *)data; 144 | sd_bus_slot_unref(k->slot); 145 | free((void *)k->sysname); 146 | free(k); 147 | } 148 | 149 | static int kbd_new(struct udev_device *dev, void *userdata) { 150 | kbd_t *k = calloc(1, sizeof(kbd_t)); 151 | if (!k) { 152 | m_log("failed to malloc.\n"); 153 | return -1; 154 | } 155 | 156 | k->max = atoi(udev_device_get_sysattr_value(dev, "max_brightness")); 157 | k->sysname = strdup(udev_device_get_sysname(dev)); 158 | 159 | make_valid_obj_path(k->obj_path, sizeof(k->obj_path), object_path, k->sysname); 160 | 161 | int r = sd_bus_add_object_vtable(bus, &k->slot, k->obj_path, bus_interface, vtable, k); 162 | if (r < 0) { 163 | m_log("Failed to add object vtable on path '%s': %d\n", k->obj_path, r); 164 | dtor_kbd(k); 165 | } else { 166 | map_put(kbds, k->sysname, k); 167 | } 168 | 169 | if (userdata) { 170 | kbd_t **ktmp = (kbd_t **)userdata; 171 | *ktmp = k; 172 | } 173 | return 0; 174 | } 175 | 176 | static inline int set_value(kbd_t *k, const char *sysattr, int value) { 177 | char val[15] = {0}; 178 | snprintf(val, sizeof(val) - 1, "%d", value); 179 | int ret; 180 | WITH_KBD_DEVICE(k, { 181 | ret = udev_device_set_sysattr_value(kbd_dev, sysattr, val); 182 | }); 183 | return ret; 184 | } 185 | 186 | static map_ret_code set_brightness(void *userdata, const char *key, void *data) { 187 | double target_pct = *((double *)userdata); 188 | kbd_t *k = (kbd_t *)data; 189 | 190 | if (set_value(k, "brightness", (int)round(target_pct * k->max)) >= 0) { 191 | // Emit on global object 192 | sd_bus_emit_signal(bus, object_path, main_interface, "Changed", "sd", k->sysname, target_pct); 193 | // Emit on specific object too! 194 | sd_bus_emit_signal(bus, k->obj_path, bus_interface, "Changed", "d", target_pct); 195 | return MAP_OK; 196 | } 197 | return MAP_ERR; 198 | } 199 | 200 | static map_ret_code set_timeout(void *userdata, const char *key, void *data) { 201 | int value = *((int *)userdata); 202 | kbd_t *k = (kbd_t *)data; 203 | if (set_value(k, "stop_timeout", value) >= 0) { 204 | return MAP_OK; 205 | } 206 | return MAP_ERR; 207 | } 208 | 209 | static int method_setkeyboard(sd_bus_message *m, void *userdata, sd_bus_error *ret_error) { 210 | ASSERT_AUTH(); 211 | 212 | double target_pct; 213 | int r = sd_bus_message_read(m, "d", &target_pct); 214 | if (r >= 0) { 215 | if (target_pct >= 0.0 && target_pct <= 1.0) { 216 | kbd_t *k = (kbd_t *)userdata; 217 | if (k) { 218 | r = set_brightness(&target_pct, NULL, k); 219 | } else { 220 | r = map_iterate(kbds, set_brightness, &target_pct); 221 | } 222 | return sd_bus_reply_method_return(m, "b", r >= 0); 223 | } 224 | sd_bus_error_set_errno(ret_error, EINVAL); 225 | return -EINVAL; 226 | } 227 | return r; 228 | } 229 | 230 | static int method_getkeyboard(sd_bus_message *m, void *userdata, sd_bus_error *ret_error) { 231 | kbd_t *k = (kbd_t *)userdata; 232 | if (k) { 233 | int curr; 234 | WITH_KBD_DEVICE(k, { 235 | curr = atoi(udev_device_get_sysattr_value(kbd_dev, "brightness")); 236 | }); 237 | const double pct = (double)curr / k->max; 238 | return sd_bus_reply_method_return(m, "d", pct); 239 | } 240 | sd_bus_message *reply = NULL; 241 | sd_bus_message_new_method_return(m, &reply); 242 | sd_bus_message_open_container(reply, SD_BUS_TYPE_ARRAY, "(sd)"); 243 | int r = map_iterate(kbds, append_backlight, reply); 244 | sd_bus_message_close_container(reply); 245 | if (r == 0) { 246 | r = sd_bus_send(NULL, reply, NULL); 247 | } 248 | sd_bus_message_unref(reply); 249 | return r; 250 | } 251 | 252 | static map_ret_code append_backlight(void *userdata, const char *key, void *data) { 253 | sd_bus_message *reply = (sd_bus_message *)userdata; 254 | 255 | kbd_t *k = (kbd_t *)data; 256 | int curr; 257 | WITH_KBD_DEVICE(k, { 258 | curr = atoi(udev_device_get_sysattr_value(kbd_dev, "brightness")); 259 | }); 260 | const double pct = (double)curr / k->max; 261 | 262 | sd_bus_message_open_container(reply, SD_BUS_TYPE_STRUCT, "sd"); 263 | sd_bus_message_append(reply, "sd", key, pct); 264 | sd_bus_message_close_container(reply); 265 | 266 | return MAP_OK; 267 | } 268 | 269 | static int method_settimeout(sd_bus_message *m, void *userdata, sd_bus_error *ret_error) { 270 | ASSERT_AUTH(); 271 | 272 | int timeout; 273 | int r = sd_bus_message_read(m, "i", &timeout); 274 | if (r >= 0) { 275 | if (timeout > 0) { 276 | kbd_t *k = (kbd_t *)userdata; 277 | if (k) { 278 | r = set_timeout(&timeout, NULL, k); 279 | } else { 280 | r = map_iterate(kbds, set_timeout, &timeout); 281 | } 282 | return sd_bus_reply_method_return(m, "b", r >= 0); 283 | } 284 | sd_bus_error_set_errno(ret_error, EINVAL); 285 | return -EINVAL; 286 | } 287 | return r; 288 | } 289 | 290 | static int method_gettimeout(sd_bus_message *m, void *userdata, sd_bus_error *ret_error) { 291 | kbd_t *k = (kbd_t *)userdata; 292 | if (k) { 293 | int tm = fetch_timeout(k); 294 | if (tm >= 0) { 295 | return sd_bus_reply_method_return(m, "i", tm); 296 | } 297 | return sd_bus_error_set_errno(ret_error, -tm); 298 | } 299 | sd_bus_message *reply = NULL; 300 | sd_bus_message_new_method_return(m, &reply); 301 | sd_bus_message_open_container(reply, SD_BUS_TYPE_ARRAY, "(si)"); 302 | int r = map_iterate(kbds, append_timeout, reply); 303 | sd_bus_message_close_container(reply); 304 | if (r == 0) { 305 | r = sd_bus_send(NULL, reply, NULL); 306 | } 307 | sd_bus_message_unref(reply); 308 | return r; 309 | } 310 | 311 | static int fetch_timeout(kbd_t *k) { 312 | const char *timeout; 313 | WITH_KBD_DEVICE(k, { 314 | timeout = udev_device_get_sysattr_value(kbd_dev, "stop_timeout"); 315 | }); 316 | if (timeout) { 317 | int tm; 318 | char suffix = 's'; 319 | if (sscanf(timeout, "%d%c", &tm, &suffix) >= 1) { 320 | const char suffixes[] = { 's', 'm', 'h' }; 321 | for (int i = 0; i < SIZE(suffixes); i++) { 322 | if (suffix == suffixes[i]) { 323 | if (i > 0) { 324 | tm *= i * 60; 325 | } 326 | break; 327 | } 328 | } 329 | return tm; 330 | } 331 | return -EINVAL; 332 | } 333 | return -ENOENT; 334 | } 335 | 336 | static map_ret_code append_timeout(void *userdata, const char *key, void *data) { 337 | sd_bus_message *reply = (sd_bus_message *)userdata; 338 | 339 | kbd_t *k = (kbd_t *)data; 340 | int tm = fetch_timeout(k); 341 | if (tm >= 0) { 342 | sd_bus_message_open_container(reply, SD_BUS_TYPE_STRUCT, "si"); 343 | sd_bus_message_append(reply, "si", key, tm); 344 | sd_bus_message_close_container(reply); 345 | } 346 | return MAP_OK; 347 | } 348 | -------------------------------------------------------------------------------- /src/modules/screen.c: -------------------------------------------------------------------------------- 1 | #ifdef SCREEN_PRESENT 2 | 3 | #include "screen.h" 4 | #include "bus_utils.h" 5 | 6 | #define MONITOR_ILL_MAX 255 7 | 8 | static int method_getbrightness(sd_bus_message *m, void *userdata, sd_bus_error *ret_error); 9 | 10 | static screen_plugin *plugins[SCREEN_NUM]; 11 | static const char object_path[] = "/org/clightd/clightd/Screen"; 12 | static const char bus_interface[] = "org.clightd.clightd.Screen"; 13 | static const sd_bus_vtable vtable[] = { 14 | SD_BUS_VTABLE_START(0), 15 | SD_BUS_METHOD("GetEmittedBrightness", "ss", "d", method_getbrightness, SD_BUS_VTABLE_UNPRIVILEGED), 16 | SD_BUS_VTABLE_END 17 | }; 18 | 19 | MODULE("SCREEN"); 20 | 21 | static void module_pre_start(void) { 22 | 23 | } 24 | 25 | static bool check(void) { 26 | return true; 27 | } 28 | 29 | static bool evaluate(void) { 30 | return true; 31 | } 32 | 33 | static void init(void) { 34 | int r = sd_bus_add_object_vtable(bus, 35 | NULL, 36 | object_path, 37 | bus_interface, 38 | vtable, 39 | NULL); 40 | for (int i = 0; i < SCREEN_NUM && !r; i++) { 41 | if (plugins[i]) { 42 | snprintf(plugins[i]->obj_path, sizeof(plugins[i]->obj_path) - 1, "%s/%s", object_path, plugins[i]->name); 43 | r += sd_bus_add_object_vtable(bus, 44 | NULL, 45 | plugins[i]->obj_path, 46 | bus_interface, 47 | vtable, 48 | plugins[i]); 49 | } 50 | } 51 | if (r < 0) { 52 | m_log("Failed to issue method call: %s\n", strerror(-r)); 53 | } 54 | } 55 | 56 | static void receive(const msg_t *msg, const void *userdata) { 57 | 58 | } 59 | 60 | static void destroy(void) { 61 | 62 | } 63 | 64 | void screen_register_new(screen_plugin *plugin) { 65 | const char *plugins_names[] = { 66 | #define X(name, val) #name, 67 | _SCREEN_PLUGINS 68 | #undef X 69 | }; 70 | 71 | int i; 72 | for (i = 0; i < SCREEN_NUM; i++) { 73 | if (strcasestr(plugins_names[i], plugin->name)) { 74 | break; 75 | } 76 | } 77 | 78 | if (i < SCREEN_NUM) { 79 | plugins[i] = plugin; 80 | printf("Registered '%s' screen plugin.\n", plugin->name); 81 | } else { 82 | printf("Screen plugin '%s' not recognized. Not registering.\n", plugin->name); 83 | } 84 | } 85 | 86 | int rgb_frame_brightness(const uint8_t *data, const int width, const int height, const int stride) { 87 | /* 88 | * takes 1 pixel every div*div area 89 | * (div values > 8 will almost not give performance improvements) 90 | */ 91 | int r = 0, g = 0, b = 0; 92 | const int div = 8; 93 | const int wmax = (double)width / div; 94 | const int hmax = (double)height / div; 95 | const int pixelsize = (double)stride / width; 96 | for (int i = 0; i < wmax; i++) { 97 | for (int k = 0; k < hmax; k++) { 98 | /* obtain r,g,b components */ 99 | const uint8_t *p = data + (i * div * pixelsize + k * div * stride); 100 | r += p[2] & 0xFF; 101 | g += p[1] & 0xFF; 102 | b += p[0] & 0xFF; 103 | } 104 | } 105 | const int area = wmax * hmax; 106 | r = (double)r / area; 107 | g = (double)g / area; 108 | b = (double)b / area; 109 | /* https://en.wikipedia.org/wiki/Rec._709#luma_coefficients */ 110 | /* https://www.itu.int/dms_pubrec/itu-r/rec/bt/R-REC-BT.709-6-201506-I!!PDF-E.pdf */ 111 | return 0.2126 * r + 0.7152 * g + 0.0722 * b; 112 | } 113 | 114 | static int method_getbrightness(sd_bus_message* m, void* userdata, sd_bus_error* ret_error) { 115 | const char *display = NULL, *env = NULL; 116 | 117 | /* Read the parameters */ 118 | int r = sd_bus_message_read(m, "ss", &display, &env); 119 | if (r < 0) { 120 | m_log("Failed to parse parameters: %s\n", strerror(-r)); 121 | return r; 122 | } 123 | 124 | bus_sender_fill_creds(m); 125 | 126 | screen_plugin *plugin = userdata; 127 | int br = WRONG_PLUGIN; 128 | if (!plugin) { 129 | for (int i = 0; i < SCREEN_NUM && br == WRONG_PLUGIN; i++) { 130 | br = plugins[i]->get(display, env); 131 | } 132 | } else { 133 | br = plugin->get(display, env); 134 | } 135 | 136 | if (br < 0) { 137 | switch (br) { 138 | case -EINVAL: 139 | sd_bus_error_set_errno(ret_error, -br); 140 | break; 141 | case COMPOSITOR_NO_PROTOCOL: 142 | sd_bus_error_set_const(ret_error, SD_BUS_ERROR_FAILED, "Compositor does not support 'wlr-screencopy-unstable-v1' protocol."); 143 | break; 144 | case WRONG_PLUGIN: 145 | sd_bus_error_set_const(ret_error, SD_BUS_ERROR_FAILED, "No plugin available for your configuration."); 146 | break; 147 | case -EIO: 148 | sd_bus_error_set_errno(ret_error, EIO); 149 | } 150 | return -EACCES; 151 | } 152 | return sd_bus_reply_method_return(m, "d", (double)br / MONITOR_ILL_MAX); 153 | } 154 | 155 | #endif 156 | -------------------------------------------------------------------------------- /src/modules/screen.h: -------------------------------------------------------------------------------- 1 | #include "commons.h" 2 | 3 | #define _SCREEN_PLUGINS \ 4 | X(XORG, 0) \ 5 | X(WL, 1) \ 6 | X(FB, 2) 7 | 8 | enum screen_plugins { 9 | #define X(name, val) name = val, 10 | _SCREEN_PLUGINS 11 | #undef X 12 | SCREEN_NUM 13 | }; 14 | 15 | typedef struct { 16 | const char *name; 17 | int (*get)(const char *id, const char *env); 18 | char obj_path[100]; 19 | } screen_plugin; 20 | 21 | #define SCREEN(name) \ 22 | static int get_frame_brightness(const char *id, const char *env); \ 23 | static void _ctor_ register_gamma_plugin(void) { \ 24 | static screen_plugin self = { name, get_frame_brightness }; \ 25 | screen_register_new(&self); \ 26 | } 27 | 28 | void screen_register_new(screen_plugin *plugin); 29 | int rgb_frame_brightness(const uint8_t *data, const int width, const int height, const int stride); 30 | -------------------------------------------------------------------------------- /src/modules/screen_plugins/fb.c: -------------------------------------------------------------------------------- 1 | #include "screen.h" 2 | #include /* to handle framebuffer ioctls */ 3 | #include 4 | #include "udev.h" 5 | 6 | #define FB_SUBSYSTEM "graphics" 7 | 8 | static int get_framebufferdata(int fd, struct fb_var_screeninfo *fb_varinfo_p, struct fb_fix_screeninfo *fb_fixedinfo); 9 | static int read_framebuffer(int fd, size_t bytes, unsigned char *buf_p, int skip_bytes); 10 | 11 | SCREEN("Fb") 12 | 13 | /* Many thanks to fbgrab utility: https://github.com/GunnarMonell/fbgrab/blob/master/fbgrab.c */ 14 | static int get_frame_brightness(const char *id, const char *env) { 15 | int ret = WRONG_PLUGIN; 16 | 17 | struct udev_device *dev = NULL; 18 | if (id == NULL || id[0] == '\0') { 19 | /* Fetch first matching device from udev */ 20 | get_udev_device(NULL, FB_SUBSYSTEM, NULL, NULL, &dev); 21 | if (!dev) { 22 | return ret; 23 | } 24 | id = udev_device_get_devnode(dev); 25 | } 26 | 27 | unsigned char *buf_p = NULL; 28 | int fd = open(id, O_RDONLY); 29 | if (fd == -1) { 30 | fprintf(stderr, "Error: Couldn't open %s.\n", id); 31 | goto err; 32 | } 33 | 34 | ret = UNSUPPORTED; 35 | struct fb_var_screeninfo fb_varinfo = {0}; 36 | struct fb_fix_screeninfo fb_fixedinfo = {0}; 37 | if (get_framebufferdata(fd, &fb_varinfo, &fb_fixedinfo) != 0) { 38 | goto err; 39 | } 40 | 41 | const int bitdepth = fb_varinfo.bits_per_pixel; 42 | const int width = fb_varinfo.xres; 43 | const int height = fb_varinfo.yres; 44 | const int line_length = fb_fixedinfo.line_length / (bitdepth >> 3); 45 | const int skip_bytes = (fb_varinfo.yoffset * fb_varinfo.xres) * (bitdepth >> 3); 46 | const int stride = fb_fixedinfo.line_length; 47 | 48 | fprintf(stderr, "Fb resolution: %ix%i depth %i.\n", width, height, bitdepth); 49 | 50 | const size_t buf_size = (size_t)height * stride; 51 | 52 | if (line_length < width) { 53 | fprintf(stderr, "Line length cannot be smaller than width"); 54 | ret = -EINVAL; 55 | } else { 56 | buf_p = calloc(buf_size, sizeof(unsigned char)); 57 | if (buf_p == NULL) { 58 | ret = -ENOMEM; 59 | goto err; 60 | } 61 | if (read_framebuffer(fd, buf_size, buf_p, skip_bytes) != 0) { 62 | ret = -EAGAIN; 63 | } else { 64 | ret = rgb_frame_brightness(buf_p, line_length, height, stride); 65 | } 66 | } 67 | 68 | err: 69 | free(buf_p); 70 | if (fd != -1) { 71 | close(fd); 72 | } 73 | if (dev) { 74 | udev_device_unref(dev); 75 | } 76 | return ret; 77 | } 78 | 79 | static int get_framebufferdata(int fd, struct fb_var_screeninfo *fb_varinfo_p, struct fb_fix_screeninfo *fb_fixedinfo) { 80 | if (ioctl(fd, FBIOGET_VSCREENINFO, fb_varinfo_p) != 0) { 81 | return -1; 82 | } 83 | 84 | if (ioctl(fd, FBIOGET_FSCREENINFO, fb_fixedinfo) != 0) { 85 | return -1; 86 | } 87 | return 0; 88 | } 89 | 90 | static int read_framebuffer(int fd, size_t bytes, unsigned char *buf_p, int skip_bytes) { 91 | if (skip_bytes) { 92 | lseek(fd, skip_bytes, SEEK_SET); 93 | } 94 | 95 | if (read(fd, buf_p, bytes) != (ssize_t) bytes) { 96 | fprintf(stderr, "Error: Not enough memory or data\n"); 97 | return -1; 98 | } 99 | return 0; 100 | } 101 | -------------------------------------------------------------------------------- /src/modules/screen_plugins/wl.c: -------------------------------------------------------------------------------- 1 | #include "screen.h" 2 | #include "wl_utils.h" 3 | #include "wlr-screencopy-unstable-v1-client-protocol.h" 4 | 5 | struct cl_display { 6 | struct wl_display *wl_display; 7 | struct wl_registry *wl_registry; 8 | struct wl_shm *shm; 9 | struct wl_list outputs; 10 | struct zwlr_screencopy_manager_v1 *screencopy_manager; 11 | }; 12 | 13 | struct cl_buffer { 14 | struct wl_buffer *wl_buffer; 15 | void *shm_data; 16 | }; 17 | 18 | struct cl_frame { 19 | enum wl_shm_format shm_format; 20 | int32_t width, height, stride, size; 21 | int brightness; 22 | bool copy_done; 23 | bool copy_err; 24 | }; 25 | 26 | struct cl_output { 27 | struct wl_output *wl_output; 28 | struct cl_display *display; 29 | struct cl_buffer *buffer; 30 | struct cl_frame *frame; 31 | struct wl_list link; 32 | struct zwlr_screencopy_frame_v1 *screencopy_frame; 33 | char *name; 34 | }; 35 | 36 | SCREEN("Wl"); 37 | 38 | void noop() {} 39 | 40 | struct wl_buffer *create_shm_buffer(struct wl_shm *shm, 41 | enum wl_shm_format format, int width, 42 | int height, int stride, int size, void **data_out) { 43 | int fd = create_anonymous_file(size, "clightd-screen-wlr"); 44 | if (fd < 0) { 45 | fprintf(stderr, "creating a buffer file for %d B failed: %m\n", size); 46 | return NULL; 47 | } 48 | void *data = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); 49 | if (data == MAP_FAILED) { 50 | fprintf(stderr, "mmap failed: %m\n"); 51 | close(fd); 52 | return NULL; 53 | } 54 | struct wl_shm_pool *pool = wl_shm_create_pool(shm, fd, size); 55 | close(fd); 56 | struct wl_buffer *wl_buffer = 57 | wl_shm_pool_create_buffer(pool, 0, width, height, stride, format); 58 | wl_shm_pool_destroy(pool); 59 | *data_out = data; 60 | return wl_buffer; 61 | } 62 | 63 | static void frame_handle_buffer(void *data, 64 | struct zwlr_screencopy_frame_v1 *frame, 65 | enum wl_shm_format format, uint32_t width, 66 | uint32_t height, uint32_t stride) { 67 | struct cl_output *output = (struct cl_output *)data; 68 | output->frame->shm_format = format; 69 | output->frame->width = width; 70 | output->frame->height = height; 71 | output->frame->stride = stride; 72 | output->frame->size = stride * height; 73 | } 74 | 75 | static void frame_handle_ready(void *data, 76 | struct zwlr_screencopy_frame_v1 *frame, 77 | uint32_t tv_sec_hi, uint32_t tv_sec_low, 78 | uint32_t tv_nsec) { 79 | struct cl_output *output = (struct cl_output *)data; 80 | ++output->frame->copy_done; 81 | } 82 | 83 | static void frame_handle_failed(void *data, 84 | struct zwlr_screencopy_frame_v1 *frame) { 85 | struct cl_output *output = (struct cl_output *)data; 86 | fprintf(stderr, "Failed to copy frame\n"); 87 | ++output->frame->copy_err; 88 | } 89 | 90 | static void frame_handle_buffer_done(void *data, 91 | struct zwlr_screencopy_frame_v1 *frame) { 92 | struct cl_output *output = (struct cl_output *)data; 93 | struct cl_buffer *buffer = calloc(1, sizeof(struct cl_buffer)); 94 | buffer->wl_buffer = create_shm_buffer( 95 | output->display->shm, output->frame->shm_format, output->frame->width, 96 | output->frame->height, output->frame->stride, output->frame->size, 97 | &buffer->shm_data); 98 | if (buffer->wl_buffer == NULL) { 99 | fprintf(stderr, "failed to create buffer\n"); 100 | ++output->frame->copy_err; 101 | } else { 102 | output->buffer = buffer; 103 | zwlr_screencopy_frame_v1_copy(frame, output->buffer->wl_buffer); 104 | } 105 | } 106 | 107 | static const struct zwlr_screencopy_frame_v1_listener 108 | screencopy_frame_listener = { 109 | .buffer = frame_handle_buffer, 110 | .flags = noop, 111 | .ready = frame_handle_ready, 112 | .failed = frame_handle_failed, 113 | .buffer_done = frame_handle_buffer_done, 114 | .linux_dmabuf = noop, 115 | }; 116 | 117 | static void output_handle_name(void *data, struct wl_output *wl_output, 118 | const char *name) { 119 | struct cl_output *output = (struct cl_output *)data; 120 | if (output->name != NULL) 121 | free(output->name); 122 | output->name = strdup(name); 123 | }; 124 | 125 | static const struct wl_output_listener wl_output_listener = { 126 | .name = output_handle_name, 127 | .geometry = noop, 128 | .mode = noop, 129 | .scale = noop, 130 | .description = noop, 131 | .done = noop, 132 | }; 133 | 134 | static void handle_global(void *data, struct wl_registry *registry, 135 | uint32_t name, const char *interface, 136 | uint32_t version) { 137 | struct cl_display *display = (struct cl_display *)data; 138 | struct cl_output *output = calloc(1, sizeof(struct cl_output)); 139 | output->display = display; 140 | if (strcmp(interface, wl_output_interface.name) == 0) { 141 | output->wl_output = 142 | wl_registry_bind(registry, name, &wl_output_interface, 4); 143 | wl_output_add_listener(output->wl_output, &wl_output_listener, output); 144 | output->name = NULL; 145 | wl_list_insert(&display->outputs, &output->link); 146 | } else if (strcmp(interface, wl_shm_interface.name) == 0) { 147 | display->shm = wl_registry_bind(registry, name, &wl_shm_interface, 1); 148 | } else if (strcmp(interface, zwlr_screencopy_manager_v1_interface.name) == 149 | 0) { 150 | display->screencopy_manager = wl_registry_bind( 151 | registry, name, &zwlr_screencopy_manager_v1_interface, 3); 152 | } 153 | } 154 | 155 | static const struct wl_registry_listener registry_listener = { 156 | .global = handle_global, 157 | .global_remove = noop, 158 | }; 159 | 160 | static void wl_cleanup(struct cl_display *display) { 161 | struct cl_output *output; 162 | struct cl_output *tmp_output; 163 | wl_list_for_each_safe(output, tmp_output, &display->outputs, link) { 164 | output->frame->copy_done = false; 165 | output->frame->copy_err = false; 166 | if (output->screencopy_frame != NULL) { 167 | zwlr_screencopy_frame_v1_destroy(output->screencopy_frame); 168 | } 169 | if (output->buffer->shm_data) { 170 | munmap(output->buffer->shm_data, output->frame->size); 171 | } 172 | if (output->buffer->wl_buffer) { 173 | wl_buffer_destroy(output->buffer->wl_buffer); 174 | } 175 | free(output->buffer); 176 | wl_output_destroy(output->wl_output); 177 | wl_list_remove(&output->link); 178 | free(output->name); 179 | free(output); 180 | } 181 | if (display->wl_registry) { 182 | wl_registry_destroy(display->wl_registry); 183 | } 184 | if (display->screencopy_manager) { 185 | zwlr_screencopy_manager_v1_destroy(display->screencopy_manager); 186 | } 187 | } 188 | 189 | static int get_frame_brightness(const char *id, const char *env) { 190 | struct cl_display display = {}; 191 | struct cl_output *output; 192 | int ret = 0; 193 | 194 | display.wl_display = fetch_wl_display(id, env); 195 | 196 | if (display.wl_display == NULL) { 197 | fprintf(stderr, "display error\n"); 198 | ret = WRONG_PLUGIN; 199 | goto err; 200 | } 201 | wl_list_init(&display.outputs); 202 | 203 | display.wl_registry = wl_display_get_registry(display.wl_display); 204 | wl_registry_add_listener(display.wl_registry, ®istry_listener, &display); 205 | wl_display_roundtrip(display.wl_display); 206 | if (display.shm == NULL) { 207 | fprintf(stderr, "Compositor is missing wl_shm\n"); 208 | ret = COMPOSITOR_NO_PROTOCOL; 209 | goto err; 210 | } 211 | if (display.screencopy_manager == NULL) { 212 | fprintf(stderr, "Compositor is screencopy manager\n"); 213 | ret = COMPOSITOR_NO_PROTOCOL; 214 | goto err; 215 | } 216 | if (wl_list_empty(&display.outputs)) { 217 | fprintf(stderr, "No outputs available\n"); 218 | ret = UNSUPPORTED; 219 | goto err; 220 | } 221 | int sum = 0; 222 | wl_list_for_each(output, &display.outputs, link) { 223 | struct cl_frame *frame = calloc(1, sizeof(struct cl_frame)); 224 | output->frame = frame; 225 | output->screencopy_frame = zwlr_screencopy_manager_v1_capture_output( 226 | display.screencopy_manager, 0, output->wl_output); 227 | zwlr_screencopy_frame_v1_add_listener( 228 | output->screencopy_frame, &screencopy_frame_listener, output); 229 | 230 | while (!output->frame->copy_done && !output->frame->copy_err && 231 | wl_display_dispatch(display.wl_display) != -1) { 232 | // This space is intentionally left blank 233 | } 234 | if (output->frame->copy_done) { 235 | output->frame->brightness = rgb_frame_brightness( 236 | output->buffer->shm_data, output->frame->width, 237 | output->frame->height, output->frame->stride); 238 | sum += output->frame->brightness; 239 | } 240 | } 241 | if (sum > 0) { 242 | sum = sum / wl_list_length(&display.outputs); 243 | } 244 | err: 245 | wl_cleanup(&display); 246 | if (ret != 0) { 247 | return ret; 248 | } 249 | // NOTE: dpy is disconnected on program exit to workaround 250 | // gamma protocol limitation that resets gamma as soon as display is disconnected. 251 | // See wl_utils.c 252 | return sum; 253 | } 254 | -------------------------------------------------------------------------------- /src/modules/screen_plugins/xorg.c: -------------------------------------------------------------------------------- 1 | #include "screen.h" 2 | #include "bus_utils.h" 3 | #include "xorg_utils.h" 4 | #include 5 | 6 | SCREEN("Xorg"); 7 | 8 | /* Robbed from calise source code, thanks!! */ 9 | static int get_frame_brightness(const char *id, const char *env) { 10 | Display *dpy = fetch_xorg_display(&id, env); 11 | if (!dpy) { 12 | return WRONG_PLUGIN; 13 | } 14 | 15 | int ret = UNSUPPORTED; 16 | /* window frame size definition: 85% should be ok */ 17 | const float pct = 0.85; 18 | int w = (int) (pct * XDisplayWidth(dpy, 0)); 19 | int h = (int) (pct * XDisplayHeight(dpy, 0)); 20 | int x = (XDisplayWidth(dpy, 0) - w) / 2; 21 | int y = (XDisplayHeight(dpy, 0) - h) / 2; 22 | 23 | Window root_window = XRootWindow(dpy, XDefaultScreen(dpy)); 24 | XImage *ximage = XGetImage(dpy, root_window, x, y, w, h, AllPlanes, ZPixmap); 25 | if (ximage) { 26 | ret = rgb_frame_brightness((const uint8_t *)ximage->data, ximage->width, ximage->height, ximage->bytes_per_line); 27 | XDestroyImage(ximage); 28 | } 29 | XCloseDisplay(dpy); 30 | return ret; 31 | } 32 | -------------------------------------------------------------------------------- /src/modules/sensor.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "bus_utils.h" 4 | 5 | #define SENSOR_MAX_CAPTURES 20 6 | 7 | static bool is_sensor_available(sensor_t *sensor, const char *interface, 8 | void **device); 9 | static void *find_available_sensor(sensor_t *sensor, const char *interface, void **dev); 10 | static void sensor_receive_device(const sensor_t *sensor, void **dev); 11 | static int method_issensoravailable(sd_bus_message *m, void *userdata, sd_bus_error *ret_error); 12 | static int method_capturesensor(sd_bus_message *m, void *userdata, sd_bus_error *ret_error); 13 | 14 | static sensor_t *sensors[SENSOR_NUM]; 15 | static const char object_path[] = "/org/clightd/clightd/Sensor"; 16 | static const char bus_interface[] = "org.clightd.clightd.Sensor"; 17 | static const sd_bus_vtable vtable[] = { 18 | SD_BUS_VTABLE_START(0), 19 | SD_BUS_METHOD("Capture", "sis", "sad", method_capturesensor, SD_BUS_VTABLE_UNPRIVILEGED), 20 | SD_BUS_METHOD("IsAvailable", "s", "sb", method_issensoravailable, SD_BUS_VTABLE_UNPRIVILEGED), 21 | SD_BUS_SIGNAL("Changed", "ss", 0), 22 | SD_BUS_VTABLE_END 23 | }; 24 | 25 | MODULE("SENSOR"); 26 | 27 | static void module_pre_start(void) { 28 | 29 | } 30 | 31 | static bool check(void) { 32 | return true; 33 | } 34 | 35 | static bool evaluate(void) { 36 | return true; 37 | } 38 | 39 | static void init(void) { 40 | int r = sd_bus_add_object_vtable(bus, 41 | NULL, 42 | object_path, 43 | bus_interface, 44 | vtable, 45 | NULL); 46 | for (int i = 0; i < SENSOR_NUM && !r; i++) { 47 | if (sensors[i]) { 48 | int fd = sensors[i]->init_monitor(); 49 | if (fd != -1) { 50 | snprintf(sensors[i]->obj_path, sizeof(sensors[i]->obj_path) - 1, "%s/%s", object_path, sensors[i]->name); 51 | r += sd_bus_add_object_vtable(bus, 52 | NULL, 53 | sensors[i]->obj_path, 54 | bus_interface, 55 | vtable, 56 | sensors[i]); 57 | r += m_register_fd(fd, false, sensors[i]); 58 | } else { 59 | fprintf(stderr, "Sensor '%s' unsupported.\n", sensors[i]->name); 60 | sensors[i] = NULL; 61 | } 62 | } 63 | } 64 | if (r < 0) { 65 | m_log("Failed to issue method call: %s\n", strerror(-r)); 66 | } 67 | } 68 | 69 | static void receive(const msg_t *msg, const void *userdata) { 70 | if (!msg->is_pubsub) { 71 | sensor_t *sensor = (sensor_t *)msg->fd_msg->userptr; 72 | void *dev = NULL; 73 | sensor_receive_device(sensor, &dev); 74 | if (dev) { 75 | const char *node = NULL; 76 | const char *action = NULL; 77 | sensor->fetch_props_dev(dev, &node, &action); 78 | 79 | if (strcmp(action, UDEV_ACTION_ADD) == 0 || 80 | strcmp(action, UDEV_ACTION_RM) == 0) { 81 | sd_bus_emit_signal(bus, sensor->obj_path, bus_interface, "Changed", "ss", node, action); 82 | /* Changed is emitted on Sensor main object too */ 83 | sd_bus_emit_signal(bus, object_path, bus_interface, "Changed", "ss", node, action); 84 | } 85 | sensor->destroy_dev(dev); 86 | } 87 | } 88 | } 89 | 90 | static void destroy(void) { 91 | for (int i = 0; i < SENSOR_NUM; i++) { 92 | if (sensors[i]) { 93 | sensors[i]->destroy_monitor(); 94 | } 95 | } 96 | } 97 | 98 | void sensor_register_new(sensor_t *sensor) { 99 | const char *sensor_names[] = { 100 | #define X(name) #name, 101 | _SENSORS 102 | #undef X 103 | }; 104 | 105 | enum sensors s; 106 | for (s = 0; s < SENSOR_NUM; s++) { 107 | if (strcasestr(sensor_names[s], sensor->name)) { 108 | break; 109 | } 110 | } 111 | 112 | if (s < SENSOR_NUM) { 113 | sensors[s] = sensor; 114 | printf("Registered '%s' sensor plugin.\n", sensor->name); 115 | } else { 116 | printf("Sensor plugin '%s' not recognized. Not registering.\n", sensor->name); 117 | } 118 | } 119 | 120 | static void sensor_receive_device(const sensor_t *sensor, void **dev) { 121 | *dev = NULL; 122 | if (sensor) { 123 | void *d = NULL; 124 | sensor->recv_monitor(&d); 125 | if (d && sensor->validate_dev(d)) { 126 | *dev = d; 127 | } else if (d) { 128 | sensor->destroy_dev(d); 129 | } 130 | } 131 | } 132 | 133 | static bool is_sensor_available(sensor_t *sensor, const char *interface, 134 | void **device) { 135 | sensor->fetch_dev(interface, device); 136 | if (*device) { 137 | if (sensor->validate_dev(*device)) { 138 | return true; 139 | } 140 | sensor->destroy_dev(*device); 141 | *device = NULL; 142 | } 143 | return false; 144 | } 145 | 146 | static void *find_available_sensor(sensor_t *sensor, const char *interface, void **dev) { 147 | *dev = NULL; 148 | 149 | if (!sensor) { 150 | /* No sensor requested; check first available one */ 151 | for (enum sensors s = 0; s < SENSOR_NUM && !*dev; s++) { 152 | if (sensors[s]) { 153 | sensor = sensors[s]; 154 | is_sensor_available(sensor, interface, dev); 155 | } 156 | } 157 | } else { 158 | is_sensor_available(sensor, interface, dev); 159 | } 160 | 161 | if (*dev) { 162 | return sensor; 163 | } 164 | return NULL; 165 | } 166 | 167 | static int method_issensoravailable(sd_bus_message *m, void *userdata, sd_bus_error *ret_error) { 168 | const char *interface = NULL; 169 | int r = sd_bus_message_read(m, "s", &interface); 170 | if (r < 0) { 171 | m_log("Failed to parse parameters: %s\n", strerror(-r)); 172 | return r; 173 | } 174 | 175 | void *dev = NULL; 176 | sensor_t *sensor = find_available_sensor(userdata, interface, &dev); 177 | if (sensor) { 178 | const char *node = NULL; 179 | sensor->fetch_props_dev(dev, &node, NULL); 180 | r = sd_bus_reply_method_return(m, "sb", node, true); 181 | sensor->destroy_dev(dev); 182 | } else { 183 | r = sd_bus_reply_method_return(m, "sb", interface, false); 184 | } 185 | return r; 186 | } 187 | 188 | static int method_capturesensor(sd_bus_message *m, void *userdata, sd_bus_error *ret_error) { 189 | ASSERT_AUTH(); 190 | 191 | const char *interface = NULL; 192 | char *settings = NULL; 193 | const int num_captures; 194 | int r = sd_bus_message_read(m, "sis", &interface, &num_captures, &settings); 195 | if (r < 0) { 196 | m_log("Failed to parse parameters: %s\n", strerror(-r)); 197 | return r; 198 | } 199 | 200 | if (num_captures <= 0 || num_captures > SENSOR_MAX_CAPTURES) { 201 | sd_bus_error_set_const(ret_error, SD_BUS_ERROR_INVALID_ARGS, "Number of captures should be between 1 and 20."); 202 | return -EINVAL; 203 | } 204 | 205 | bus_sender_fill_creds(m); // used by PW plugin 206 | 207 | void *dev = NULL; 208 | sensor_t *sensor = NULL; 209 | double *pct = calloc(num_captures, sizeof(double)); 210 | if (pct) { 211 | sensor = find_available_sensor(userdata, interface, &dev); 212 | 213 | // default value 214 | r = -ENODEV; 215 | if (sensor) { 216 | /* Bus Interface required sensor-specific method */ 217 | r = sensor->capture(dev, pct, num_captures, settings); 218 | } 219 | } else { 220 | r = -ENOMEM; 221 | } 222 | 223 | /* No sensors available */ 224 | if (r < 0) { 225 | sd_bus_error_set_errno(ret_error, -r); 226 | } else if (r == 0) { 227 | sd_bus_error_set_errno(ret_error, EIO); 228 | } else { 229 | /* Reply with array response */ 230 | sd_bus_message *reply = NULL; 231 | sd_bus_message_new_method_return(m, &reply); 232 | 233 | const char *node = NULL; 234 | sensor->fetch_props_dev(dev, &node, NULL); 235 | sd_bus_message_append(reply, "s", node); 236 | sd_bus_message_append_array(reply, 'd', pct, r * sizeof(double)); 237 | r = sd_bus_send(NULL, reply, NULL); 238 | sd_bus_message_unref(reply); 239 | } 240 | 241 | /* Properly free dev if needed */ 242 | if (sensor) { 243 | sensor->destroy_dev(dev); 244 | } 245 | free(pct); 246 | return r; 247 | } 248 | -------------------------------------------------------------------------------- /src/modules/sensor.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Sensor Interface: 3 | * 4 | * Any sensor should expose this interface: 5 | * - a name, that will be used to create sensor's dbus object path. 6 | * - a bunch of callbacks: 7 | * 8 | * -> validate_dev() called when retrieving a device from a monitor to validate retrieved device. 9 | * -> fetch_dev() called by is_sensor_available(), to fetch a device (with requested interface, if set) 10 | * -> fetch_props_dev() to retrieve a device properties, ie: its node and, if (action), an associated action 11 | * -> destroy_dev() to free dev resources 12 | * 13 | * -> init_monitor() to initialize a monitor, returning a valid fd (or < 0 if useless) 14 | * -> recv_monitor() to retrieve a device from an awoken monitor fd 15 | * -> destroy_monitor() to free monitor resources 16 | * 17 | * -> capture() that will actually capture frames from device 18 | * 19 | * To add a new sensor, just insert a new define in _SENSORS; note that sensors are priority-ordered: lower int has higher priority. 20 | * Remeber that sensor's name should contain sensor's define stringified to actually be registered. 21 | **/ 22 | 23 | #include 24 | 25 | #ifndef NDEBUG 26 | #define INFO(fmt, ...) printf(fmt, ##__VA_ARGS__); 27 | #else 28 | #define INFO(fmt, ...) 29 | #endif 30 | 31 | /* Sensor->name must match its enumeration stringified value */ 32 | #define _SENSORS \ 33 | X(ALS) \ 34 | X(YOCTOLIGHT) \ 35 | X(PIPEWIRE) \ 36 | X(CAMERA) \ 37 | X(CUSTOM) 38 | 39 | enum sensors { 40 | #define X(name) name, 41 | _SENSORS 42 | #undef X 43 | SENSOR_NUM 44 | }; 45 | 46 | typedef struct _sensor { 47 | const char *name; // actually sensor name -> note that exposed bus methods should have "name" in them 48 | bool (*validate_dev)(void *dev); 49 | void (*fetch_dev)(const char *subsystem, void **dev); 50 | void (*fetch_props_dev)(void *dev, const char **node, const char **action); 51 | void (*destroy_dev)(void *dev); 52 | int (*init_monitor)(void); 53 | void (*recv_monitor)(void **dev); 54 | void (*destroy_monitor)(void); // return number of frames actually captured, or a -errno style error 55 | int (*capture)(void *userdata, double *pct, const int num_captures, char *settings); 56 | char obj_path[100]; 57 | } sensor_t; 58 | 59 | #define SENSOR(name) \ 60 | static bool validate_dev(void *dev); \ 61 | static void fetch_dev(const char *interface, void **dev); \ 62 | static void fetch_props_dev(void *dev, const char **node, const char **action); \ 63 | static void destroy_dev(void *dev); \ 64 | static int init_monitor(void); \ 65 | static void recv_monitor(void **dev); \ 66 | static void destroy_monitor(void); \ 67 | static int capture(void *dev, double *pct, const int num_captures, char *settings); \ 68 | static void _ctor_ register_sensor(void) { \ 69 | static sensor_t self = { name, validate_dev, fetch_dev, fetch_props_dev, destroy_dev, init_monitor, recv_monitor, destroy_monitor, capture }; \ 70 | sensor_register_new(&self); \ 71 | } 72 | 73 | void sensor_register_new(sensor_t *sensor); 74 | -------------------------------------------------------------------------------- /src/modules/sensors/als.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include "als.h" 6 | 7 | #define ALS_NAME "Als" 8 | #define ALS_SUBSYSTEM "iio" 9 | #define PROCESS_CHANNEL_BITS(bits) val = ((int##bits##_t*)p_dat)[i]; 10 | 11 | // Buffer method has higher prio. See https://gitlab.freedesktop.org/hadess/iio-sensor-proxy/-/merge_requests/352. 12 | typedef enum { ALS_IIO_BUFFER, ALS_IIO_POLL, ALS_IIO_MAX } als_iio_types; 13 | 14 | typedef struct als_device { 15 | struct udev_device *dev; 16 | const char *attr_name[ALS_IIO_MAX]; 17 | double (*capture[ALS_IIO_MAX])(struct als_device *als, double *pct, const int num_captures, int interval); 18 | } als_device_t; 19 | 20 | SENSOR(ALS_NAME); 21 | 22 | /* properties names to be checked. "in_illuminance_input" has higher priority. */ 23 | static const char *ill_poll_names[] = { "in_illuminance_input", "in_illuminance0_input", "in_illuminance_raw", "in_intensity_clear_raw" }; 24 | static const char *ill_buff_names[] = { "scan_elements/in_illuminance_en", "scan_elements/in_intensity_both_en" }; 25 | static const char *scale_names[] = { "in_illuminance_scale", "in_intensity_scale" }; 26 | 27 | static struct udev_monitor *mon; 28 | 29 | static double iio_poll_capture(struct als_device *als, double *pct, const int num_captures, int interval) { 30 | const char *syspath = udev_device_get_syspath(als->dev); 31 | 32 | INFO("[IIO-POLL] Start capture: '%s' syspath.\n", syspath); 33 | 34 | // Load scale value 35 | const char *val = NULL; 36 | double scale = 1.0; // defaults to 1.0 37 | for (int i = 0; i < SIZE(scale_names) && !val; i++) { 38 | val = udev_device_get_sysattr_value(als->dev, scale_names[i]); 39 | if (val) { 40 | scale = atof(val); 41 | } 42 | } 43 | INFO("[IIO-POLL] Loaded scale: %f.\n", scale); 44 | 45 | 46 | int ctr = 0; 47 | for (int i = 0; i < num_captures; i++) { 48 | struct udev_device *non_cached_dev = udev_device_new_from_syspath(udev, syspath); 49 | val = udev_device_get_sysattr_value(non_cached_dev, als->attr_name[ALS_IIO_POLL]); 50 | INFO("[IIO-POLL] Read: %s.\n", val); 51 | if (val) { 52 | double illuminance = atof(val) * scale; 53 | ctr++; 54 | pct[i] = compute_value(illuminance); 55 | INFO("[IIO-POLL] Pct[%d] = %lf\n", i, pct[i]); 56 | } 57 | udev_device_unref(non_cached_dev); 58 | usleep(interval * 1000); 59 | } 60 | return ctr; 61 | } 62 | 63 | static double iio_buffer_capture(struct als_device *als, double *pct, const int num_captures, int interval) { 64 | int ctr = 0; 65 | 66 | const char *sysname = udev_device_get_sysname(als->dev); 67 | INFO("[IIO-BUF] Start capture: '%s' sysname.\n", sysname); 68 | 69 | 70 | /* Getting local iio device context */ 71 | struct iio_context *local_ctx = iio_create_local_context(); 72 | if (!local_ctx) { 73 | fprintf(stderr, "Failed to create local iio ctx.\n"); 74 | return ctr; 75 | } 76 | 77 | const struct iio_device *dev = iio_context_find_device(local_ctx, sysname); 78 | if (!dev) { 79 | fprintf(stderr, "Couldn't find device '%s': %m\n", sysname); 80 | return ctr; 81 | } 82 | 83 | INFO("[IIO-BUF] Found device.\n"); 84 | 85 | // Compute channel name from attribute ("illuminance" or "intensity_both") 86 | char *name = strrchr(als->attr_name[ALS_IIO_BUFFER], '/') + 1; // "scan_elements/in_illuminance_en" -> "in_illuminance_en" 87 | name = strchr(name, '_') + 1; // "in_illuminance_en" -> "illuminance_en" 88 | char *ptr = strrchr(name, '_'); 89 | char channel_name[64]; 90 | snprintf(channel_name, strlen(name) - strlen(ptr) + 1, "%s", name); // "illuminance_en" -> "illuminance" 91 | 92 | INFO("[IIO-BUF] Channel name: '%s'.\n", channel_name); 93 | 94 | struct iio_channel *ch = iio_device_find_channel(dev, channel_name, false); 95 | if (!ch) { 96 | fprintf(stderr, "Failed to fetch '%s' channel: %m\n", channel_name); 97 | return ctr; 98 | } 99 | 100 | if (iio_channel_is_output(ch)) { 101 | fprintf(stderr, "Wrong output channel selected.\n"); 102 | return ctr; 103 | } 104 | if (!iio_channel_is_scan_element(ch)) { 105 | fprintf(stderr, "Channel is not a scan element.\n"); 106 | return ctr; 107 | } 108 | 109 | iio_channel_enable(ch); 110 | if (!iio_channel_is_enabled(ch)) { 111 | fprintf(stderr, "Failed to enable channel '%s'!\n", channel_name); 112 | return ctr; 113 | } 114 | 115 | INFO("[IIO-BUF] Creating buffer.\n"); 116 | struct iio_buffer *rxbuf = iio_device_create_buffer(dev, 1, false); 117 | if (!rxbuf) { 118 | fprintf(stderr, "Failed to allocated buffer: %m\n"); 119 | return ctr; 120 | } 121 | 122 | // Load scale 123 | double scale = 1.0; // default to 1.0 124 | const struct iio_data_format *fmt = iio_channel_get_data_format(ch); 125 | if (!fmt) { 126 | fprintf(stderr, "Failed to fetch channel format.\n"); 127 | return ctr; 128 | } 129 | if (fmt->with_scale) { 130 | scale = fmt->scale; 131 | } 132 | 133 | INFO("[IIO-BUF] Data fmt: bits: %d | signed: %d | len: %d | rep: %d | scale: %f | has_scale: %d | shift: %d.\n", 134 | fmt->bits, fmt->is_signed, fmt->length, fmt->repeat, fmt->scale, fmt->with_scale, fmt->shift); 135 | 136 | const size_t read_size = fmt->bits / 8; 137 | for (int i = 0; i < num_captures; i++) { 138 | int ret = iio_buffer_refill(rxbuf); 139 | INFO("[IIO-BUF] Refill ret: %d/%ld\n", ret, read_size); 140 | if (ret == read_size) { 141 | int64_t val = 0; 142 | if (ret == read_size) { 143 | iio_channel_read(ch, rxbuf, &val, read_size); 144 | INFO("[IIO-BUF] Read %ld\n", val); 145 | double illuminance = (double)val * scale; 146 | ctr++; 147 | pct[i] = compute_value(illuminance); 148 | INFO("[IIO-BUF] Pct[%d] = %lf\n", i, pct[i]); 149 | } 150 | } 151 | usleep(interval * 1000); 152 | } 153 | iio_buffer_destroy(rxbuf); 154 | iio_context_destroy(local_ctx); 155 | return ctr; 156 | } 157 | 158 | static bool validate_dev(void *dev) { 159 | als_device_t *als = (als_device_t *)dev; 160 | 161 | bool valid = false; 162 | /* Check if device exposes any of the requested sysattrs */ 163 | for (int i = 0; i < SIZE(ill_buff_names); i++) { 164 | if (udev_device_get_sysattr_value(als->dev, ill_buff_names[i])) { 165 | als->attr_name[ALS_IIO_BUFFER] = ill_buff_names[i]; 166 | als->capture[ALS_IIO_BUFFER] = iio_buffer_capture; 167 | valid = true; 168 | INFO("Buffer available, using '%s' sysattr\n", ill_buff_names[i]); 169 | break; 170 | } 171 | } 172 | 173 | for (int i = 0; i < SIZE(ill_poll_names); i++) { 174 | if (udev_device_get_sysattr_value(als->dev, ill_poll_names[i])) { 175 | als->attr_name[ALS_IIO_POLL] = ill_poll_names[i]; 176 | als->capture[ALS_IIO_POLL] = iio_poll_capture; 177 | valid = true; 178 | INFO("Poll available, using '%s' sysattr\n", ill_poll_names[i]); 179 | break; 180 | } 181 | } 182 | return valid; 183 | } 184 | 185 | static void fetch_dev(const char *interface, void **dev) { 186 | struct udev_device *d = NULL; 187 | /* Check if any device exposes requested sysattr */ 188 | for (int i = 0; i < SIZE(ill_buff_names) && !d; i++) { 189 | /* Only check existence for needed sysattr */ 190 | const udev_match match = { .sysattr_key = ill_buff_names[i] }; 191 | get_udev_device(interface, ALS_SUBSYSTEM, &match, NULL, &d); 192 | } 193 | 194 | if (!d) { 195 | for (int i = 0; i < SIZE(ill_poll_names) && !d; i++) { 196 | /* Only check existence for needed sysattr */ 197 | const udev_match match = { .sysattr_key = ill_poll_names[i] }; 198 | get_udev_device(interface, ALS_SUBSYSTEM, &match, NULL, &d); 199 | } 200 | } 201 | 202 | if (d) { 203 | als_device_t *als = calloc(1, sizeof(als_device_t)); 204 | als->dev = d; 205 | *dev = als; 206 | } 207 | } 208 | 209 | static void fetch_props_dev(void *dev, const char **node, const char **action) { 210 | als_device_t *als = (als_device_t *)dev; 211 | if (node) { 212 | *node = udev_device_get_devnode(als->dev); 213 | } 214 | if (action) { 215 | *action = udev_device_get_action(als->dev); 216 | } 217 | } 218 | 219 | static void destroy_dev(void *dev) { 220 | als_device_t *als = (als_device_t *)dev; 221 | udev_device_unref(als->dev); 222 | free(als); 223 | } 224 | 225 | static int init_monitor(void) { 226 | return init_udev_monitor(ALS_SUBSYSTEM, &mon); 227 | } 228 | 229 | static void recv_monitor(void **dev) { 230 | struct udev_device *d = udev_monitor_receive_device(mon); 231 | als_device_t *als = calloc(1, sizeof(als_device_t)); 232 | als->dev = d; 233 | *dev = als; 234 | } 235 | 236 | static void destroy_monitor(void) { 237 | udev_monitor_unref(mon); 238 | } 239 | 240 | static int capture(void *dev, double *pct, const int num_captures, char *settings) { 241 | int interval; 242 | parse_settings(settings, &interval); 243 | 244 | als_device_t *als = (als_device_t *)dev; 245 | int ret = 0; 246 | // Try buffer then poll methods, if both are available 247 | for (int i = 0; i < ALS_IIO_MAX && ret == 0; i++) { 248 | if (als->capture[i]) { 249 | ret = als->capture[i](als, pct, num_captures, interval); 250 | } 251 | } 252 | return ret; 253 | } 254 | -------------------------------------------------------------------------------- /src/modules/sensors/als.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | /* For more on interpreting lux values: https://docs.microsoft.com/en-us/windows/win32/sensorsapi/understanding-and-interpreting-lux-values */ 6 | #ifndef ALS_INTERVAL 7 | #define ALS_INTERVAL 20 // ms, customizable. Yocto sensor has different default interval 8 | #endif 9 | #define ALS_ILL_MAX 100000 // Direct sunlight 10 | #define ALS_ILL_MIN 1 // Pitch black 11 | 12 | static inline double compute_value(double illuminance) { 13 | if (illuminance > ALS_ILL_MAX) { 14 | illuminance = ALS_ILL_MAX; 15 | } else if (illuminance < ALS_ILL_MIN) { 16 | illuminance = ALS_ILL_MIN; 17 | } 18 | return log10(illuminance) / log10(ALS_ILL_MAX); 19 | } 20 | 21 | static inline void parse_settings(char *settings, int *interval) { 22 | const char opts[] = { 'i' }; 23 | int *vals[] = { interval }; 24 | 25 | /* Default value */ 26 | *interval = ALS_INTERVAL; 27 | 28 | if (settings && settings[0] != '\0') { 29 | char *token; 30 | char *rest = settings; 31 | 32 | while ((token = strtok_r(rest, ",", &rest))) { 33 | char opt; 34 | int val; 35 | 36 | if (sscanf(token, "%c=%d", &opt, &val) == 2) { 37 | bool found = false; 38 | for (int i = 0; i < SIZE(opts) && !found; i++) { 39 | if (opts[i] == opt) { 40 | *(vals[i]) = val; 41 | found = true; 42 | } 43 | } 44 | 45 | if (!found) { 46 | fprintf(stderr, "Option %c not found.\n", opt); 47 | } 48 | } else { 49 | fprintf(stderr, "Expected a=b format.\n"); 50 | } 51 | } 52 | } 53 | 54 | /* Sanity checks */ 55 | if (*interval < 0 || *interval > 1000) { 56 | fprintf(stderr, "Wrong interval value. Resetting default.\n"); 57 | *interval = ALS_INTERVAL; 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/modules/sensors/camera.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #define CAMERA_NAME "Camera" 9 | #define CAMERA_SUBSYSTEM "video4linux" 10 | #define CAMERA_CAPTURE_PROP_NAME "ID_V4L_CAPABILITIES" 11 | #define CAMERA_CAPTURE_PROP_VAL ":capture:" 12 | 13 | #define CAMERA_ILL_MAX 255 14 | #define HISTOGRAM_STEPS 40 15 | 16 | #define SET_V4L2(op, val) \ 17 | do { \ 18 | struct v4l2_control *data = set_camera_setting(priv, op, val, #op, true); \ 19 | if (data) { \ 20 | map_put(stored_values, #op, data); \ 21 | } \ 22 | } while(false) 23 | 24 | typedef struct { 25 | int row_start; 26 | int row_end; 27 | int col_start; 28 | int col_end; 29 | } rect_info_t; 30 | 31 | struct histogram { 32 | double count; 33 | double sum; 34 | }; 35 | 36 | typedef enum { X_AXIS, Y_AXIS, MAX_AXIS } crop_axis; 37 | 38 | typedef struct { 39 | bool enabled; 40 | double area_pct[2]; // start - end 41 | } crop_info_t; 42 | 43 | static struct v4l2_control *set_camera_setting(void *priv, uint32_t op, float val, const char *op_name, bool store); 44 | 45 | static map_t *stored_values; 46 | static crop_info_t crop[MAX_AXIS]; 47 | static bool camera_set; 48 | 49 | static double get_frame_brightness(uint8_t *img_data, rect_info_t *full, bool is_yuv) { 50 | double brightness = 0.0; 51 | double min = CAMERA_ILL_MAX; 52 | double max = 0.0; 53 | 54 | /* 55 | * If greyscale (rgb is converted to grey) -> increment by 1. 56 | * If YUYV -> increment by 2: we only want Y! 57 | */ 58 | const int inc = 1 + is_yuv; 59 | 60 | rect_info_t crop_rect = *full; 61 | if (crop[X_AXIS].enabled) { 62 | crop_rect.col_start = crop[X_AXIS].area_pct[0] * full->col_end; 63 | crop_rect.col_end = crop[X_AXIS].area_pct[1] * full->col_end; 64 | } 65 | if (crop[Y_AXIS].enabled) { 66 | crop_rect.row_start = crop[Y_AXIS].area_pct[0] * full->row_end; 67 | crop_rect.row_end = crop[Y_AXIS].area_pct[1] * full->row_end; 68 | } 69 | INFO("Rect: rows[%d-%d], cols[%d-%d]\n", crop_rect.row_start, crop_rect.row_end, 70 | crop_rect.col_start, crop_rect.col_end); 71 | 72 | /* Find minimum and maximum brightness */ 73 | int total = 0; // compute total used pixels 74 | for (int row = crop_rect.row_start; row < crop_rect.row_end; row++) { 75 | for (int col = crop_rect.col_start * inc; col < crop_rect.col_end * inc; col += inc) { 76 | const int idx = (row * full->col_end * inc) + col; 77 | if (img_data[idx] < min) { 78 | min = img_data[idx]; 79 | } 80 | if (img_data[idx] > max) { 81 | max = img_data[idx]; 82 | } 83 | total++; 84 | } 85 | } 86 | INFO("Total computed pixels: %d\n", total); 87 | 88 | /* Ok, we should never get in here */ 89 | if (max == 0.0) { 90 | return brightness; 91 | } 92 | 93 | /* Calculate histogram */ 94 | struct histogram hist[HISTOGRAM_STEPS] = {0}; 95 | const double step_size = (max - min) / HISTOGRAM_STEPS; 96 | for (int row = crop_rect.row_start; row < crop_rect.row_end; row++) { 97 | for (int col = crop_rect.col_start * inc; col < crop_rect.col_end * inc; col += inc) { 98 | const int idx = (row * full->col_end * inc) + col; 99 | int bucket = (img_data[idx] - min) / step_size; 100 | if (bucket >= 0 && bucket < HISTOGRAM_STEPS) { 101 | hist[bucket].sum += img_data[idx]; 102 | hist[bucket].count++; 103 | } 104 | } 105 | } 106 | 107 | /* Find (approximate) quartiles for histogram steps */ 108 | const double quartile_size = (double)total / 4; 109 | double quartiles[3] = {0}; 110 | int j = 0; 111 | for (int i = 0; i < HISTOGRAM_STEPS && j < 3; i++) { 112 | quartiles[j] += hist[i].count; 113 | if (quartiles[j] >= quartile_size) { 114 | quartiles[j] = (quartile_size / quartiles[j]) + i; 115 | j++; 116 | } 117 | } 118 | 119 | /* 120 | * Results may be clustered in a single estimated quartile, 121 | * in which case consider full range. 122 | */ 123 | int min_bucket = 0; 124 | int max_bucket = HISTOGRAM_STEPS - 1; 125 | if (quartiles[2] > quartiles[0]) { 126 | /* Trim outlier buckets via interquartile range */ 127 | const double iqr = (quartiles[2] - quartiles[0]) * 1.5; 128 | min_bucket = quartiles[0] - iqr; 129 | max_bucket = quartiles[2] + iqr; 130 | if (min_bucket < 0) { 131 | min_bucket = 0; 132 | } 133 | if (max_bucket > HISTOGRAM_STEPS - 1) { 134 | max_bucket = HISTOGRAM_STEPS - 1; 135 | } 136 | } 137 | 138 | /* 139 | * Find the highest brightness bucket with 140 | * a significant sample count 141 | * and return the average brightness for it. 142 | */ 143 | for (int i = max_bucket; i >= min_bucket; i--) { 144 | if (hist[i].count > step_size) { 145 | brightness = hist[i].sum / hist[i].count; 146 | break; 147 | } 148 | } 149 | return (double)brightness / CAMERA_ILL_MAX; 150 | } 151 | 152 | static void set_camera_settings_def(void *priv) { 153 | SET_V4L2(V4L2_CID_SCENE_MODE, -1); 154 | SET_V4L2(V4L2_CID_AUTO_WHITE_BALANCE, -1); 155 | SET_V4L2(V4L2_CID_EXPOSURE_AUTO, -1); 156 | SET_V4L2(V4L2_CID_AUTOGAIN, -1); 157 | SET_V4L2(V4L2_CID_ISO_SENSITIVITY_AUTO, -1); 158 | SET_V4L2(V4L2_CID_BACKLIGHT_COMPENSATION, -1); 159 | SET_V4L2(V4L2_CID_AUTOBRIGHTNESS, -1); 160 | 161 | SET_V4L2(V4L2_CID_WHITE_BALANCE_TEMPERATURE, -1); 162 | SET_V4L2(V4L2_CID_EXPOSURE_ABSOLUTE, -1); 163 | SET_V4L2(V4L2_CID_IRIS_ABSOLUTE, -1); 164 | SET_V4L2(V4L2_CID_GAIN, -1); 165 | SET_V4L2(V4L2_CID_ISO_SENSITIVITY, -1); 166 | SET_V4L2(V4L2_CID_BRIGHTNESS, -1); 167 | } 168 | 169 | static void set_camera_settings(void *priv, char *settings) { 170 | // Already called; expect a restore_camera_settings() call 171 | if (camera_set) { 172 | return; 173 | } 174 | 175 | /* Default values: until end of axis */ 176 | crop[X_AXIS].area_pct[1] = 1.0; 177 | crop[Y_AXIS].area_pct[1] = 1.0; 178 | 179 | stored_values = map_new(true, free); 180 | 181 | /* Set default values */ 182 | set_camera_settings_def(priv); 183 | if (settings && settings[0] != '\0') { 184 | char *token; 185 | char *rest = settings; 186 | 187 | while ((token = strtok_r(rest, ",", &rest))) { 188 | uint32_t v4l2_op; 189 | int32_t v4l2_val; 190 | char axis; 191 | double area_pct[2]; 192 | 193 | if (sscanf(token, "%u=%d", &v4l2_op, &v4l2_val) == 2) { 194 | SET_V4L2(v4l2_op, v4l2_val); 195 | } else if (sscanf(token, "%c=%lf-%lf", &axis, &area_pct[0], &area_pct[1]) == 3) { 196 | int8_t crop_idx = -1; 197 | if (area_pct[0] >= area_pct[1]) { 198 | fprintf(stderr, "Start should be lesser than end: %lf-%lf\n", area_pct[0], area_pct[1]); 199 | } else { 200 | switch (axis) { 201 | case 'x': 202 | crop_idx = X_AXIS; 203 | break; 204 | case 'y': 205 | crop_idx = Y_AXIS; 206 | break; 207 | default: 208 | fprintf(stderr, "wrong axis specified: %c; 'x' or 'y' supported.\n", axis); 209 | break; 210 | } 211 | } 212 | if (crop_idx != -1 && !crop[crop_idx].enabled) { 213 | crop[crop_idx].enabled = true; 214 | crop[crop_idx].area_pct[0] = area_pct[0]; 215 | crop[crop_idx].area_pct[1] = area_pct[1]; 216 | } 217 | } else { 218 | fprintf(stderr, "Expected a=b format in '%s' token.\n", token); 219 | } 220 | } 221 | } 222 | camera_set = true; 223 | } 224 | 225 | static void restore_camera_settings(void *priv) { 226 | for (map_itr_t *itr = map_itr_new(stored_values); itr; itr = map_itr_next(itr)) { 227 | struct v4l2_control *old_ctrl = map_itr_get_data(itr); 228 | const char *ctrl_name = map_itr_get_key(itr); 229 | INFO("Restoring setting for '%s'\n", ctrl_name) 230 | set_camera_setting(priv, old_ctrl->id, old_ctrl->value, ctrl_name, false); 231 | } 232 | 233 | map_free(stored_values); 234 | stored_values = NULL; 235 | 236 | memset(crop, 0, sizeof(crop)); 237 | camera_set = false; 238 | } 239 | -------------------------------------------------------------------------------- /src/modules/sensors/custom.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #define CUSTOM_NAME "Custom" 7 | #define CUSTOM_ILL_MAX 4096 8 | #define CUSTOM_ILL_MIN 0 9 | #define CUSTOM_INTERVAL 20 // ms 10 | #define CUSTOM_FLD "/etc/clightd/sensors.d/" 11 | 12 | #define BUF_LEN (sizeof(struct inotify_event) + NAME_MAX + 1) 13 | 14 | SENSOR(CUSTOM_NAME); 15 | 16 | static int inot_fd, inot_wd; 17 | 18 | static bool validate_dev(void *dev) { 19 | return true; 20 | } 21 | 22 | static void fetch_dev(const char *interface, void **dev) { 23 | *dev = NULL; 24 | 25 | if (interface && interface[0] != '\0') { 26 | char fullpath[PATH_MAX + 1] = {0}; 27 | 28 | /* User gave us a relative path! prepend CUSTOM_FLD as prefix! */ 29 | if (interface[0] != '/') { 30 | snprintf(fullpath, PATH_MAX, CUSTOM_FLD"%s", interface); 31 | interface = fullpath; 32 | } 33 | 34 | /* If requested script exists */ 35 | if (access(interface, F_OK) == 0) { 36 | /* Use user provided script */ 37 | *dev = strdup(interface); 38 | } 39 | } else { 40 | /* Cycle files inside sensors.d folder */ 41 | glob_t gl = {0}; 42 | if (glob(CUSTOM_FLD"*", GLOB_ERR, NULL, &gl) == 0) { 43 | for (int i = 0; i < gl.gl_pathc; i++) { 44 | if (access(gl.gl_pathv[i], F_OK) == 0) { 45 | *dev = strdup(gl.gl_pathv[i]); // first file it finds 46 | break; 47 | } 48 | } 49 | globfree(&gl); 50 | } 51 | } 52 | } 53 | 54 | static void fetch_props_dev(void *dev, const char **node, const char **action) { 55 | if (node) { 56 | *node = dev; 57 | } 58 | 59 | if (action) { 60 | static const char *actions[] = { UDEV_ACTION_RM, UDEV_ACTION_ADD }; 61 | 62 | int idx = access(dev, F_OK) == 0; 63 | *action = actions[idx]; 64 | } 65 | } 66 | 67 | static void destroy_dev(void *dev) { 68 | free(dev); 69 | } 70 | 71 | static int init_monitor(void) { 72 | inot_fd = inotify_init(); 73 | inot_wd = inotify_add_watch(inot_fd, CUSTOM_FLD, IN_CREATE | IN_DELETE | IN_MOVE); 74 | return inot_fd; 75 | } 76 | 77 | static void recv_monitor(void **dev) { 78 | *dev = NULL; 79 | char buffer[BUF_LEN]; 80 | 81 | size_t len = read(inot_fd, buffer, BUF_LEN); 82 | if (len > 0) { 83 | struct inotify_event *event = (struct inotify_event *)buffer; 84 | if (event->len) { 85 | /* prepend /etc/clightd/sensors.d/ */ 86 | char fullpath[PATH_MAX + 1] = {0}; 87 | snprintf(fullpath, PATH_MAX, CUSTOM_FLD"%s", event->name); 88 | *dev = strdup(fullpath); 89 | } 90 | } 91 | } 92 | 93 | static void destroy_monitor(void) { 94 | inotify_rm_watch(inot_fd, inot_wd); 95 | close(inot_fd); 96 | } 97 | 98 | static void parse_settings(char *settings, int *min, int *max, int *interval) { 99 | const char opts[] = { 'i', 'm', 'M' }; 100 | int *vals[] = { interval, min, max }; 101 | 102 | /* Default values */ 103 | *min = CUSTOM_ILL_MIN; 104 | *max = CUSTOM_ILL_MAX; 105 | *interval = CUSTOM_INTERVAL; 106 | 107 | if (settings && settings[0] != '\0') { 108 | char *token; 109 | char *rest = settings; 110 | 111 | while ((token = strtok_r(rest, ",", &rest))) { 112 | char opt; 113 | int val; 114 | 115 | if (sscanf(token, "%c=%d", &opt, &val) == 2) { 116 | bool found = false; 117 | for (int i = 0; i < SIZE(opts) && !found; i++) { 118 | if (opts[i] == opt) { 119 | *(vals[i]) = val; 120 | found = true; 121 | } 122 | } 123 | 124 | if (!found) { 125 | fprintf(stderr, "Option %c not found.\n", opt); 126 | } 127 | } else { 128 | fprintf(stderr, "Expected a=b format.\n"); 129 | } 130 | } 131 | } 132 | 133 | /* Sanity checks */ 134 | if (*interval < 0 || *interval > 1000) { 135 | fprintf(stderr, "Wrong interval value. Resetting default.\n"); 136 | *interval = CUSTOM_INTERVAL; 137 | } 138 | if (*min < 0) { 139 | fprintf(stderr, "Wrong min value. Resetting default.\n"); 140 | *min = CUSTOM_ILL_MAX; 141 | } 142 | if (*max < 0) { 143 | fprintf(stderr, "Wrong max value. Resetting default.\n"); 144 | *max = CUSTOM_ILL_MAX; 145 | } 146 | if (*min > *max) { 147 | fprintf(stderr, "Wrong min/max values. Resetting defaults.\n"); 148 | *min = CUSTOM_ILL_MAX; 149 | *max = CUSTOM_ILL_MAX; 150 | } 151 | } 152 | 153 | static int capture(void *dev, double *pct, const int num_captures, char *settings) { 154 | int min, max, interval; 155 | parse_settings(settings, &min, &max, &interval); 156 | 157 | int ctr = 0; 158 | FILE *fdev = fopen(dev, "r"); 159 | if (fdev) { 160 | for (int i = 0; i < num_captures; i++) { 161 | int ill = -1; 162 | if (fscanf(fdev, "%d", &ill) == 1) { 163 | if (ill > max) { 164 | ill = max; 165 | } else if (ill < min) { 166 | ill = min; 167 | } 168 | pct[ctr++] = (double)ill / max; 169 | rewind(fdev); 170 | } 171 | usleep(interval * 1000); 172 | } 173 | } 174 | fclose(fdev); 175 | return ctr; 176 | } 177 | -------------------------------------------------------------------------------- /src/modules/sensors/yoctolight.c: -------------------------------------------------------------------------------- 1 | #ifdef YOCTOLIGHT_PRESENT 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | /* This overrides define in als.h. Define it before include. */ 8 | #define ALS_INTERVAL 500 // ms 9 | #include "als.h" 10 | 11 | #define YOCTO_ERR(fmt, ...) fprintf(stderr, fmt, ##__VA_ARGS__); return -1; 12 | 13 | #define YOCTO_NAME "YoctoLight" 14 | #define YOCTO_SUBSYSTEM "usb" 15 | #define YOCTO_PROPERTY "idVendor" 16 | #define YOCTO_VENDORID "24e0" 17 | #define YOCTO_PKT_SIZE 64 18 | #define YOCTO_SERIAL_LEN 20 19 | #define YOCTO_IFACE 0 20 | 21 | #define YOCTO_MAX_TRIES 20 22 | 23 | #define YOCTO_CONF_RESET 0 24 | #define YOCTO_CONF_START 1 25 | 26 | #define YOCTO_PKT_STREAM 0 27 | #define YOCTO_PKT_CONF 1 28 | 29 | #define YOCTO_STREAM_NOTICE 3 30 | #define YOCTO_STREAM_NOTICE_V2 7 31 | 32 | #define YOCTO_NOTIFY_PKT_STREAMREADY 6 33 | #define YOCTO_NOTIFY_PKT_STREAMREADY_2 49 // sometimes 49 is reported as streamready value too! 34 | 35 | #define TO_SAFE_U16(safe,unsafe) {(safe).low = (unsafe)&0xff; (safe).high=(unsafe)>>8;} 36 | #define FROM_SAFE_U16(safe,unsafe) {(unsafe) = (safe).low |((uint16_t)((safe).high)<<8);} 37 | 38 | enum usb_state { EMPTY, ATTACHED, CLAIMED }; 39 | 40 | typedef struct { 41 | uint8_t low; 42 | uint8_t high; 43 | } SAFE_U16; 44 | 45 | typedef struct{ 46 | char serial[YOCTO_SERIAL_LEN]; 47 | uint8_t type; 48 | } Notification_header; 49 | 50 | typedef union { 51 | uint8_t firstByte; 52 | Notification_header head; 53 | } USB_Notify_Pkt; 54 | 55 | #ifndef CPU_BIG_ENDIAN 56 | typedef struct { 57 | uint8_t pktno : 3; 58 | uint8_t stream : 5; 59 | uint8_t pkt : 2; 60 | uint8_t size : 6; 61 | } YSTREAM_Head; 62 | #else 63 | typedef struct { 64 | uint8_t stream : 5; 65 | uint8_t pktno : 3; 66 | uint8_t size : 6; 67 | uint8_t pkt : 2; 68 | } YSTREAM_Head; 69 | #endif 70 | 71 | typedef union{ 72 | struct{ 73 | SAFE_U16 api; 74 | uint8_t ok; 75 | uint8_t ifaceno; 76 | uint8_t nbifaces; 77 | } reset; 78 | struct{ 79 | uint8_t nbifaces; 80 | uint8_t ack_delay; 81 | } start; 82 | } USB_Conf_Pkt; 83 | 84 | typedef union { 85 | uint8_t data[YOCTO_PKT_SIZE]; 86 | struct { 87 | YSTREAM_Head head; 88 | USB_Conf_Pkt conf; 89 | } confpkt; 90 | } USB_Packet; 91 | 92 | typedef struct { 93 | libusb_device_handle *hdl; 94 | struct libusb_config_descriptor *config; 95 | uint8_t rdendp; 96 | uint8_t wrendp; 97 | int interval; 98 | enum usb_state st; 99 | } ylight_state; 100 | 101 | static bool get_dev_config(libusb_device *dev); 102 | static int init_usb_device(void); 103 | static int start_usb_device(USB_Packet *rpkt); 104 | static int destroy_usb_device(void); 105 | 106 | static struct udev_monitor *mon; 107 | static ylight_state state; 108 | 109 | SENSOR(YOCTO_NAME); 110 | 111 | static void _ctor_ init_libusb(void) { 112 | libusb_init(NULL); 113 | } 114 | 115 | static void _dtor_ destroy_libusb(void) { 116 | libusb_exit(NULL); 117 | } 118 | 119 | static bool get_dev_config(libusb_device *dev) { 120 | int ret = libusb_get_active_config_descriptor(dev, &state.config); 121 | if (ret == LIBUSB_ERROR_NOT_FOUND) { 122 | ret = libusb_get_config_descriptor(dev, 0, &state.config); 123 | } 124 | return ret == 0; 125 | } 126 | 127 | static bool validate_dev(void *dev) { 128 | const char *vendor_id = udev_device_get_sysattr_value(dev, YOCTO_PROPERTY); 129 | if (vendor_id && !strcmp(vendor_id, YOCTO_VENDORID)) { 130 | const char *product_id = udev_device_get_sysattr_value(dev, "idProduct"); 131 | if (product_id) { 132 | int vendor = (int)strtol(vendor_id, NULL, 16); // hex 133 | int product = (int)strtol(product_id, NULL, 16); // hex 134 | state.hdl = libusb_open_device_with_vid_pid(NULL, vendor, product); 135 | if (state.hdl) { 136 | return get_dev_config(libusb_get_device(state.hdl)); 137 | } 138 | } 139 | /* Always return true if action is "remove", ie: when called by udev monitor */ 140 | const char *action = udev_device_get_action(dev); 141 | return action && !strcmp(action, UDEV_ACTION_RM); 142 | } 143 | return false; 144 | } 145 | 146 | static void fetch_dev(const char *interface, void **dev) { 147 | const udev_match match = { .sysattr_key = YOCTO_PROPERTY, .sysattr_val = YOCTO_VENDORID }; 148 | get_udev_device(interface, YOCTO_SUBSYSTEM, &match, NULL, (struct udev_device **)dev); 149 | } 150 | 151 | static void fetch_props_dev(void *dev, const char **node, const char **action) { 152 | if (node) { 153 | *node = udev_device_get_devnode(dev); 154 | } 155 | if (action) { 156 | *action = udev_device_get_action(dev); 157 | } 158 | } 159 | 160 | static void destroy_dev(void *dev) { 161 | udev_device_unref(dev); 162 | if (state.hdl) { 163 | libusb_close(state.hdl); 164 | state.hdl = NULL; 165 | } 166 | if (state.config) { 167 | libusb_free_config_descriptor(state.config); 168 | state.config = NULL; 169 | } 170 | } 171 | 172 | static int init_monitor(void) { 173 | return init_udev_monitor(YOCTO_SUBSYSTEM, &mon); 174 | } 175 | 176 | static void recv_monitor(void **dev) { 177 | *dev = udev_monitor_receive_device(mon); 178 | } 179 | 180 | static void destroy_monitor(void) { 181 | udev_monitor_unref(mon); 182 | } 183 | 184 | static inline void build_conf_packet(USB_Packet *pkt, int stream_type) { 185 | memset(pkt, 0, sizeof(USB_Packet)); 186 | pkt->confpkt.head.pkt = YOCTO_PKT_CONF; 187 | pkt->confpkt.head.stream = stream_type; 188 | pkt->confpkt.head.size = YOCTO_PKT_SIZE - sizeof(pkt->confpkt.head); 189 | pkt->confpkt.head.pktno = 0; 190 | } 191 | 192 | static inline int send_and_recv_packet(USB_Packet *pkt, USB_Packet *rpkt) { 193 | int pkt_type = pkt->confpkt.head.pkt; 194 | int stream_type = pkt->confpkt.head.stream; 195 | 196 | int trans = 0; 197 | int ret = libusb_interrupt_transfer(state.hdl, state.wrendp, (unsigned char *)pkt, YOCTO_PKT_SIZE, &trans, state.interval); 198 | if (ret == 0 && trans == YOCTO_PKT_SIZE) { 199 | trans = 0; 200 | memset(rpkt, 0, sizeof(USB_Packet)); 201 | for (int i = 0; i < YOCTO_MAX_TRIES; i++) { 202 | ret = libusb_interrupt_transfer(state.hdl, state.rdendp, (unsigned char *)rpkt, YOCTO_PKT_SIZE, &trans, state.interval); 203 | if (rpkt->confpkt.head.pkt == pkt_type && rpkt->confpkt.head.stream == stream_type) { 204 | return 0; 205 | } 206 | } 207 | } 208 | return -1; 209 | } 210 | 211 | static int init_usb_device(void) { 212 | int ret = libusb_kernel_driver_active(state.hdl, YOCTO_IFACE); 213 | if (ret < 0) { 214 | YOCTO_ERR("libusb_kernel_driver_active\n"); 215 | } 216 | if (ret > 0) { 217 | printf("Need to detach kernel driver\n"); 218 | ret = libusb_detach_kernel_driver(state.hdl, YOCTO_IFACE); 219 | if (ret != 0) { 220 | YOCTO_ERR("libusb_detach_kernel_driver\n"); 221 | } 222 | } 223 | state.st |= ATTACHED; 224 | 225 | ret = libusb_claim_interface(state.hdl, YOCTO_IFACE); 226 | if (ret < 0) { 227 | YOCTO_ERR("libusb_claim_interface\n"); 228 | } 229 | state.st |= CLAIMED; 230 | 231 | const struct libusb_interface_descriptor *ifd = &state.config->interface[YOCTO_IFACE].altsetting[0]; 232 | for (int j = 0; j < ifd->bNumEndpoints; j++) { 233 | if ((ifd->endpoint[j].bEndpointAddress & LIBUSB_ENDPOINT_DIR_MASK) == LIBUSB_ENDPOINT_IN) { 234 | state.rdendp = ifd->endpoint[j].bEndpointAddress; 235 | } else { 236 | state.wrendp = ifd->endpoint[j].bEndpointAddress;; 237 | } 238 | } 239 | return -(state.rdendp == 0 || state.wrendp == 0); 240 | } 241 | 242 | static int start_usb_device(USB_Packet *rpkt) { 243 | /** Send reset **/ 244 | USB_Packet pkt; 245 | build_conf_packet(&pkt, YOCTO_CONF_RESET); 246 | pkt.confpkt.conf.reset.ok = 1; 247 | TO_SAFE_U16(pkt.confpkt.conf.reset.api, 0x0209); 248 | 249 | if (send_and_recv_packet(&pkt, rpkt) == -1) { 250 | YOCTO_ERR("Error sending packet: RESET.\n"); 251 | } 252 | /** **/ 253 | 254 | /** Send Start **/ 255 | build_conf_packet(&pkt, YOCTO_CONF_START); 256 | pkt.confpkt.conf.start.nbifaces = 1; 257 | pkt.confpkt.conf.start.ack_delay = 0; 258 | if (send_and_recv_packet(&pkt, rpkt) == -1) { 259 | YOCTO_ERR("Error sending packet: START.\n"); 260 | } 261 | /** **/ 262 | 263 | /** Check **/ 264 | int nextiface = rpkt->confpkt.conf.start.nbifaces; 265 | if (nextiface != 0) { 266 | YOCTO_ERR("Device has not been started correctly\n"); 267 | } 268 | /** **/ 269 | 270 | /** Wait on stream ready **/ 271 | 272 | /* 273 | * Note: we wait on firstByte == 1 AND streamready notification received. 274 | * They seem to not always arrive at the same moment; 275 | * sometimes, eg: first time yocto sensor is plugged, we receive 276 | * firstByte == 76, not->head.type == YOCTO_NOTIFY_PKT_STREAMREADY 277 | * 278 | * This means that stream is ready but device is not yet able to stream data. 279 | * We then wait on firstByte == 1. 280 | */ 281 | bool recved_streamready = false; 282 | memset(rpkt, 0, sizeof(USB_Packet)); 283 | for (int i = 0; i < YOCTO_MAX_TRIES; i++) { 284 | int trans = 0; 285 | libusb_interrupt_transfer(state.hdl, state.rdendp, (unsigned char *) rpkt, YOCTO_PKT_SIZE, &trans, state.interval); 286 | if (rpkt->confpkt.head.pkt == YOCTO_PKT_STREAM) { 287 | if (rpkt->confpkt.head.stream == YOCTO_STREAM_NOTICE || rpkt->confpkt.head.stream == YOCTO_STREAM_NOTICE_V2) { 288 | uint8_t *data =((uint8_t*)&rpkt->confpkt.head) + sizeof(YSTREAM_Head); 289 | USB_Notify_Pkt *not = (USB_Notify_Pkt*)data; 290 | recved_streamready |= not->head.type == YOCTO_NOTIFY_PKT_STREAMREADY || not->head.type == YOCTO_NOTIFY_PKT_STREAMREADY_2; 291 | if (not->firstByte == 1 && recved_streamready) { 292 | /* Device is now fully ready. */ 293 | return 0; 294 | } 295 | } 296 | } 297 | } 298 | /** **/ 299 | YOCTO_ERR("failed to wait on stream ready packet\n"); 300 | } 301 | 302 | static int destroy_usb_device(void) { 303 | int ret = 0; 304 | for (int i = CLAIMED; i > EMPTY; i--) { 305 | if (state.st & i) { 306 | switch (i) { 307 | case CLAIMED: 308 | ret = libusb_release_interface(state.hdl, YOCTO_IFACE); 309 | if (ret && ret != LIBUSB_ERROR_NOT_FOUND && ret != LIBUSB_ERROR_NO_DEVICE) { 310 | fprintf(stderr, "Failed: libusb_release_interface.\n"); 311 | } 312 | break; 313 | case ATTACHED: 314 | ret = libusb_attach_kernel_driver(state.hdl, YOCTO_IFACE); 315 | if(ret < 0 && ret != LIBUSB_ERROR_NO_DEVICE) { 316 | fprintf(stderr, "Failed: libusb_attach_kernel_driver.\n"); 317 | } 318 | break; 319 | default: 320 | break; 321 | } 322 | } 323 | } 324 | state.rdendp = 0; 325 | state.st = EMPTY; 326 | return ret; 327 | } 328 | 329 | static int capture(void *dev, double *pct, const int num_captures, char *settings) { 330 | int interval; 331 | parse_settings(settings, &interval); 332 | int ctr = -ENODEV; 333 | 334 | if (state.hdl) { 335 | state.interval = interval; 336 | USB_Packet rpkt = { 0 }; 337 | if (init_usb_device() == 0 && start_usb_device(&rpkt) == 0) { 338 | ctr = 0; 339 | for (int i = 0; i < num_captures; i++) { 340 | int trans = 0; 341 | libusb_interrupt_transfer(state.hdl, state.rdendp, (unsigned char *) &rpkt, YOCTO_PKT_SIZE, &trans, interval); 342 | double illuminance = atof((char *)&rpkt.data[3]); 343 | pct[ctr++] = compute_value(illuminance); 344 | } 345 | } 346 | destroy_usb_device(); 347 | } 348 | return ctr; 349 | } 350 | 351 | #endif 352 | -------------------------------------------------------------------------------- /src/modules/signal.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | MODULE("SIGNAL"); 7 | 8 | static sigset_t mask; 9 | 10 | 11 | static void block_signals(void) { 12 | sigemptyset(&mask); 13 | sigaddset(&mask, SIGINT); 14 | sigaddset(&mask, SIGTERM); 15 | sigprocmask(SIG_BLOCK, &mask, NULL); 16 | } 17 | 18 | /* 19 | * Block signals for any thread spawned, 20 | * so that only main thread will indeed receive SIGTERM/SIGINT signals 21 | * 22 | * It is needed as some libraries (libusb, libddcutil, libpipewire) spawn threads 23 | * that would mess with the signal receiving mechanism of Clightd 24 | * 25 | * NOTE: we cannot use a normal constructor because libraries ctor are 26 | * always run before program ctor, as expected; 27 | * see: https://github.com/lattera/glibc/blob/895ef79e04a953cac1493863bcae29ad85657ee1/elf/dl-init.c#L109 28 | */ 29 | __attribute__((section(".preinit_array"), used)) static typeof(block_signals) *preinit_p = block_signals; 30 | 31 | static bool check(void) { 32 | return true; 33 | } 34 | 35 | static bool evaluate(void) { 36 | return true; 37 | } 38 | 39 | static void init(void) { 40 | m_register_fd(signalfd(-1, &mask, 0), true, NULL); 41 | } 42 | 43 | static void receive(const msg_t *msg, const void *userdata) { 44 | if (!msg->is_pubsub) { 45 | struct signalfd_siginfo fdsi; 46 | ssize_t s = read(msg->fd_msg->fd, &fdsi, sizeof(struct signalfd_siginfo)); 47 | if (s != sizeof(struct signalfd_siginfo)) { 48 | m_log("An error occurred while getting signalfd data.\n"); 49 | } 50 | m_log("Received signal %d. Leaving.\n", fdsi.ssi_signo); 51 | modules_quit(0); 52 | } 53 | } 54 | 55 | static void destroy(void) { 56 | 57 | } 58 | -------------------------------------------------------------------------------- /src/utils/bus_utils.c: -------------------------------------------------------------------------------- 1 | #include "bus_utils.h" 2 | #include 3 | 4 | static char xdg_runtime_dir[PATH_MAX + 1]; 5 | static char xauth_path[PATH_MAX + 1]; 6 | 7 | int bus_sender_fill_creds(sd_bus_message *m) { 8 | xdg_runtime_dir[0] = 0; 9 | xauth_path[0] = 0; 10 | 11 | int ret = -1; 12 | sd_bus_creds *c = NULL; 13 | sd_bus_query_sender_creds(m, SD_BUS_CREDS_EUID, &c); 14 | if (c) { 15 | uid_t uid = 0; 16 | if (sd_bus_creds_get_euid(c, &uid) >= 0) { 17 | snprintf(xdg_runtime_dir, PATH_MAX, "/run/user/%d", uid); 18 | setpwent(); 19 | for (struct passwd *p = getpwent(); p; p = getpwent()) { 20 | if (p->pw_uid == uid && p->pw_dir) { 21 | snprintf(xauth_path, PATH_MAX, "%s/.Xauthority", p->pw_dir); 22 | ret = 0; 23 | break; 24 | } 25 | } 26 | endpwent(); 27 | } 28 | free(c); 29 | } 30 | return ret; 31 | } 32 | 33 | const char *bus_sender_runtime_dir(void) { 34 | if (xdg_runtime_dir[0] != 0) { 35 | return xdg_runtime_dir; 36 | } 37 | return NULL; 38 | } 39 | 40 | const char *bus_sender_xauth(void) { 41 | if (xauth_path[0] != 0) { 42 | return xauth_path; 43 | } 44 | return NULL; 45 | } 46 | 47 | void make_valid_obj_path(char *storage, size_t size, const char *root, const char *basename) { 48 | /* 49 | * Substitute wrong chars, eg: dell::kbd_backlight -> dell__kbd_backlight 50 | * See spec: https://dbus.freedesktop.org/doc/dbus-specification.html#message-protocol-marshaling-object-path 51 | */ 52 | const char *valid_chars = "ABCDEFGHIJKLMNOPQRSTUWXYZabcdefghijklmnopqrstuvwxyz0123456789_"; 53 | 54 | snprintf(storage, size, "%s/%s", root, basename); 55 | 56 | char *path = storage + strlen(root) + 1; 57 | const int full_len = strlen(path); 58 | 59 | while (true) { 60 | int len = strspn(path, valid_chars); 61 | if (len == full_len) { 62 | break; 63 | } 64 | path[len] = '_'; 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/utils/bus_utils.h: -------------------------------------------------------------------------------- 1 | #include "commons.h" 2 | 3 | int bus_sender_fill_creds(sd_bus_message *m); 4 | const char *bus_sender_runtime_dir(void); 5 | const char *bus_sender_xauth(void); 6 | 7 | void make_valid_obj_path(char *storage, size_t size, const char *root, const char *basename); 8 | -------------------------------------------------------------------------------- /src/utils/drm_utils.c: -------------------------------------------------------------------------------- 1 | #if defined GAMMA_PRESENT || defined DPMS_PRESENT 2 | 3 | #include "drm_utils.h" 4 | #include "commons.h" 5 | #include "udev.h" 6 | 7 | #define DRM_SUBSYSTEM "drm" 8 | 9 | int drm_open_card(const char **card) { 10 | if (*card == NULL || *card[0] == '\0') { 11 | /* Fetch first matching device from udev */ 12 | struct udev_device *dev = NULL; 13 | get_udev_device(NULL, DRM_SUBSYSTEM, NULL, NULL, &dev); 14 | if (!dev) { 15 | return -ENOENT; 16 | } 17 | 18 | /* Free old value */ 19 | free((char *)*card); 20 | 21 | /* Store new value */ 22 | *card = strdup(udev_device_get_devnode(dev)); 23 | udev_device_unref(dev); 24 | } 25 | int fd = open(*card, O_RDWR | O_CLOEXEC); 26 | if (fd < 0) { 27 | perror("open"); 28 | } 29 | return fd; 30 | } 31 | 32 | #endif 33 | -------------------------------------------------------------------------------- /src/utils/drm_utils.h: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | int drm_open_card(const char **card_num); 5 | -------------------------------------------------------------------------------- /src/utils/polkit.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | int check_authorization(sd_bus_message *m) { 4 | int authorized = 0; 5 | sd_bus_error error = SD_BUS_ERROR_NULL; 6 | sd_bus_message *reply = NULL; 7 | sd_bus_creds *c = sd_bus_message_get_creds(m); 8 | 9 | const char *busname; 10 | int r = sd_bus_creds_get_unique_name(c, &busname); 11 | if (r < 0) { 12 | fprintf(stderr, "%s\n", strerror(-r)); 13 | goto end; 14 | } 15 | 16 | char action_id[100] = {0}; 17 | snprintf(action_id, sizeof(action_id), "%s.%s", sd_bus_message_get_destination(m), sd_bus_message_get_member(m)); 18 | 19 | r = sd_bus_call_method(bus, "org.freedesktop.PolicyKit1", "/org/freedesktop/PolicyKit1/Authority", 20 | "org.freedesktop.PolicyKit1.Authority", "CheckAuthorization", &error, &reply, 21 | "(sa{sv})sa{ss}us", "system-bus-name", 1, "name", "s", busname, action_id, NULL, 0, ""); 22 | if (r < 0) { 23 | fprintf(stderr, "%s\n", error.message); 24 | } else { 25 | /* only read first boolean -> complete signature is "bba{ss}" but we only need first (authorized boolean) */ 26 | r = sd_bus_message_read(reply, "(bba{ss})", &authorized, NULL, NULL); 27 | if (r < 0) { 28 | fprintf(stderr, "%s\n", strerror(-r)); 29 | } 30 | } 31 | 32 | end: 33 | if (reply) { 34 | sd_bus_message_unref(reply); 35 | } 36 | if (error.message) { 37 | sd_bus_error_free(&error); 38 | } 39 | return authorized; 40 | } 41 | -------------------------------------------------------------------------------- /src/utils/polkit.h: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #define ASSERT_AUTH() \ 4 | if (!check_authorization(m)) { \ 5 | sd_bus_error_set_errno(ret_error, EPERM); \ 6 | return -EPERM; \ 7 | } 8 | 9 | int check_authorization(sd_bus_message *m); 10 | -------------------------------------------------------------------------------- /src/utils/udev.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | static void get_first_matching_device(struct udev_device **dev, const char *subsystem, const udev_match *match); 4 | 5 | int init_udev_monitor(const char *subsystem, struct udev_monitor **mon) { 6 | *mon = udev_monitor_new_from_netlink(udev, "udev"); 7 | udev_monitor_filter_add_match_subsystem_devtype(*mon, subsystem, NULL); 8 | udev_monitor_enable_receiving(*mon); 9 | return udev_monitor_get_fd(*mon); 10 | } 11 | 12 | /** 13 | * Set dev to first device in subsystem, eventually matching requested sysattr existence. 14 | */ 15 | static void get_first_matching_device(struct udev_device **dev, const char *subsystem, 16 | const udev_match *match) { 17 | struct udev_enumerate *enumerate = udev_enumerate_new(udev); 18 | udev_enumerate_add_match_subsystem(enumerate, subsystem); 19 | bool last_added =false; 20 | if (match) { 21 | if (match->sysattr_key) { 22 | udev_enumerate_add_match_sysattr(enumerate, match->sysattr_key, match->sysattr_val); 23 | } 24 | if (match->prop_key) { 25 | udev_enumerate_add_match_property(enumerate, match->prop_key, match->prop_val); 26 | } 27 | last_added = match->last_added; 28 | } 29 | udev_enumerate_scan_devices(enumerate); 30 | struct udev_list_entry *devices = udev_enumerate_get_list_entry(enumerate); 31 | if (devices) { 32 | if (!last_added) { 33 | // Return first found 34 | const char *path = udev_list_entry_get_name(devices); 35 | *dev = udev_device_new_from_syspath(udev, path); 36 | } else { 37 | // Return last found, ie: the one with largest USEC_INITIALIZED 38 | *dev = NULL; 39 | struct udev_list_entry *entry = NULL; 40 | uint64_t max_usec_initialized = 0; 41 | udev_list_entry_foreach(entry, devices) { 42 | const char *path = udev_list_entry_get_name(entry); 43 | struct udev_device *d = udev_device_new_from_syspath(udev, path); 44 | const char *usec_initialized_str = udev_device_get_property_value(d, "USEC_INITIALIZED"); 45 | uint64_t usec_initialized = strtoll(usec_initialized_str, NULL, 10); 46 | if (usec_initialized > max_usec_initialized) { 47 | if (*dev) { 48 | udev_device_unref(*dev); 49 | } 50 | *dev = d; 51 | max_usec_initialized = usec_initialized; 52 | } else { 53 | udev_device_unref(d); 54 | } 55 | } 56 | } 57 | } 58 | /* Free the enumerator object */ 59 | udev_enumerate_unref(enumerate); 60 | } 61 | 62 | void get_udev_device(const char *interface, const char *subsystem, const udev_match *match, 63 | sd_bus_error **ret_error, struct udev_device **dev) { 64 | *dev = NULL; 65 | /* if no interface is specified, try to get first matching device */ 66 | if (interface == NULL || interface[0] == '\0') { 67 | get_first_matching_device(dev, subsystem, match); 68 | } else { 69 | char *name = strrchr(interface, '/'); 70 | if (name) { 71 | return get_udev_device(++name, subsystem, match, ret_error, dev); 72 | } 73 | *dev = udev_device_new_from_subsystem_sysname(udev, subsystem, interface); 74 | if (!*dev) { 75 | // Try it as device ATTR{name} 76 | static udev_match m = { .sysattr_key = "name" }; 77 | m.sysattr_val = interface; 78 | return get_udev_device(NULL, subsystem, &m, ret_error, dev); 79 | } 80 | } 81 | if (!(*dev) && ret_error) { 82 | sd_bus_error_set_errno(*ret_error, ENODEV); 83 | } 84 | } 85 | 86 | void udev_devices_foreach(const char *subsystem, const udev_match *match, 87 | int (*cb)(struct udev_device *dev, void *userdata), void *userdata) { 88 | 89 | struct udev_enumerate *enumerate = udev_enumerate_new(udev); 90 | udev_enumerate_add_match_subsystem(enumerate, subsystem); 91 | if (match) { 92 | if (match->sysattr_key) { 93 | udev_enumerate_add_match_sysattr(enumerate, match->sysattr_key, match->sysattr_val); 94 | } 95 | if (match->sysname) { 96 | udev_enumerate_add_match_sysname(enumerate, match->sysname); 97 | } 98 | } 99 | udev_enumerate_scan_devices(enumerate); 100 | struct udev_list_entry *devices = udev_enumerate_get_list_entry(enumerate); 101 | if (devices) { 102 | struct udev_list_entry *entry = NULL; 103 | udev_list_entry_foreach(entry, devices) { 104 | const char *path = udev_list_entry_get_name(entry); 105 | struct udev_device *dev = udev_device_new_from_syspath(udev, path); 106 | int ret = cb(dev, userdata); 107 | udev_device_unref(dev); 108 | if (ret < 0) { 109 | break; 110 | } 111 | } 112 | } 113 | /* Free the enumerator object */ 114 | udev_enumerate_unref(enumerate); 115 | } 116 | -------------------------------------------------------------------------------- /src/utils/udev.h: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | typedef struct { 4 | const char *sysattr_key; 5 | const char *sysattr_val; 6 | const char *sysname; 7 | const char *prop_key; 8 | const char *prop_val; 9 | bool last_added; 10 | } udev_match; 11 | 12 | int init_udev_monitor(const char *subsystem, struct udev_monitor **mon); 13 | void get_udev_device(const char *interface, const char *subsystem, const udev_match *match, 14 | sd_bus_error **ret_error, struct udev_device **dev); 15 | void udev_devices_foreach(const char *subsystem, const udev_match *match, 16 | int (*cb)(struct udev_device *dev, void *userdata), void *userdata); 17 | -------------------------------------------------------------------------------- /src/utils/wl_utils.c: -------------------------------------------------------------------------------- 1 | #if defined GAMMA_PRESENT || defined DPMS_PRESENT || defined SCREEN_PRESENT 2 | 3 | #include "wl_utils.h" 4 | #include "commons.h" 5 | #include "bus_utils.h" 6 | #include 7 | #include 8 | 9 | #define WL_DISPLAY_DEF "wayland-0" 10 | 11 | typedef struct { 12 | struct wl_display *dpy; 13 | char *env; 14 | } wl_info; 15 | 16 | static void wl_info_dtor(void *data); 17 | 18 | static map_t *wl_map; 19 | 20 | static void _ctor_ init_wl_map(void) { 21 | wl_map = map_new(true, wl_info_dtor); 22 | } 23 | 24 | static void _dtor_ dtor_wl_map(void) { 25 | map_free(wl_map); 26 | } 27 | 28 | static void wl_info_dtor(void *data) { 29 | wl_info *info = (wl_info *)data; 30 | wl_display_disconnect(info->dpy); 31 | free(info->env); 32 | free(info); 33 | } 34 | 35 | struct wl_display *fetch_wl_display(const char *display, const char *env) { 36 | if (!env || env[0] == 0) { 37 | env = bus_sender_runtime_dir(); 38 | } 39 | if (!display || display[0] == 0) { 40 | display = WL_DISPLAY_DEF; 41 | } 42 | if (env) { 43 | wl_info *info = map_get(wl_map, display); 44 | if (!info) { 45 | /* Required for wl_display_connect */ 46 | setenv("XDG_RUNTIME_DIR", env, 1); 47 | struct wl_display *dpy = wl_display_connect(display); 48 | unsetenv("XDG_RUNTIME_DIR"); 49 | 50 | if (dpy) { 51 | info = malloc(sizeof(wl_info)); 52 | if (info) { 53 | info->dpy = dpy; 54 | info->env = strdup(env); 55 | map_put(wl_map, display, info); 56 | } else { 57 | fprintf(stderr, "Failed to malloc.\n"); 58 | wl_display_disconnect(dpy); 59 | } 60 | } 61 | } 62 | 63 | /* 64 | * Actually check that env passed is the same that 65 | * was stored when wl_display connection was created 66 | */ 67 | if (info && !strcmp(info->env, env)) { 68 | return info->dpy; 69 | } 70 | } 71 | return NULL; 72 | } 73 | 74 | /* 75 | * Directly use syscall on old glibc: 76 | * > The memfd_create() system call first appeared in Linux 3.17; 77 | * > glibc support was added in version 2.27. 78 | */ 79 | int create_anonymous_file(off_t size, const char *filename) { 80 | #if __GLIBC__ >= 2 && __GLIBC_MINOR__ >= 27 81 | int fd = memfd_create(filename, 0); 82 | #else 83 | int fd = syscall(SYS_memfd_create, filename, 0); 84 | #endif 85 | if (fd < 0) { 86 | return -1; 87 | } 88 | 89 | int ret; 90 | do { 91 | errno = 0; 92 | ret = ftruncate(fd, size); 93 | } while (errno == EINTR); 94 | if (ret < 0) { 95 | close(fd); 96 | return -1; 97 | } 98 | return fd; 99 | } 100 | 101 | #endif 102 | -------------------------------------------------------------------------------- /src/utils/wl_utils.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | struct wl_display *fetch_wl_display(const char *display, const char *env); 7 | int create_anonymous_file(off_t size, const char *filename); 8 | -------------------------------------------------------------------------------- /src/utils/xorg_utils.c: -------------------------------------------------------------------------------- 1 | #if defined GAMMA_PRESENT || defined DPMS_PRESENT || defined SCREEN_PRESENT 2 | 3 | #include "bus_utils.h" 4 | #include "xorg_utils.h" 5 | 6 | #define XORG_DISPLAY_DEF ":0" 7 | 8 | Display *fetch_xorg_display(const char **display, const char *xauthority) { 9 | if (!*display || display[0] == 0) { 10 | *display = XORG_DISPLAY_DEF; 11 | } 12 | if (!xauthority || xauthority[0] == 0) { 13 | xauthority = bus_sender_xauth(); 14 | } 15 | 16 | if (xauthority) { 17 | setenv("XAUTHORITY", xauthority, 1); 18 | Display *dpy = XOpenDisplay(*display); 19 | unsetenv("XAUTHORITY"); 20 | return dpy; 21 | } 22 | return NULL; 23 | } 24 | 25 | #endif 26 | -------------------------------------------------------------------------------- /src/utils/xorg_utils.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | Display *fetch_xorg_display(const char **display, const char *xauthority); 6 | --------------------------------------------------------------------------------