├── .gitmodules ├── CMake ├── FindChck.cmake ├── FindInihck.cmake ├── FindLibInput.cmake ├── FindMath.cmake ├── FindWlc.cmake ├── FindXKBCommon.cmake ├── GCCCompatibleCompilerOptions.cmake └── subproject.cmake ├── CMakeLists.txt ├── CONTRIBUTING.rst ├── LICENSE ├── README.rst ├── include └── orbment │ ├── defines.h │ └── plugin.h ├── lib └── CMakeLists.txt ├── orbment.1.in ├── plugins ├── CMakeLists.txt ├── autostart │ ├── CMakeLists.txt │ └── autostart.c ├── common.h ├── compressor │ ├── CMakeLists.txt │ ├── compressor-png.c │ ├── compressor-ppm.c │ └── compressor.c ├── configuration │ ├── CMakeLists.txt │ ├── configuration-ini.c │ ├── configuration.c │ └── configuration.rst ├── core-dpms │ ├── CMakeLists.txt │ └── core-dpms.c ├── core-functionality │ ├── CMakeLists.txt │ └── core-functionality.c ├── core-input │ ├── CMakeLists.txt │ └── core-input.c ├── core-layouts │ ├── CMakeLists.txt │ └── core-layouts.c ├── core-screenshot │ ├── CMakeLists.txt │ └── core-screenshot.c ├── crappy-borders │ ├── CMakeLists.txt │ └── crappy-borders.c ├── keybind │ ├── CMakeLists.txt │ └── keybind.c └── layout │ ├── CMakeLists.txt │ └── layout.c ├── src ├── CMakeLists.txt ├── config.h.in ├── hooks.c ├── hooks.h ├── log.c ├── log.h ├── orbment.c ├── plugin.c ├── plugin.h ├── signals.c └── signals.h └── uncrustify.conf /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "wlc"] 2 | path = lib/wlc 3 | url = git://github.com/Cloudef/wlc.git 4 | branch = master 5 | [submodule "inihck"] 6 | path = lib/inihck 7 | url = git://github.com/Cloudef/inihck.git 8 | branch = master 9 | [submodule "lib/chck"] 10 | path = lib/chck 11 | url = git://github.com/Cloudef/chck.git 12 | branch = master 13 | -------------------------------------------------------------------------------- /CMake/FindChck.cmake: -------------------------------------------------------------------------------- 1 | #.rst: 2 | # FindChck 3 | # ------- 4 | # 5 | # Find chck library 6 | # 7 | # Try to find chck library. The following values are defined 8 | # 9 | # :: 10 | # 11 | # CHCK_FOUND - True if chck is available 12 | # CHCK_INCLUDE_DIRS - Include directories for chck 13 | # CHCK_LIBRARIES - List of libraries for chck 14 | # CHCK_DEFINITIONS - List of definitions for chck 15 | # 16 | #============================================================================= 17 | # Copyright (c) 2015 Jari Vetoniemi 18 | # 19 | # Distributed under the OSI-approved BSD License (the "License"); 20 | # 21 | # This software is distributed WITHOUT ANY WARRANTY; without even the 22 | # implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 23 | # See the License for more information. 24 | #============================================================================= 25 | 26 | unset(CHCK_INCLUDE_DIRS CACHE) 27 | unset(CHCK_LIBRARIES CACHE) 28 | 29 | include(FeatureSummary) 30 | set_package_properties(chck PROPERTIES 31 | URL "https://github.com/Cloudef/chck/" 32 | DESCRIPTION "Collection of C utilities (Shared linking not recommended)") 33 | 34 | find_package(PkgConfig) 35 | pkg_check_modules(PC_CHCK QUIET chck) 36 | find_path(CHCK_INCLUDE_DIRS NAMES chck/macros.h HINTS ${PC_CHCK_INCLUDE_DIRS}) 37 | 38 | set(libraries 39 | chck-atlas 40 | chck-buffer 41 | chck-dl 42 | chck-fs 43 | chck-lut 44 | chck-pool 45 | chck-sjis 46 | chck-string 47 | chck-tqueue 48 | chck-unicode 49 | chck-xdg) 50 | 51 | unset(libs) 52 | foreach(lib ${libraries}) 53 | find_library(CHCK_LIBRARIES_${lib} NAMES ${lib} HINTS ${PC_CHCK_LIBRARY_DIRS}) 54 | list(APPEND libs ${CHCK_LIBRARIES_${lib}}) 55 | mark_as_advanced(CHCK_LIBRARIES_${lib}) 56 | unset(CHCK_LIBRARIES_${lib} CACHE) 57 | endforeach (lib ${libraries}) 58 | 59 | set(CHCK_LIBRARIES ${libs} CACHE FILEPATH "Path to chck libraries" FORCE) 60 | set(CHCK_DEFINITIONS ${PC_CHCK_CFLAGS_OTHER}) 61 | 62 | include(FindPackageHandleStandardArgs) 63 | find_package_handle_standard_args(chck DEFAULT_MSG CHCK_LIBRARIES CHCK_INCLUDE_DIRS) 64 | mark_as_advanced(CHCK_LIBRARIES CHCK_INCLUDE_DIRS CHCK_DEFINITIONS) 65 | unset(libs) 66 | -------------------------------------------------------------------------------- /CMake/FindInihck.cmake: -------------------------------------------------------------------------------- 1 | #.rst: 2 | # FindInihck 3 | # ------- 4 | # 5 | # Find inihck library 6 | # 7 | # Try to find wlc library. The following values are defined 8 | # 9 | # :: 10 | # 11 | # INIHCK_FOUND - True if inihck is available 12 | # INIHCK_INCLUDE_DIRS - Include directories for inihck 13 | # INIHCK_LIBRARIES - List of libraries for inihck 14 | # INIHCK_DEFINITIONS - List of definitions for inihck 15 | # 16 | #============================================================================= 17 | # Copyright (c) 2015 Jari Vetoniemi 18 | # 19 | # Distributed under the OSI-approved BSD License (the "License"); 20 | # 21 | # This software is distributed WITHOUT ANY WARRANTY; without even the 22 | # implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 23 | # See the License for more information. 24 | #============================================================================= 25 | 26 | unset(INIHCK_INCLUDE_DIRS CACHE) 27 | unset(INIHCK_LIBRARIES CACHE) 28 | 29 | find_package(PkgConfig) 30 | pkg_check_modules(PC_INIHCK QUIET inihck) 31 | find_path(INIHCK_INCLUDE_DIRS NAMES inihck/inihck.h HINTS ${PC_INIHCK_INCLUDE_DIRS}) 32 | find_library(INIHCK_LIBRARIES NAMES inihck HINTS ${PC_INIHCK_LIBRARY_DIRS}) 33 | 34 | set(INIHCK_DEFINITIONS ${PC_INIHCK_CFLAGS_OTHER}) 35 | 36 | include(FindPackageHandleStandardArgs) 37 | find_package_handle_standard_args(inihck DEFAULT_MSG INIHCK_LIBRARIES INIHCK_INCLUDE_DIRS) 38 | mark_as_advanced(INIHCK_LIBRARIES INIHCK_INCLUDE_DIRS INIHCK_DEFINITIONS) 39 | -------------------------------------------------------------------------------- /CMake/FindLibInput.cmake: -------------------------------------------------------------------------------- 1 | #.rst: 2 | # FindLibInput 3 | # ------- 4 | # 5 | # Find LibInput library 6 | # 7 | # Try to find LibInpu library. The following values are defined 8 | # 9 | # :: 10 | # 11 | # LIBINPUT_FOUND - True if libinput is available 12 | # LIBINPUT_INCLUDE_DIRS - Include directories for libinput 13 | # LIBINPUT_LIBRARIES - List of libraries for libinput 14 | # LIBINPUT_DEFINITIONS - List of definitions for libinput 15 | # 16 | # and also the following more fine grained variables 17 | # 18 | # :: 19 | # 20 | # LIBINPUT_VERSION 21 | # LIBINPUT_VERSION_MAJOR 22 | # LIBINPUT_VERSION_MINOR 23 | # LIBINPUT_VERSION_MICRO 24 | # 25 | #============================================================================= 26 | # Copyright (c) 2015 Jari Vetoniemi 27 | # 28 | # Distributed under the OSI-approved BSD License (the "License"); 29 | # 30 | # This software is distributed WITHOUT ANY WARRANTY; without even the 31 | # implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 32 | # See the License for more information. 33 | #============================================================================= 34 | 35 | include(FeatureSummary) 36 | set_package_properties(LibInput PROPERTIES 37 | URL "http://freedesktop.org/wiki/Software/libinput/" 38 | DESCRIPTION "Library to handle input devices") 39 | 40 | find_package(PkgConfig) 41 | pkg_check_modules(PC_INPUT QUIET libinput) 42 | find_library(LIBINPUT_LIBRARIES NAMES input HINTS ${PC_INPUT_LIBRARY_DIRS}) 43 | find_path(LIBINPUT_INCLUDE_DIRS libinput.h HINTS ${PC_INPUT_INCLUDE_DIRS}) 44 | 45 | set(LIBINPUT_VERSION ${PC_INPUT_VERSION}) 46 | string(REPLACE "." ";" VERSION_LIST "${PC_INPUT_VERSION}") 47 | 48 | LIST(LENGTH VERSION_LIST n) 49 | if (n EQUAL 3) 50 | list(GET VERSION_LIST 0 LIBINPUT_VERSION_MAJOR) 51 | list(GET VERSION_LIST 1 LIBINPUT_VERSION_MINOR) 52 | list(GET VERSION_LIST 2 LIBINPUT_VERSION_MICRO) 53 | endif () 54 | 55 | # This is compatible with libinput-version.h that exists in upstream 56 | # but isn't in distribution (probably forgotten) 57 | set(LIBINPUT_DEFINITIONS ${PC_INPUT_CFLAGS_OTHER} 58 | -DLIBINPUT_VERSION=\"${LIBINPUT_VERSION}\" 59 | -DLIBINPUT_VERSION_MAJOR=${LIBINPUT_VERSION_MAJOR} 60 | -DLIBINPUT_VERSION_MINOR=${LIBINPUT_VERSION_MINOR} 61 | -DLIBINPUT_VERSION_MICRO=${LIBINPUT_VERSION_MICRO}) 62 | 63 | include(FindPackageHandleStandardArgs) 64 | find_package_handle_standard_args(LIBINPUT DEFAULT_MSG LIBINPUT_INCLUDE_DIRS LIBINPUT_LIBRARIES) 65 | mark_as_advanced(LIBINPUT_INCLUDE_DIRS LIBINPUT_LIBRARIES LIBINPUT_DEFINITIONS 66 | LIBINPUT_VERSION LIBINPUT_VERSION_MAJOR LIBINPUT_VERSION_MICRO LIBINPUT_VERSION_MINOR) 67 | -------------------------------------------------------------------------------- /CMake/FindMath.cmake: -------------------------------------------------------------------------------- 1 | #.rst: 2 | # FindMath 3 | # ------- 4 | # 5 | # Find standard C math library 6 | # 7 | # Try to find standard C math library. The following values are defined 8 | # 9 | # :: 10 | # 11 | # MATH_FOUND - True if math is available 12 | # MATH_LIBRARY - Library for math 13 | # 14 | #============================================================================= 15 | # Copyright (c) 2015 Jari Vetoniemi 16 | # 17 | # Distributed under the OSI-approved BSD License (the "License"); 18 | # 19 | # This software is distributed WITHOUT ANY WARRANTY; without even the 20 | # implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 21 | # See the License for more information. 22 | #============================================================================= 23 | 24 | set_package_properties(Math PROPERTIES 25 | DESCRIPTION "Standard C math library") 26 | 27 | find_library(MATH_LIBRARY m) 28 | include(FindPackageHandleStandardArgs) 29 | find_package_handle_standard_args(MATH DEFAULT_MSG MATH_LIBRARY) 30 | mark_as_advanced(MATH_LIBRARY) 31 | -------------------------------------------------------------------------------- /CMake/FindWlc.cmake: -------------------------------------------------------------------------------- 1 | #.rst: 2 | # FindWlc 3 | # ------- 4 | # 5 | # Find wlc library 6 | # 7 | # Try to find wlc library. The following values are defined 8 | # 9 | # :: 10 | # 11 | # WLC_FOUND - True if wlc is available 12 | # WLC_INCLUDE_DIRS - Include directories for wlc 13 | # WLC_LIBRARIES - List of libraries for wlc 14 | # WLC_DEFINITIONS - List of definitions for wlc 15 | # 16 | #============================================================================= 17 | # Copyright (c) 2015 Jari Vetoniemi 18 | # 19 | # Distributed under the OSI-approved BSD License (the "License"); 20 | # 21 | # This software is distributed WITHOUT ANY WARRANTY; without even the 22 | # implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 23 | # See the License for more information. 24 | #============================================================================= 25 | 26 | unset(WLC_INCLUDE_DIRS CACHE) 27 | unset(WLC_LIBRARIES CACHE) 28 | 29 | include(FeatureSummary) 30 | set_package_properties(wlc PROPERTIES 31 | URL "https://github.com/Cloudef/wlc/" 32 | DESCRIPTION "Wayland compositor library") 33 | 34 | find_package(PkgConfig) 35 | pkg_check_modules(PC_WLC QUIET wlc) 36 | find_path(WLC_INCLUDE_DIRS NAMES wlc/wlc.h HINTS ${PC_WLC_INCLUDE_DIRS}) 37 | find_library(WLC_LIBRARIES NAMES wlc HINTS ${PC_WLC_LIBRARY_DIRS}) 38 | 39 | set(WLC_DEFINITIONS ${PC_WLC_CFLAGS_OTHER}) 40 | 41 | include(FindPackageHandleStandardArgs) 42 | find_package_handle_standard_args(wlc DEFAULT_MSG WLC_LIBRARIES WLC_INCLUDE_DIRS) 43 | mark_as_advanced(WLC_LIBRARIES WLC_INCLUDE_DIRS WLC_DEFINITIONS) 44 | -------------------------------------------------------------------------------- /CMake/FindXKBCommon.cmake: -------------------------------------------------------------------------------- 1 | #.rst: 2 | # FindXKBCommon 3 | # ------- 4 | # 5 | # Find XKBCommon library 6 | # 7 | # Try to find XKBCommon library. The following values are defined 8 | # 9 | # :: 10 | # 11 | # XKBCOMMON_FOUND - True if XKBCommon is available 12 | # XKBCOMMON_INCLUDE_DIRS - Include directories for XKBCommon 13 | # XKBCOMMON_LIBRARIES - List of libraries for XKBCommon 14 | # XKBCOMMON_DEFINITIONS - List of definitions for XKBCommon 15 | # 16 | #============================================================================= 17 | # Copyright (c) 2015 Jari Vetoniemi 18 | # 19 | # Distributed under the OSI-approved BSD License (the "License"); 20 | # 21 | # This software is distributed WITHOUT ANY WARRANTY; without even the 22 | # implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 23 | # See the License for more information. 24 | #============================================================================= 25 | 26 | include(FeatureSummary) 27 | set_package_properties(XKBCommon PROPERTIES 28 | URL "http://xkbcommon.org/" 29 | DESCRIPTION "Library to handle keyboard descriptions") 30 | 31 | find_package(PkgConfig) 32 | pkg_check_modules(PC_XKBCOMMON QUIET xkbcommon) 33 | find_path(XKBCOMMON_INCLUDE_DIRS NAMES xkbcommon/xkbcommon.h HINTS ${PC_XKBCOMMON_INCLUDE_DIRS}) 34 | find_library(XKBCOMMON_LIBRARIES NAMES xkbcommon HINTS ${PC_XKBCOMMON_LIBRARY_DIRS}) 35 | 36 | set(XKBCOMMON_DEFINITIONS ${PC_XKBCOMMON_CFLAGS_OTHER}) 37 | 38 | include(FindPackageHandleStandardArgs) 39 | find_package_handle_standard_args(XKBCOMMON DEFAULT_MSG XKBCOMMON_LIBRARIES XKBCOMMON_INCLUDE_DIRS) 40 | mark_as_advanced(XKBCOMMON_LIBRARIES XKBCOMMON_INCLUDE_DIRS XKBCOMMON_DEFINITIONS) 41 | -------------------------------------------------------------------------------- /CMake/GCCCompatibleCompilerOptions.cmake: -------------------------------------------------------------------------------- 1 | include(CheckCCompilerFlag) 2 | 3 | # Add list of compiler warnings 4 | # Every warning gets checked with check_c_compiler_flags 5 | function(add_compiler_warnings) 6 | foreach (flag ${ARGN}) 7 | check_c_compiler_flag(${flag} ok) 8 | if (ok) 9 | add_compile_options(${flag}) 10 | endif () 11 | endforeach () 12 | endfunction () 13 | 14 | # Create new ${EXE,MODULE,SHARED}_LINKER_FLAGS build type for list of linker flags 15 | # Every linker flag gets checked with check_c_compiler_flag 16 | function(create_custom_linker_flags name) 17 | set(ldflags) 18 | foreach (flag ${ARGN}) 19 | check_c_compiler_flag(-Wl,${flag} ok) 20 | if (ok) 21 | if (ldflags) 22 | set(ldflags "${ldflags},${flag}") 23 | else () 24 | set(ldflags "-Wl,${flag}") 25 | endif () 26 | endif () 27 | endforeach () 28 | 29 | string(TOUPPER ${name} upper) 30 | set(CMAKE_EXE_LINKER_FLAGS_${upper} "${ldflags}" CACHE STRING "${name} exe linker flags" FORCE) 31 | set(CMAKE_MODULE_LINKER_FLAGS_${upper} "${ldflags}" CACHE STRING "${name} module linker flags" FORCE) 32 | set(CMAKE_SHARED_LINKER_FLAGS_${upper} "${ldflags}" CACHE STRING "${name} shared linker flags" FORCE) 33 | mark_as_advanced(CMAKE_EXE_LINKER_FLAGS_${upper} CMAKE_SHARED_LINKER_FLAGS_${upper} CMAKE_MODULE_LINKER_FLAGS_${upper}) 34 | endfunction () 35 | 36 | # Create new {C,CXX}_FLAGS build type for list of compiler flags 37 | # Every compiler flag gets checked with check_c_compiler_flag 38 | function(create_custom_compiler_flags name) 39 | set(cflags) 40 | foreach (flag ${ARGN}) 41 | check_c_compiler_flag(${flag} ok) 42 | if (ok) 43 | if (cflags) 44 | set(cflags "${cflags} ${flag}") 45 | else () 46 | set(cflags "${flag}") 47 | endif () 48 | endif () 49 | endforeach () 50 | 51 | string(TOUPPER ${name} upper) 52 | set(CMAKE_C_FLAGS_${upper} "${cflags}" CACHE STRING "${name} C flags" FORCE) 53 | set(CMAKE_CXX_FLAGS_${upper} "${cflags}" CACHE STRING "${name} CXX flags" FORCE) 54 | mark_as_advanced(CMAKE_CXX_FLAGS_${upper} CMAKE_C_FLAGS_${upper}) 55 | endfunction () 56 | -------------------------------------------------------------------------------- /CMake/subproject.cmake: -------------------------------------------------------------------------------- 1 | # subproject.cmake: 2 | # 3 | # Builds another dependant CMake project, unless the project was already built or installed systemwide. 4 | # 5 | # It does this by first checking if project exists systemwide and uses that. 6 | # If not, it checks if the project was already included and if it was, it does nothing. 7 | # If both of the above are not true, another CMake project is build. 8 | # 9 | # Usually the subprojects are git submodules on git repository, so packagers can avoid linking subprojects and 10 | # instead always use systemwide libraries, by not downloading submodules. This will normally cause build to fail 11 | # if systemwide package was not found. 12 | # 13 | # Developers can control this behaviour with -DSOURCE_=ON|OFF option. It's usually useful to have 14 | # local development versions of everything when developing, and this option makes sure nothing is linked against 15 | # systemwide libraries. 16 | # 17 | # If subprojects are being linked locally, and subprojects include subprojects that were already included. 18 | # They will not include the subproject again. Instead they link against the already compiled subprojects. 19 | # 20 | # This is convenient for development, but not so covenient if you need to have different versions of submodules 21 | # in-tree for some reason. Rather than doing that I just suggest updating the codebase to work with same library versions. 22 | 23 | function(add_subproject name) 24 | if(ARGC GREATER 1) 25 | set(package_name ${ARGV1}) 26 | else() 27 | set(package_name ${name}) 28 | endif() 29 | 30 | if(TARGET ${name}) 31 | message("Subproject ${name} already included, skipping") 32 | else() 33 | find_package(${package_name} QUIET) 34 | string(TOUPPER ${package_name} upper) 35 | if(NOT SOURCE_${upper} AND ${upper}_FOUND) 36 | message("Found ${package_name} on system") 37 | else() 38 | message("Adding ${name} as subdirectory") 39 | add_subdirectory(${name} EXCLUDE_FROM_ALL) 40 | endif() 41 | endif() 42 | endfunction() 43 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | CMAKE_MINIMUM_REQUIRED(VERSION 3.1) 2 | PROJECT(orbment VERSION 0.0.1 LANGUAGES C) 3 | set(CMAKE_MODULE_PATH "${CMAKE_MODULE_PATH}" "${PROJECT_SOURCE_DIR}/CMake") 4 | 5 | # Subprojects 6 | include(subproject) 7 | add_subdirectory(lib) 8 | 9 | # Public depencies of wlc 10 | find_package(XKBCommon REQUIRED) 11 | add_definitions(${XKBCOMMON_DEFINTIONS}) 12 | include_directories(${XKBCOMMON_INCLUDE_DIRS}) 13 | 14 | find_package(LibInput REQUIRED) 15 | add_definitions(${LIBINPUT_DEFINTIONS}) 16 | include_directories(${LIBINPUT_INCLUDE_DIRS}) 17 | 18 | # CPack 19 | set(CPACK_SYSTEM_NAME "${CMAKE_SYSTEM_NAME}-${CMAKE_SYSTEM_PROCESSOR}") 20 | set(CPACK_GENERATOR "7Z") 21 | set(CPACK_PACKAGE_VERSION "${PROJECT_VERSION}") 22 | set(CPACK_PACKAGE_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/pkg") 23 | set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "Modular Wayland compositor") 24 | set(CPACK_ERROR_ON_ABSOLUTE_INSTALL_DESTINATION ON) 25 | 26 | # Includes 27 | include(GNUInstallDirs) 28 | include(FeatureSummary) 29 | include(CPack) 30 | set(ORBMENT_PLUGINS_FULL_PATH "${CMAKE_INSTALL_FULL_LIBDIR}/orbment" CACHE STRING "Systemwide plugins path (absolute)" FORCE) 31 | set(ORBMENT_PLUGINS_PATH "${CMAKE_INSTALL_LIBDIR}/orbment" CACHE STRING "Systemwide plugins path" FORCE) 32 | 33 | # Compiler options 34 | include(GCCCompatibleCompilerOptions) 35 | 36 | if (CMAKE_COMPILER_IS_GNUCC OR CMAKE_COMPILER_IS_GNUCXX) 37 | set(ldflags -O1 --sort-common --as-needed -z,relro -z,now) 38 | set(cflags -flto -fuse-linker-plugin) 39 | endif () 40 | 41 | check_c_compiler_flag(-fstack-protector-strong has_fstack_protector_strong) 42 | if (has_fstack_protector_strong) 43 | list(APPEND cflags -fstack-protector-strong -fstack-check --param ssp-buffer-size=4) 44 | else () 45 | list(APPEND cflags -fstack-protector-all -fstack-check --param ssp-buffer-size=4) 46 | endif () 47 | 48 | create_custom_linker_flags(Upstream ${ldflags}) 49 | create_custom_compiler_flags(Upstream -g -O2 ${cflags}) 50 | 51 | add_compiler_warnings(-Wall -Wextra -Wno-variadic-macros -Wno-long-long -Wformat=2 -Winit-self -Wfloat-equal -Wcast-align -Wpointer-arith -Wmissing-prototypes) 52 | 53 | if (CMAKE_COMPILER_IS_GNUCC OR CMAKE_COMPILER_IS_GNUCXX) 54 | add_compiler_warnings(-Wsuggest-attribute=pure -Wsuggest-attribute=const) 55 | elseif (CMAKE_C_COMPILER_ID MATCHES "Clang") 56 | add_compiler_warnings(-Wno-pointer-bool-conversion -Wno-missing-field-initializers -Wno-missing-braces) 57 | endif () 58 | 59 | # -std=c99 -fpic -fpie -D_DEFAULT_SOURCE 60 | set(CMAKE_C_STANDARD 99) 61 | set(CMAKE_C_STANDARD_REQUIRED ON) 62 | set(CMAKE_C_EXTENSIONS OFF) 63 | set(CMAKE_POSITION_INDEPENDENT_CODE ON) 64 | add_definitions(-D_DEFAULT_SOURCE) 65 | 66 | add_subdirectory(src) 67 | add_subdirectory(plugins) 68 | 69 | configure_file(orbment.1.in man/man1/orbment.1 @ONLY) 70 | install(DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/man/man1" DESTINATION "${CMAKE_INSTALL_MANDIR}") 71 | install(DIRECTORY include/orbment DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}") 72 | 73 | if ("${CMAKE_PROJECT_NAME}" STREQUAL "${PROJECT_NAME}") 74 | feature_summary(WHAT ALL) 75 | endif () 76 | -------------------------------------------------------------------------------- /CONTRIBUTING.rst: -------------------------------------------------------------------------------- 1 | HOW TO CONTRIBUTE 2 | ----------------- 3 | 4 | Look at the project issues list for things to do. 5 | If you are planning contributing code that does not fix issue, visit the project IRC channel first. 6 | 7 | Include clear description in comment, what your commit does. 8 | If it fixes issue, include issue code in the description. 9 | 10 | Eventually some testing guidelines will be added, but for now there is none. 11 | 12 | If you have questions or need help, visit the project IRC channel. 13 | 14 | MAKING CHANGES 15 | -------------- 16 | 17 | - Fork the repository. 18 | - Create branch for your feature. 19 | - Follow the code style guidelines below and make changes. 20 | - Open PR against master. 21 | - When your PR is approved, squash your commits into one if they aren't already. 22 | - PR gets merged. 23 | 24 | CODE STYLE 25 | ---------- 26 | 27 | Project uses the C99 standard, avoid GNU and other unportable extensions. 28 | Use C99 standard types. (stdbool, stdint) 29 | 30 | 3 spaces indentation, no tabs. Unix newlines (LF), UTF8 files and no BOM. 31 | 32 | Variable declarations should be done where the variables are first used. 33 | Do not put variable declarations top of the function like in ANSI C, this is pointless. 34 | 35 | Asserts are encouraged, use them whenever it makes sense. 36 | When validating function arguments (user input), put the asserts at top of the function. 37 | This helps others to immediately see what is not valid input for the function. 38 | 39 | Avoid useless comments in code. Code should be clear enough to understand without comments. 40 | If something is hard to explain with comment, or you are not sure of something, the code probably could be simplified. 41 | Comments are there when something has good reason to be explained. 42 | 43 | Do not use typedef without a good reason. See Linux kernel's `Chapter 5 Typedefs `_ 44 | 45 | Use const whenever variable should not change. 46 | 47 | Use static always when symbol should not be exposed outside, do not use static inside functions unless really needed. 48 | 49 | Use newline after type name in function declarations. For function prototypes, just keep them one line. 50 | 51 | Single line conditionals are allowed, however if the conditional contains else, it should be bracketed. 52 | 53 | Avoid unnecessary whitespace and newlines. 54 | 55 | Put opening brace on same line for anything except function declarations and cases. 56 | 57 | Do not put spaces around parenthesis, with exception of open parenthesis for keyword (if, while, for) 58 | 59 | Put spaces around arithmetic operators. (1+2 -> 1 + 2) 60 | 61 | Put spaces after colons. (a,b,c -> a, b, c) 62 | 63 | Get familiar with the utility functions included in `chck `_, especially the chck_string and chck_pool. 64 | Any useful generic utility function should be contributed there. 65 | 66 | .. code:: c 67 | 68 | #include 69 | #include 70 | 71 | #if INDENT_CPP 72 | # define EXPLAIN "Put # always leftmost and indent with spaces after it" 73 | #endif 74 | 75 | enum type { 76 | WHITE, 77 | GRAY, 78 | BLACK, 79 | }; 80 | 81 | struct foo { 82 | bool bar; 83 | enum type type; 84 | }; 85 | 86 | bool prototype(int32_t foo); 87 | 88 | /** 89 | * Function comment block. 90 | * Most editors do this formatting automatically. 91 | * 92 | * Always use static when the function is not supposed to be exposed outside. 93 | */ 94 | static bool 95 | declaration(int32_t foo) 96 | { 97 | // User input assertation. 98 | // In this case we document developer, foo must be between -32 and 32. 99 | assert(foo > -32 && foo < 32); 100 | 101 | bool bar = false; 102 | 103 | // Single line ifs are allowed 104 | if (foo == 1) 105 | bar = true; 106 | 107 | // However if you must use else, use braces 108 | if ((foo + bar) * ~foo == 4) { 109 | foo = 8; 110 | } else { 111 | bar = false; 112 | } 113 | 114 | if (foo == 8) 115 | goto error; 116 | 117 | // Pointer operators (star, reference) should be next to variable. 118 | void *baf = NULL, *baz = NULL; 119 | 120 | return bar; 121 | 122 | // Labels are aligned to left 123 | error: 124 | return false; 125 | } 126 | 127 | 128 | UNCRUSTIFY 129 | ---------- 130 | 131 | The repository contains `Uncrustify `_ configuration 132 | for automatic styling of source code. While it does good job overall, there are few pitfalls. 133 | 134 | The most common one is that it thinks anything with operators after cast is arithmetic. 135 | 136 | .. code:: c 137 | 138 | // formats this 139 | static int foo = (bar)~0; 140 | 141 | // to this 142 | static int foo = (bar) ~0; 143 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Orbment, Modular Wayland compositor 2 | Copyright (C) 2014 Jari Vetoniemi 3 | 4 | This program is free software: you can redistribute it and/or modify 5 | it under the terms of the GNU General Public License as published by 6 | the Free Software Foundation, either version 3 of the License, or 7 | (at your option) any later version. 8 | 9 | This program is distributed in the hope that it will be useful, 10 | but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | GNU General Public License for more details. 13 | 14 | You should have received a copy of the GNU General Public License 15 | along with this program. If not, see . 16 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | .. |build| image:: http://build.cloudef.pw/build/orbment/master/linux%20x86_64/current/status.svg 2 | .. _build: http://build.cloudef.pw/build/orbment/master/linux%20x86_64 3 | 4 | .. image:: http://cloudef.pw/armpit/loliwm-gh.png 5 | :IRC: #orbment @ freenode 6 | :Video: https://www.youtube.com/watch?v=nh_7aqNtrik 7 | :Build: |build|_ 8 | 9 | Orbment is modular compositor for Wayland with flexible plugin architecture where plugins may co-operate with each other. 10 | The core consist of small code base which provide plugin management, and hooks api for plugins. 11 | Core plugins are used to provide functionality you would expect from bare bones tiling window manager. 12 | 13 | OPTIONS 14 | ------- 15 | 16 | Basic information about what you can currently do in ``orbment``. 17 | 18 | +-----------------------+------------------------------------------------+ 19 | | ``--log FILE`` | Logs output to specified ``FILE``. | 20 | +-----------------------+------------------------------------------------+ 21 | 22 | See `wlc documentation `_ for ``wlc`` specific options. 23 | 24 | KEYBINDS 25 | -------- 26 | 27 | Note that these keybinds are temporary until configuration is added. 28 | 29 | +-----------------+------------------------------------------------------+ 30 | | ``mod-return`` | Opens a terminal emulator. | 31 | +-----------------+------------------------------------------------------+ 32 | | ``mod-p`` | Opens ``bemenu-run``. | 33 | +-----------------+------------------------------------------------------+ 34 | | ``mod-w`` | Rotates through available layouts. | 35 | +-----------------+------------------------------------------------------+ 36 | | ``mod-l`` | Rotates focus through outputs. | 37 | +-----------------+------------------------------------------------------+ 38 | | ``mod-j, k`` | Rotates focus through clients. | 39 | +-----------------+------------------------------------------------------+ 40 | | ``mod-f`` | Toggles fullscreen. | 41 | +-----------------+------------------------------------------------------+ 42 | | ``mod-[1..n]`` | Activate space. | 43 | +-----------------+------------------------------------------------------+ 44 | | ``mod-F1..F10`` | Moves focused client to corresponding space. | 45 | +-----------------+------------------------------------------------------+ 46 | | ``mod-z, x, c`` | Moves focused client to output 1, 2 and 3 | 47 | | | respectively. | 48 | +-----------------+------------------------------------------------------+ 49 | | ``mod-h`` | Cycles clients. | 50 | +-----------------+------------------------------------------------------+ 51 | | ``mod-q`` | Closes focused client. | 52 | +-----------------+------------------------------------------------------+ 53 | | ``mod-i, o`` | Shifts the cut of the nmaster layout to shrink or | 54 | | | expand the view. | 55 | +-----------------+------------------------------------------------------+ 56 | | ``mod-s`` | Takes a screenshot in PNG format. | 57 | +-----------------+------------------------------------------------------+ 58 | | ``mod-esc`` | Quits ``orbment``. | 59 | +-----------------+------------------------------------------------------+ 60 | 61 | KEYBOARD LAYOUT 62 | --------------- 63 | 64 | You can set your preferred keyboard layout using ``XKB_DEFAULT_LAYOUT``. 65 | 66 | .. code:: sh 67 | 68 | XKB_DEFAULT_LAYOUT=gb orbment 69 | 70 | RUNNING ON TTY 71 | -------------- 72 | 73 | If you have ``logind``, you can just run ``orbment`` normally. 74 | 75 | Without ``logind`` you need to suid the orbment binary to root user. 76 | The permissions will be dropped runtime. 77 | 78 | BUILDING 79 | -------- 80 | 81 | See `wlc documentation `_ for dependencies. 82 | 83 | You can build bootstrapped version of ``orbment`` which also includes ``wlc`` with the following steps. 84 | 85 | .. code:: sh 86 | 87 | git submodule update --init --recursive # - initialize and fetch submodules 88 | mkdir target && cd target # - create build target directory 89 | cmake -DCMAKE_BUILD_TYPE=Upstream -DSOURCE_WLC=ON .. # - run CMake 90 | make # - compile 91 | 92 | # You can now run 93 | ./src/orbment 94 | 95 | If built in Debug mode, ``./plugins`` is added to plugin search paths, and you can run ``orbment`` straight 96 | from the build directory and it will find the core plugins. This is useful for testing development version, 97 | bootstrapping or developing plugins. 98 | 99 | PACKAGING 100 | --------- 101 | 102 | For now you can look at the `AUR recipe `_ for a example. 103 | 104 | CONTRIBUTING 105 | ------------ 106 | 107 | See the `CONTRIBUTING `_ for more information. 108 | 109 | SIMILAR SOFTWARE 110 | ---------------- 111 | 112 | - `Velox `_ - Tiling wayland compositor based on swc 113 | - `Waysome `_ - Scriptable wayland compositor 114 | - `sway `_ - i3-compatible window manager for Wayland 115 | -------------------------------------------------------------------------------- /include/orbment/defines.h: -------------------------------------------------------------------------------- 1 | #ifndef __orbment_defines_h__ 2 | #define __orbment_defines_h__ 3 | 4 | #if __GNUC__ 5 | # define PLOG_ATTR(x, y) __attribute__((format(printf, x, y))) 6 | # define PNONULL __attribute__((nonnull)) 7 | # define PNONULLV(...) __attribute__((nonnull(__VA_ARGS__))) 8 | # define PPURE __attribute__((pure)) 9 | # define PCONST __attribute__((const)) 10 | #else 11 | # define PLOG_ATTR(x, y) 12 | # define PNONULL 13 | # define PNONULLV 14 | # define PPURE 15 | # define PCONST 16 | #endif 17 | 18 | #endif /* __orbment_defines_h__ */ 19 | -------------------------------------------------------------------------------- /include/orbment/plugin.h: -------------------------------------------------------------------------------- 1 | #ifndef __orbment_plugin_h__ 2 | #define __orbment_plugin_h__ 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | /** 10 | * Struct abstracting exported method from plugin. 11 | * Do not use this struct directly, but instead the REGISTER_METHOD macro. 12 | */ 13 | struct method { 14 | void *function; 15 | struct method_info { 16 | const char *name, *signature; 17 | } info; 18 | bool deprecated; 19 | }; 20 | 21 | /** 22 | * Use this instead of pointers when passing functions between plugins. 23 | * The other plugin can sanity check the function signature. 24 | * See also the FUN macro. 25 | */ 26 | struct function { 27 | void *function; 28 | const char *signature; 29 | }; 30 | 31 | /** 32 | * Struct which statically allocated reference should be returned by plugin_register function. 33 | * Used for filling information and functionality of the plugin. 34 | * 35 | * XXX: Set some good standard for plugins that provide common functionaly. 36 | * For example status bar plugins should provide 'bar' plugin or something. 37 | * Plugin that depends on OpenGL backend in wlc (for example desktop cube) should require on 'opengl' meta-plugin. 38 | */ 39 | struct plugin_info { 40 | /** 41 | * Name of the plugin. 42 | */ 43 | const char *name; 44 | 45 | /** 46 | * Description of the plugin. 47 | */ 48 | const char *description; 49 | 50 | /** 51 | * Version of the plugin. 52 | * XXX: Semantic version? 53 | * XXX: We don't relly use this information for anything yet. Should we? Just for metadata right now. 54 | */ 55 | const char *version; 56 | 57 | /** 58 | * Use REGISTER_METHOD macro to register each method the plugin exports for others. 59 | * Zero-terminated array of methods (struct method) 60 | */ 61 | const struct method *methods; 62 | 63 | /** 64 | * Provides given plugins in addition to the name of this plugin. 65 | * May be NULL, if no additional provides are needed. 66 | * Zero-terminated array of pointers to char arrays. 67 | */ 68 | const char **provides; 69 | 70 | /** 71 | * Conflicts with the given plugins in addition to the name of this plugin. 72 | * May be NULL, if no additional conflicts are needed. 73 | * Zero-terminated array of pointers to char arrays. 74 | */ 75 | const char **conflicts; 76 | 77 | /** 78 | * Requires the given plugins. (Hard dependency) 79 | * The plugin will be loaded after required plugins, if they are available. 80 | * May be NULL, if no requires are needed. 81 | * Zero-terminated array of pointers to char arrays. 82 | */ 83 | const char **requires; 84 | 85 | /** 86 | * Loads after the given plugins. (Optional dependency) 87 | * The plugin will be loaded after the optional plugins, regardless if they are available. 88 | * May be NULL, if no requires are needed. 89 | * Zero-terminated array of pointers to char arrays. 90 | */ 91 | const char **after; 92 | 93 | /** 94 | * Groups the plugin belongs to. Plugins may require or load after group. 95 | * Group name can be same as plugin name. 96 | * This will not cause recursion if plugin belonging to the group also requires similar named plugin. 97 | * This is useful when providing api, which other plugins can extend. (ex. compressor) 98 | * It makes sure the api and the extensions are loaded on require. 99 | * May be NULL, if no groups are needed 100 | * Zero-terminated array of pointers to char arrays. 101 | */ 102 | const char **groups; 103 | }; 104 | 105 | /** 106 | * Plugin handle. 107 | */ 108 | typedef size_t plugin_h; 109 | 110 | /** 111 | * Type of log message. 112 | */ 113 | enum plugin_log_type { 114 | PLOG_INFO, 115 | PLOG_WARN, 116 | PLOG_ERROR, 117 | }; 118 | 119 | /** 120 | * Logging utility. 121 | */ 122 | PNONULLV(3) PLOG_ATTR(3, 4) void plog(plugin_h self, enum plugin_log_type, const char *fmt, ...); 123 | 124 | /** 125 | * Imports plugin with name. 126 | * Returns plugin handle, 0 if no such plugin. 127 | */ 128 | PNONULL plugin_h import_plugin(plugin_h self, const char *name); 129 | 130 | /** 131 | * Check if plugin has zero-terminated list of methods. 132 | * Returns true, if all methods with the signatures exist. 133 | * 134 | * If the method exists in plugin, import_method for that method will always succeeed. 135 | * Thus this method can be used to check all hard depencies to another plugin. 136 | */ 137 | PNONULL bool has_methods(plugin_h self, plugin_h plugin, const struct method_info *methods); 138 | 139 | /** 140 | * Imports a method from another plugin. 141 | * Returns NULL if signature does not match with loaded plugin version, or method is not found. 142 | */ 143 | PNONULL void* import_method(plugin_h self, plugin_h plugin, const char *name, const char *signature); 144 | 145 | /** 146 | * Helper macro for registering methods. 147 | * 148 | * Type literals: 149 | * XXX: Extendable, however we should have good base that does not change 150 | * 151 | * u8 | uint8_t 152 | * i8 | int8_t 153 | * u16 | uint16_t 154 | * i16 | int16_t 155 | * u32 | uint32_t 156 | * i32 | int32_t 157 | * u64 | uint64_t 158 | * i64 | int64_t 159 | * up | uintptr_t 160 | * ip | intptr_t 161 | * sz | size_t 162 | * f | float 163 | * d | double 164 | * c | char 165 | * b | bool 166 | * e | enum 167 | * * | pointer 168 | * [] | array, you can use element count inside as C allows compile time checks for element sizes. 169 | * v | void 170 | * fun | function, see function struct above. 171 | * h | opaque pod type (wlc_handle, plugin_h, etc...) 172 | * 173 | * Written as: 174 | * ()| 175 | * 176 | * Arguments are separated with colons for readibility. 177 | * 178 | * Example: 179 | * *(c[12],i16,i32)|1 180 | * 181 | * Returns pointer, takes char array with size of 12, signed short and signed int. 182 | * For pointers to POD arrays use [] without number instead of *. eg. char* would be c[]. 183 | * For pointers to POD types use *, eg. size_t* would be sz*. 184 | * 185 | * The ABI version must be incremented for each revision of the function where meaning of input data changed. 186 | * This is most important with pointers used as references to data structures. 187 | * If the data structure changed, you should raise ABI version. 188 | * 189 | * This protects against ABI changes and allows backwards compatibility to some degree. 190 | * 191 | * The plugins however most likely will always be compiled with some sort of helper tool and exist in 192 | * central repository, so ABI breakages should not be common, but still something to think about. 193 | * 194 | * Intentional bad plugins can still cause havoc, but these are the tradeoffs for the power of native shared libraries, 195 | * without using sandboxing and IPC. I think this is not a problem when plugins are available and easily compiled 196 | * from central repository where bad plugins can be fixed or removed. 197 | * 198 | * Of course, it is always good idea to check source of your plugins. 199 | */ 200 | #define REGISTER_METHOD(fun, sig) { .info = { .name = #fun, .signature = sig }, .function = fun, .deprecated = false } 201 | 202 | /** 203 | * Same as above, but marks as deprecated. 204 | */ 205 | #define REGISTER_DEPRECATED(fun, sig) { .info = { .name = #fun, .signature = sig }, .function = fun, .deprecated = true } 206 | 207 | /** 208 | * Helper macro for filling method_info struct, mainly for has_methods function. 209 | */ 210 | #define METHOD(fun, sig) { .name = fun, .signature = sig } 211 | 212 | /** 213 | * Helper macro for filling function struct, use when passing functions to other plugins. 214 | */ 215 | #define FUN(fun, sig) &(struct function){ .function = fun, .signature = sig } 216 | 217 | #endif /* __orbment_plugin_h__ */ 218 | -------------------------------------------------------------------------------- /lib/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_compile_options(-fvisibility=hidden) 2 | 3 | set(CHCK_BUILD_STATIC ON CACHE STRING "Build chck statically if not found systemwide" FORCE) 4 | set(CHCK_BUILD_TESTS OFF CACHE STRING "Do not build chck tests" FORCE) 5 | add_subproject(chck Chck) 6 | 7 | # plugins will need wlc to be dynamic 8 | set(WLC_BUILD_STATIC OFF CACHE STRING "Build wlc statically if not found systemwide" FORCE) 9 | set(WLC_BUILD_EXAMPLES OFF CACHE STRING "Do not build wlc examples" FORCE) 10 | set(WLC_BUILD_TESTS OFF CACHE STRING "Do not build wlc tests" FORCE) 11 | add_subproject(wlc Wlc) 12 | 13 | set(INIHCK_BUILD_STATIC ON CACHE STRING "Build inihck statically if not found systemwide" FORCE) 14 | set(INIHCK_BUILD_TESTS OFF CACHE STRING "Do not build inihck tests" FORCE) 15 | add_subproject(inihck Inihck) 16 | -------------------------------------------------------------------------------- /orbment.1.in: -------------------------------------------------------------------------------- 1 | .TH ORBMENT 1 2015-09-30 2 | 3 | .SH NAME 4 | 5 | orbment \- dynamic wayland compositor 6 | 7 | .SH SYNOPSIS 8 | 9 | .SY orbment 10 | [options] 11 | 12 | .SH DESCRIPTION 13 | 14 | Orbment is a dynamic window manager and compositor for Wayland built using the 15 | wlc library. 16 | 17 | .SH OPTIONS 18 | 19 | .B \-\-log 20 | .I FILE 21 | .RS 22 | File in which the logging output is captured. 23 | .RE 24 | 25 | .SH KEYBINDINGS 26 | 27 | N.B. These are a tentative set of keybindings created specifically to provide 28 | basic usage until more flexible mechanisms are added. 29 | 30 | Some plugins can also add their own keybinds. These additional keybinds are 31 | displayed in the log. (See \fI\-\-log) 32 | 33 | .B mod-return 34 | .RS 35 | Opens a new terminal emulator client. (The client can be set via the 36 | \fBTERMINAL\fR environment otherwise orbment will fallback on 37 | \fBweston-terminal\fR). 38 | .RE 39 | 40 | .B mod-p 41 | .RS 42 | Opens \fBbemenu-run\fR (similar to \fBdmenu_run\fR) for starting applications. 43 | .RE 44 | 45 | .B mod-w 46 | .RS 47 | Rotates through available layouts. 48 | .RE 49 | 50 | .B mod-l 51 | .RS 52 | Rotates focus through available outputs. 53 | .RE 54 | 55 | .B mod-j, k 56 | .RS 57 | Rotates focus through clients. 58 | .RE 59 | 60 | .B mod-f 61 | .RS 62 | Toggles fullscreen for the focused client. 63 | .RE 64 | 65 | .B mod-[1..n] 66 | .RS 67 | Selects the \fInth\fP workspace, currently limited to 10. 68 | .RE 69 | 70 | .B mod-[F1..F10] 71 | .RS 72 | Moves the focused client to the corresponding workspace. 73 | .RE 74 | 75 | .B mod-z, x, c 76 | .RS 77 | Moves focused client to outputs 1, 2 and 3 respectively. 78 | .RE 79 | 80 | .B mod-h 81 | .RS 82 | Cycles the focused client. 83 | .RE 84 | 85 | .B mod-q 86 | .RS 87 | Closes the focused client. 88 | .RE 89 | 90 | .B mod-i, o 91 | .RS 92 | Shifts the cut of the nmaster layout to shrink or expand the view. 93 | .RE 94 | 95 | .B mod-s 96 | .RS 97 | Takes a screenshot in PNG format and stores it in the directory \fBorbment\fR 98 | was started from. 99 | .RE 100 | 101 | .B mod-escape 102 | .RS 103 | Quits \fBorbment\fR. 104 | .RE 105 | 106 | .SH ENVIRONMENT 107 | 108 | .TP 109 | .B TERMINAL 110 | .RS 111 | \fBOrbment\fR will honor this environment variable when starting a new terminal 112 | emulator. If this is not set it will fall back on \fBweston-terminal\fR. 113 | .RE 114 | 115 | .B WLC_SHM 116 | .RS 117 | Takes a boolean \fI0\fR or \fI1\fR, true forces EGL clients to fallback to shm. 118 | (Default: \fI0\fR) 119 | .RE 120 | 121 | .B WLC_DEBUG 122 | .RS 123 | Takes a comma-separated list of values which enable corresponding debug section 124 | in wlc. These include \fIrender\fR, \fIrender-loop\fR, \fIfocus\fR and 125 | \fIxwm\fR. 126 | .RE 127 | 128 | .B WLC_DRM_DEVICE 129 | .RS 130 | The DRM device used when running under the DRM backend. (Default: \fIcard0\fR) 131 | .RE 132 | 133 | .B WLC_XWAYLAND 134 | .RS 135 | Takes a boolean \fI0\fR or \fI1\fR which disables or enables the use of 136 | xwayland respectively. (Default: \fI1\fR) 137 | .RE 138 | 139 | .B WAYLAND_DEBUG 140 | .RS 141 | Enables message tracing in libwayland. Possible values are \fI1\fR, 142 | \fIclient\fR, \fIserver\fR. 143 | .RE 144 | 145 | .B XKB_DEFAULT_LAYOUT 146 | .RS 147 | This corresponds to the layout you might use in X11 such as \fBgb\fR. Use this 148 | if you want to select a non-us keyboard layout. 149 | .RE 150 | 151 | .B XKB_DEFAULT_OPTIONS 152 | .RS 153 | Sets the keyboard options. The syntax is the same (comma-separated values) as 154 | within an \fIxorg.conf\fR or when used via \%\fBsetxkbmap -option\fR. For 155 | example \%\fBcompose:ralt,ctrl:nocaps\fR would set \fIralt\fR to the compose 156 | key and cause the \fICaps Lock\fR key to mimic \fIctrl\fR. 157 | .RE 158 | 159 | .B XKB_DEFAULT_VARIANT 160 | 161 | .B XKB_DEFAULT_RULES 162 | 163 | .B XKB_DEFAULT_MODEL 164 | 165 | .SH FILES 166 | 167 | .B Orbment 168 | will seek out any plugins in the following locations: 169 | .RS 170 | 171 | .IR XDG_DATA_HOME/orbment/plugins " (Fallback: "HOME/.local/share/orbment/plugins ) 172 | 173 | .I \fI@ORBMENT_PLUGINS_FULL_PATH@\fR 174 | .RE 175 | 176 | .SH SEE ALSO 177 | 178 | .BR xkbcommon , 179 | .BR wayland , 180 | .BR weston 181 | 182 | .SH AUTHORS 183 | 184 | .MT http://cloudef.pw 185 | Jari Vetoniemi 186 | .ME 187 | -------------------------------------------------------------------------------- /plugins/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | set(plugins 2 | keybind 3 | layout 4 | compressor 5 | core-input 6 | core-functionality 7 | core-layouts 8 | core-screenshot 9 | core-dpms 10 | configuration 11 | autostart 12 | crappy-borders 13 | ) 14 | 15 | include_directories( 16 | ${WLC_INCLUDE_DIRS} 17 | ${CHCK_INCLUDE_DIRS} 18 | ${PROJECT_SOURCE_DIR}/include 19 | ${CMAKE_BINARY_DIR}/src # for config.h 20 | ${CMAKE_CURRENT_SOURCE_DIR} # for common.h 21 | ) 22 | 23 | macro(add_plugins) 24 | foreach (plugin ${ARGN}) 25 | set_target_properties(${plugin} PROPERTIES PREFIX "") 26 | endforeach () 27 | install(TARGETS ${ARGV} DESTINATION ${ORBMENT_PLUGINS_PATH}) 28 | endmacro() 29 | 30 | set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/plugins) 31 | foreach (plugin ${plugins}) 32 | add_subdirectory(${plugin}) 33 | endforeach (plugin) 34 | -------------------------------------------------------------------------------- /plugins/autostart/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_library(orbment-plugin-autostart MODULE autostart.c) 2 | target_link_libraries(orbment-plugin-autostart PRIVATE ${ORBMENT_LIBRARIES} ${CHCK_LIBRARIES}) 3 | add_plugins(orbment-plugin-autostart) 4 | -------------------------------------------------------------------------------- /plugins/autostart/autostart.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include "config.h" 9 | #include 10 | 11 | static bool (*configuration_get)(const char *key, const char type, void *value_out); 12 | static bool (*add_hook)(plugin_h, const char *name, const struct function*); 13 | 14 | static struct { 15 | plugin_h self; 16 | } plugin; 17 | 18 | static void 19 | do_autostart(void) 20 | { 21 | struct chck_string key = {0}, command = {0}; 22 | struct chck_iter_pool argv; 23 | 24 | if (!chck_iter_pool(&argv, 4, 4, sizeof(char*))) 25 | return; 26 | 27 | for (uint32_t i = 0; ; i++) { 28 | if (!chck_string_set_format(&key, "/autostart/%u", i)) 29 | break; 30 | 31 | const char *command_cstr; 32 | if (!configuration_get(key.data, 's', &command_cstr)) 33 | break; 34 | 35 | if (!chck_string_set_cstr(&command, command_cstr, true)) 36 | break; 37 | 38 | char *t; 39 | size_t len; 40 | const char *state = NULL; 41 | while ((t = (char*)chck_cstr_tokenize_quoted(command.data, &len, " ", "\"'", &state))) { 42 | chck_iter_pool_push_back(&argv, &t); 43 | t[len] = 0; /* replaces each token with \0 */ 44 | } 45 | 46 | const char *null = NULL; 47 | chck_iter_pool_push_back(&argv, &null); /* NULL indicates end of the array */ 48 | plog(plugin.self, PLOG_INFO, "spawning: %s", command_cstr); 49 | wlc_exec(command.data, chck_iter_pool_to_c_array(&argv, NULL)); 50 | chck_iter_pool_empty(&argv); 51 | } 52 | 53 | chck_string_release(&key); 54 | chck_string_release(&command); 55 | chck_iter_pool_release(&argv); 56 | } 57 | 58 | #pragma GCC diagnostic ignored "-Wmissing-prototypes" 59 | 60 | bool 61 | plugin_init(plugin_h self) 62 | { 63 | plugin.self = self; 64 | 65 | plugin_h orbment, configuration; 66 | if (!(orbment = import_plugin(self, "orbment")) || 67 | !(configuration = import_plugin(self, "configuration"))) 68 | return false; 69 | 70 | if (!(add_hook = import_method(self, orbment, "add_hook", "b(h,c[],fun)|1")) || 71 | !(configuration_get = import_method(self, configuration, "get", "b(c[],c,v)|1"))) 72 | return false; 73 | 74 | return add_hook(self, "compositor.ready", FUN(do_autostart, "v(v)|1")); 75 | } 76 | 77 | PCONST const struct plugin_info* 78 | plugin_register(void) 79 | { 80 | static const char *requires[] = { 81 | "configuration", 82 | NULL, 83 | }; 84 | 85 | static const struct plugin_info info = { 86 | .name = "autostart", 87 | .description = "Launch programs on startup.", 88 | .version = VERSION, 89 | .requires = requires 90 | }; 91 | 92 | return &info; 93 | } 94 | -------------------------------------------------------------------------------- /plugins/common.h: -------------------------------------------------------------------------------- 1 | #ifndef __orbment_common_h__ 2 | #define __orbment_common_h__ 3 | 4 | #include 5 | 6 | // XXX: hack 7 | enum { 8 | BIT_BEMENU = 1<<5, 9 | }; 10 | 11 | enum direction { 12 | NEXT, 13 | PREV, 14 | }; 15 | 16 | static inline bool 17 | is_or(wlc_handle view) 18 | { 19 | const uint32_t type = wlc_view_get_type(view); 20 | return (type & WLC_BIT_OVERRIDE_REDIRECT) || (type & BIT_BEMENU); 21 | } 22 | 23 | static inline bool 24 | is_popup(wlc_handle view) 25 | { 26 | const uint32_t type = wlc_view_get_type(view); 27 | return (type & WLC_BIT_POPUP); 28 | } 29 | 30 | static inline bool 31 | is_managed(wlc_handle view) 32 | { 33 | const uint32_t type = wlc_view_get_type(view); 34 | return !(type & WLC_BIT_UNMANAGED) && !(type & WLC_BIT_SPLASH); 35 | } 36 | 37 | static inline bool 38 | is_modal(wlc_handle view) 39 | { 40 | const uint32_t type = wlc_view_get_type(view); 41 | return (type & WLC_BIT_MODAL); 42 | } 43 | 44 | static inline bool 45 | is_tiled(wlc_handle view) 46 | { 47 | const uint32_t state = wlc_view_get_state(view); 48 | return !(state & WLC_BIT_FULLSCREEN) && !wlc_view_get_parent(view) && is_managed(view) && !is_or(view) && !is_modal(view) && !is_popup(view); 49 | } 50 | 51 | #endif /* __orbment_common_h__ */ 52 | -------------------------------------------------------------------------------- /plugins/compressor/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_library(orbment-plugin-compressor MODULE compressor.c) 2 | target_link_libraries(orbment-plugin-compressor PRIVATE ${ORBMENT_LIBRARIES} ${CHCK_LIBRARIES}) 3 | add_plugins(orbment-plugin-compressor) 4 | 5 | set(compressors ppm) 6 | set(ppm_lib ${CHCK_LIBRARIES}) 7 | 8 | find_package(PNG) 9 | if (PNG_FOUND) 10 | list(APPEND compressors png) 11 | set(png_lib ${PNG_LIBRARIES} ${CHCK_LIBRARIES}) 12 | set(png_inc ${PNG_INCLUDES}) 13 | endif () 14 | 15 | foreach (c ${compressors}) 16 | include_directories(${${c}_inc}) 17 | add_library(orbment-plugin-compressor-${c} MODULE compressor-${c}.c) 18 | target_link_libraries(orbment-plugin-compressor-${c} PRIVATE ${${c}_lib} ${ORBMENT_LIBRARIES}) 19 | add_plugins(orbment-plugin-compressor-${c}) 20 | endforeach () 21 | 22 | -------------------------------------------------------------------------------- /plugins/compressor/compressor-png.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include "config.h" 8 | 9 | static bool (*add_compressor)(plugin_h, const char *type, const char *name, const char *ext, const struct function*); 10 | 11 | static void 12 | write_png(png_structp p, png_bytep data, png_size_t length) 13 | { 14 | assert(p); 15 | struct chck_buffer *buf = (struct chck_buffer*)png_get_io_ptr(p); 16 | assert(buf); 17 | chck_buffer_write(data, 1, length, buf); 18 | } 19 | 20 | static void 21 | set_vertically_flipped_rgba_rows(const struct wlc_size *size, uint8_t *rgba, png_bytepp rows) 22 | { 23 | // XXX: At least under OpenGL backend rgba data will be upside down 24 | for (size_t y = 0; y < size->h; ++y) 25 | rows[y] = rgba + ((size->h - 1) - y) * size->w * 4; 26 | } 27 | 28 | static uint8_t* 29 | compress_png(const struct wlc_size *size, uint8_t *rgba, size_t *out_size) 30 | { 31 | if (out_size) 32 | *out_size = 0; 33 | 34 | if (!size || !size->w || !size->h) 35 | return NULL; 36 | 37 | png_structp p; 38 | if (!(p = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL))) 39 | return NULL; 40 | 41 | png_infop info; 42 | if (!(info = png_create_info_struct(p))) 43 | goto error0; 44 | 45 | struct chck_buffer buf; 46 | if (!(chck_buffer(&buf, 8192, CHCK_ENDIANESS_LITTLE))) 47 | goto error1; 48 | 49 | buf.step = 8192; 50 | 51 | png_bytepp rows; 52 | if (!(rows = chck_malloc_mul_of(size->h, sizeof(png_bytep)))) 53 | goto error2; 54 | 55 | png_set_IHDR(p, info, size->w, size->h, 8, 56 | PNG_COLOR_TYPE_RGBA, 57 | PNG_INTERLACE_NONE, 58 | PNG_COMPRESSION_TYPE_DEFAULT, 59 | PNG_FILTER_TYPE_DEFAULT); 60 | 61 | set_vertically_flipped_rgba_rows(size, rgba, rows); 62 | 63 | png_set_rows(p, info, rows); 64 | png_set_write_fn(p, &buf, write_png, NULL); 65 | png_write_png(p, info, PNG_TRANSFORM_IDENTITY, NULL); 66 | free(rows); 67 | png_destroy_info_struct(p, &info); 68 | png_destroy_write_struct(&p, NULL); 69 | 70 | void *compressed = buf.buffer; 71 | buf.copied = false; 72 | 73 | if (out_size) 74 | *out_size = buf.curpos - buf.buffer; 75 | 76 | chck_buffer_release(&buf); 77 | return compressed; 78 | 79 | error2: 80 | chck_buffer_release(&buf); 81 | error1: 82 | png_destroy_info_struct(p, &info); 83 | error0: 84 | png_destroy_write_struct(&p, NULL); 85 | return NULL; 86 | } 87 | 88 | #pragma GCC diagnostic ignored "-Wmissing-prototypes" 89 | 90 | bool 91 | plugin_init(plugin_h self) 92 | { 93 | plugin_h compressor; 94 | if (!(compressor = import_plugin(self, "compressor"))) 95 | return false; 96 | 97 | if (!(add_compressor = import_method(self, compressor, "add_compressor", "b(h,c[],c[],c[],fun)|1"))) 98 | return false; 99 | 100 | return add_compressor(self, "image", "png", "png", FUN(compress_png, "u8[](p,u8[],sz*)|1")); 101 | } 102 | 103 | PCONST const struct plugin_info* 104 | plugin_register(void) 105 | { 106 | static const char *requires[] = { 107 | "compressor", 108 | NULL, 109 | }; 110 | 111 | static const char *groups[] = { 112 | "compressor", 113 | NULL, 114 | }; 115 | 116 | static const struct plugin_info info = { 117 | .name = "compressor-png", 118 | .description = "Compression to png image format.", 119 | .version = VERSION, 120 | .requires = requires, 121 | .groups = groups, 122 | }; 123 | 124 | return &info; 125 | } 126 | -------------------------------------------------------------------------------- /plugins/compressor/compressor-ppm.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include "config.h" 7 | 8 | static bool (*add_compressor)(plugin_h, const char *type, const char *name, const char *ext, const struct function*); 9 | 10 | static void 11 | flip_rgb_vertically(const struct wlc_size *size, uint8_t *rgb) 12 | { 13 | // XXX: At least under OpenGL backend rgba data will be upside down 14 | for (uint32_t i = 0; i * 2 < size->h; ++i) { 15 | uint32_t o = i * size->w * 3; 16 | uint32_t r = (size->h - 1 - i) * size->w * 3; 17 | for (uint32_t i2 = size->w * 3; i2 > 0; --i2, ++o, ++r) { 18 | uint8_t temp = rgb[o]; 19 | rgb[o] = rgb[r]; 20 | rgb[r] = temp; 21 | } 22 | } 23 | } 24 | 25 | static uint8_t* 26 | compress_ppm(const struct wlc_size *size, uint8_t *rgba, size_t *out_size) 27 | { 28 | if (out_size) 29 | *out_size = 0; 30 | 31 | if (!size || !size->w || !size->h) 32 | return NULL; 33 | 34 | size_t sz; 35 | uint8_t *rgb; 36 | if (chck_mul_ofsz(size->w, size->h, &sz) || !(rgb = calloc(3, sz))) 37 | return NULL; 38 | 39 | for (uint32_t i = 0, c = 0; i < size->w * size->h * 4; i += 4, c += 3) 40 | memcpy(rgb + c, rgba + i, 3); 41 | 42 | flip_rgb_vertically(size, rgb); 43 | 44 | struct chck_buffer buf; 45 | if (!chck_buffer(&buf, size->w * size->h * 3, CHCK_ENDIANESS_LITTLE)) 46 | goto error0; 47 | 48 | chck_buffer_write_format(&buf, "P6\n%d %d\n255\n", size->w, size->h); 49 | chck_buffer_write(rgb, 1, size->w * size->h * 3, &buf); 50 | free(rgb); 51 | 52 | void *compressed = buf.buffer; 53 | buf.copied = false; 54 | 55 | if (out_size) 56 | *out_size = buf.curpos - buf.buffer; 57 | 58 | chck_buffer_release(&buf); 59 | return compressed; 60 | 61 | error0: 62 | free(rgb); 63 | return NULL; 64 | } 65 | 66 | #pragma GCC diagnostic ignored "-Wmissing-prototypes" 67 | 68 | bool 69 | plugin_init(plugin_h self) 70 | { 71 | plugin_h compressor; 72 | if (!(compressor = import_plugin(self, "compressor"))) 73 | return false; 74 | 75 | if (!(add_compressor = import_method(self, compressor, "add_compressor", "b(h,c[],c[],c[],fun)|1"))) 76 | return false; 77 | 78 | return add_compressor(self, "image", "ppm", "ppm", FUN(compress_ppm, "u8[](p,u8[],sz*)|1")); 79 | } 80 | 81 | PCONST const struct plugin_info* 82 | plugin_register(void) 83 | { 84 | static const char *requires[] = { 85 | "compressor", 86 | NULL, 87 | }; 88 | 89 | static const char *groups[] = { 90 | "compressor", 91 | NULL, 92 | }; 93 | 94 | static const struct plugin_info info = { 95 | .name = "compressor-ppm", 96 | .description = "Compression to ppm image format.", 97 | .version = VERSION, 98 | .requires = requires, 99 | .groups = groups, 100 | }; 101 | 102 | return &info; 103 | } 104 | -------------------------------------------------------------------------------- /plugins/compressor/compressor.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include "config.h" 5 | 6 | static bool (*add_hook)(plugin_h, const char *name, const struct function*); 7 | 8 | struct compressor { 9 | const char *name; 10 | const char *ext; 11 | void *function; 12 | }; 13 | 14 | enum type { 15 | IMAGE, 16 | LAST 17 | }; 18 | 19 | static const char *signatures[LAST] = { 20 | "u8[](p,u8[],sz*)|1", // IMAGE 21 | }; 22 | 23 | static struct { 24 | struct chck_iter_pool compressors[LAST]; 25 | struct chck_iter_pool owners[LAST]; 26 | plugin_h self; 27 | } plugin; 28 | 29 | static enum type 30 | type_for_string(const char *type) 31 | { 32 | struct { 33 | const char *name; 34 | enum type type; 35 | } map[] = { 36 | { "image", IMAGE }, 37 | { NULL, LAST }, 38 | }; 39 | 40 | for (uint32_t i = 0; map[i].name; ++i) { 41 | if (chck_cstreq(type, map[i].name)) 42 | return map[i].type; 43 | } 44 | 45 | return LAST; 46 | } 47 | 48 | static bool 49 | compressor_exists(struct chck_iter_pool *pool, const char *name) 50 | { 51 | const struct compressor *c; 52 | chck_iter_pool_for_each(pool, c) 53 | if (chck_cstreq(name, c->name)) 54 | return true; 55 | return false; 56 | } 57 | 58 | static bool 59 | add_compressor(plugin_h caller, const char *type, const char *name, const char *ext, const struct function *fun) 60 | { 61 | if (!name || !fun || !caller) 62 | return false; 63 | 64 | enum type t; 65 | if ((t = type_for_string(type)) == LAST) { 66 | plog(plugin.self, PLOG_WARN, "Invalid type provided for '%s compressor'. (%s)", name, type); 67 | return false; 68 | } 69 | 70 | if (!chck_cstreq(fun->signature, signatures[t])) { 71 | plog(plugin.self, PLOG_WARN, "Wrong signature provided for '%s compressor' function. (%s != %s)", name, signatures[t], fun->signature); 72 | return false; 73 | } 74 | 75 | if (chck_cstr_starts_with(ext, ".")) { 76 | plog(plugin.self, PLOG_WARN, "Invalid extension for '%s compressor'. (%s) (remove leading dot)", name, ext); 77 | return false; 78 | } 79 | 80 | if (compressor_exists(&plugin.compressors[t], name)) { 81 | plog(plugin.self, PLOG_WARN, "Compressor with name '%s' already exists.", name); 82 | } 83 | 84 | struct compressor compressor = { 85 | .name = name, 86 | .ext = ext, 87 | .function = fun->function, 88 | }; 89 | 90 | if (!chck_iter_pool_push_back(&plugin.compressors[t], &compressor)) 91 | return false; 92 | 93 | if (!chck_iter_pool_push_back(&plugin.owners[t], &caller)) 94 | goto error0; 95 | 96 | return true; 97 | 98 | error0: 99 | chck_iter_pool_remove(&plugin.compressors[t], plugin.compressors[t].items.count - 1); 100 | return false; 101 | } 102 | 103 | static void 104 | remove_compressor(plugin_h caller, const char *type, const char *name) 105 | { 106 | enum type t; 107 | if ((t = type_for_string(type)) == LAST) { 108 | plog(plugin.self, PLOG_WARN, "Tried to remove '%s compressor' with invalid type. (%s)", name, type); 109 | return; 110 | } 111 | 112 | struct compressor *c; 113 | chck_iter_pool_for_each(&plugin.compressors[t], c) { 114 | plugin_h *owner = chck_iter_pool_get(&plugin.owners[t], _I - 1); 115 | if (caller != *owner || !chck_cstreq(c->name, name)) 116 | continue; 117 | 118 | chck_iter_pool_remove(&plugin.compressors[t], _I - 1); 119 | chck_iter_pool_remove(&plugin.owners[t], _I - 1); 120 | break; 121 | } 122 | } 123 | 124 | static void 125 | remove_compressors_for_plugin(plugin_h caller) 126 | { 127 | for (size_t t = 0; t < LAST; ++t) { 128 | plugin_h *owner; 129 | chck_iter_pool_for_each(&plugin.owners[t], owner) { 130 | if (caller != *owner) 131 | continue; 132 | 133 | chck_iter_pool_remove(&plugin.compressors[t], _I - 1); 134 | chck_iter_pool_remove(&plugin.owners[t], _I - 1); 135 | --_I; 136 | } 137 | } 138 | } 139 | 140 | static struct compressor* 141 | list_compressors(const char *type, const char *stsign, const char *funsign, size_t *out_memb) 142 | { 143 | if (out_memb) 144 | *out_memb = 0; 145 | 146 | enum type t; 147 | if ((t = type_for_string(type)) == LAST) { 148 | plog(plugin.self, PLOG_WARN, "Tried to list compressors with invalid type. (%s)", type); 149 | return NULL; 150 | } 151 | 152 | static const char *signature = "c[],c[],*|1"; 153 | if (!chck_cstreq(stsign, signature)) { 154 | plog(plugin.self, PLOG_WARN, "Wrong struct signature. (%s != %s)", signature, stsign); 155 | return NULL; 156 | } 157 | 158 | if (!chck_cstreq(funsign, signatures[t])) { 159 | plog(plugin.self, PLOG_WARN, "Wrong function signature. (%s != %s)", signatures[t], funsign); 160 | return NULL; 161 | } 162 | 163 | return chck_iter_pool_to_c_array(&plugin.compressors[t], out_memb); 164 | } 165 | 166 | static void 167 | plugin_deloaded(plugin_h ph) 168 | { 169 | remove_compressors_for_plugin(ph); 170 | } 171 | 172 | #pragma GCC diagnostic ignored "-Wmissing-prototypes" 173 | 174 | void 175 | plugin_deinit(plugin_h self) 176 | { 177 | (void)self; 178 | 179 | for (uint32_t i = 0; i < LAST; ++i) { 180 | chck_iter_pool_release(&plugin.compressors[i]); 181 | chck_iter_pool_release(&plugin.owners[i]); 182 | } 183 | } 184 | 185 | bool 186 | plugin_init(plugin_h self) 187 | { 188 | plugin.self = self; 189 | 190 | plugin_h orbment; 191 | if (!(orbment = import_plugin(self, "orbment"))) 192 | return false; 193 | 194 | for (uint32_t i = 0; i < LAST; ++i) { 195 | if (!chck_iter_pool(&plugin.compressors[i], 1, 0, sizeof(struct compressor)) || 196 | !chck_iter_pool(&plugin.owners[i], 1, 0, sizeof(plugin_h))) 197 | return false; 198 | } 199 | 200 | if (!(add_hook = import_method(self, orbment, "add_hook", "b(h,c[],fun)|1"))) 201 | return false; 202 | 203 | return (add_hook(self, "plugin.deloaded", FUN(plugin_deloaded, "v(h)|1"))); 204 | } 205 | 206 | PCONST const struct plugin_info* 207 | plugin_register(void) 208 | { 209 | static const struct method methods[] = { 210 | REGISTER_METHOD(add_compressor, "b(h,c[],c[],c[],fun)|1"), 211 | REGISTER_METHOD(remove_compressor, "v(h,c[],c[])|1"), 212 | REGISTER_METHOD(list_compressors, "*(c[],c[],c[],sz*)|1"), 213 | {0}, 214 | }; 215 | 216 | static const char *groups[] = { 217 | "compressor", 218 | NULL, 219 | }; 220 | 221 | static const struct plugin_info info = { 222 | .name = "compressor", 223 | .description = "Compression api.", 224 | .version = VERSION, 225 | .methods = methods, 226 | .groups = groups, 227 | }; 228 | 229 | return &info; 230 | } 231 | -------------------------------------------------------------------------------- /plugins/configuration/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_library(orbment-plugin-configuration MODULE configuration.c) 2 | target_link_libraries(orbment-plugin-configuration PRIVATE ${ORBMENT_LIBRARIES} ${CHCK_LIBRARIES}) 3 | add_plugins(orbment-plugin-configuration) 4 | 5 | add_definitions(${INIHCK_DEFINITIONS}) 6 | include_directories(${INIHCK_INCLUDE_DIRS}) 7 | add_library(orbment-plugin-configuration-ini MODULE configuration-ini.c) 8 | target_link_libraries(orbment-plugin-configuration-ini PRIVATE ${ORBMENT_LIBRARIES} ${INIHCK_LIBRARIES}) 9 | add_plugins(orbment-plugin-configuration-ini) 10 | -------------------------------------------------------------------------------- /plugins/configuration/configuration-ini.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include "config.h" 10 | 11 | static bool (*add_configuration_backend)(plugin_h loader, const char *name, const struct function *get, const struct function *list); 12 | 13 | static const char *pair_sig = "c[],c[]|1"; 14 | 15 | struct pair { 16 | char *key, *value; 17 | }; 18 | 19 | static struct { 20 | plugin_h self; 21 | } plugin; 22 | 23 | static void 24 | throw(struct ini *ini, size_t line_num, size_t position, const char *line, const char *message) 25 | { 26 | (void)ini; 27 | plog(plugin.self, PLOG_ERROR, "[%zu, %zu]: %s", line_num, position, message); 28 | plog(plugin.self, PLOG_ERROR, "%s", line); 29 | plog(plugin.self, PLOG_ERROR, "%*c", (uint32_t)position, '^'); 30 | } 31 | 32 | static bool 33 | get_config_path(struct chck_string *path) 34 | { 35 | assert(path); 36 | char *config_dir = xdg_get_path("XDG_CONFIG_HOME", ".config"); 37 | static const char *suffix = "orbment/orbment.ini"; 38 | const bool ret = (!chck_cstr_is_empty(config_dir) && chck_string_set_format(path, "%s/%s", config_dir, suffix)); 39 | free(config_dir); 40 | return ret; 41 | } 42 | 43 | static bool 44 | convert_key(struct chck_string *converted, const char *key) 45 | { 46 | assert(converted && key); 47 | 48 | if (chck_cstr_is_empty(key)) 49 | return false; 50 | 51 | if (!key[1] || !chck_string_set_format(converted, "/%s", key)) 52 | return false; 53 | 54 | for (char *s = strchr(converted->data, '.'); s && *s; s = strchr(s, '.')) 55 | *s = '/'; 56 | 57 | return true; 58 | } 59 | 60 | static bool 61 | save(const char *stsign, struct pair *pairs, size_t memb) 62 | { 63 | (void)pairs, (void)memb; 64 | 65 | if (!chck_cstreq(stsign, pair_sig)) { 66 | plog(plugin.self, PLOG_WARN, "Wrong struct signature. (%s != %s)", pair_sig, stsign); 67 | return NULL; 68 | } 69 | 70 | plog(plugin.self, PLOG_WARN, "configuration-ini does not support saving"); 71 | return false; 72 | } 73 | 74 | static struct pair* 75 | load(const char *stsign, size_t *out_memb) 76 | { 77 | if (out_memb) 78 | *out_memb = 0; 79 | 80 | if (!chck_cstreq(stsign, pair_sig)) { 81 | plog(plugin.self, PLOG_WARN, "Wrong struct signature. (%s != %s)", pair_sig, stsign); 82 | return NULL; 83 | } 84 | 85 | struct chck_string path = {0}; 86 | if (!get_config_path(&path)) 87 | return false; 88 | 89 | if (access(path.data, R_OK)) { 90 | plog(plugin.self, PLOG_WARN, "Failed to open '%s': %s", path.data, strerror(errno)); 91 | goto error0; 92 | } 93 | 94 | struct ini inif; 95 | if (!ini(&inif, '/', 256, throw)) 96 | goto error0; 97 | 98 | const struct ini_options options = { .escaping = true, .quoted_strings = true, .empty_values = true }; 99 | if (!ini_parse(&inif, path.data, &options)) { 100 | plog(plugin.self, PLOG_ERROR, "Failed to parse '%s'", path.data); 101 | goto error1; 102 | } 103 | 104 | chck_string_release(&path); 105 | 106 | size_t keys = 0; 107 | struct ini_value v; 108 | ini_for_each(&inif, &v) 109 | ++keys; 110 | 111 | struct pair *pairs; 112 | if (!(pairs = chck_calloc_of(keys, sizeof(struct pair)))) 113 | goto error1; 114 | 115 | size_t i = 0; 116 | ini_for_each(&inif, &v) { 117 | struct chck_string converted = {0}, value = {0}; 118 | if (!convert_key(&converted, _I.path)) { 119 | chck_string_release(&converted); 120 | continue; 121 | } 122 | 123 | chck_string_set_cstr_with_length(&value, v.data, v.size, true); 124 | pairs[i++] = (struct pair){ converted.data, value.data }; 125 | } 126 | 127 | if (out_memb) 128 | *out_memb = keys; 129 | 130 | // Memory for pairs and the contained strings is now owned by configuration plugin 131 | ini_release(&inif); 132 | return pairs; 133 | 134 | error1: 135 | ini_release(&inif); 136 | error0: 137 | chck_string_release(&path); 138 | return NULL; 139 | } 140 | 141 | #pragma GCC diagnostic ignored "-Wmissing-prototypes" 142 | 143 | bool 144 | plugin_init(plugin_h self) 145 | { 146 | plugin.self = self; 147 | 148 | plugin_h configuration; 149 | if (!(configuration = import_plugin(self, "configuration"))) 150 | return false; 151 | 152 | if (!(add_configuration_backend = import_method(self, configuration, "add_configuration_backend", "b(h,c[],fun,fun)|1"))) 153 | return false; 154 | 155 | if (!add_configuration_backend(self, "INI", FUN(load, "*(c[],sz*)|1"), FUN(save, "b(c[],*,sz)|1"))) 156 | return false; 157 | 158 | return true; 159 | } 160 | 161 | PCONST const struct plugin_info* 162 | plugin_register(void) 163 | { 164 | static const char *requires[] = { 165 | "configuration", 166 | NULL, 167 | }; 168 | 169 | static const char *provides[] = { 170 | "configuration-backend", 171 | NULL, 172 | }; 173 | 174 | static const char *groups[] = { 175 | "configuration", 176 | NULL, 177 | }; 178 | 179 | static const struct plugin_info info = { 180 | .name = "configuration-ini", 181 | .description = "Configuration backend for the INI format.", 182 | .version = VERSION, 183 | .requires = requires, 184 | .provides = provides, 185 | .groups = groups, 186 | }; 187 | 188 | return &info; 189 | } 190 | -------------------------------------------------------------------------------- /plugins/configuration/configuration.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include "config.h" 6 | 7 | static bool (*add_hook)(plugin_h, const char *name, const struct function*); 8 | 9 | static const char *load_sig = "*(c[],sz*)|1"; 10 | static const char *save_sig = "b(c[],*,sz)|1"; 11 | static const char *pair_sig = "c[],c[]|1"; 12 | 13 | struct pair { 14 | char *key, *value; 15 | }; 16 | 17 | struct configuration_backend { 18 | plugin_h handle; 19 | const char *name; 20 | 21 | struct pair* (*load)(const char *stsign, size_t *out_memb); 22 | bool (*save)(const char *stsign, const struct pair*, size_t memb); 23 | }; 24 | 25 | static struct { 26 | plugin_h self; 27 | struct chck_hash_table table; 28 | struct configuration_backend backend; 29 | } plugin; 30 | 31 | PPURE static bool 32 | validate_key(const char *key) 33 | { 34 | if (chck_cstr_is_empty(key)) 35 | return false; 36 | 37 | /* A leading slash is required */ 38 | if (key[0] != '/') 39 | return false; 40 | 41 | for (uint32_t i = 1; key[i]; i++) { 42 | /* a-zA-Z0-9_- only */ 43 | if (key[i] >= 'a' && key[i] <= 'z') 44 | continue; 45 | if (key[i] >= 'A' && key[i] <= 'Z') 46 | continue; 47 | if (key[i] >= '0' && key[i] <= '9') 48 | continue; 49 | if (key[i] == '-' || key[i] == '_') 50 | continue; 51 | 52 | /* Slashes cannot be adjacent, or at the end of the key */ 53 | if (key[i] == '/' && key[i - 1] != '/' && key[i + 1] != '\0') 54 | continue; 55 | 56 | return false; 57 | } 58 | 59 | return true; 60 | } 61 | 62 | static void 63 | free_ptr(void **ptr) 64 | { 65 | free((ptr ? *ptr : NULL)); 66 | } 67 | 68 | static void 69 | load_config(void) 70 | { 71 | chck_hash_table_for_each_call(&plugin.table, free_ptr); 72 | chck_hash_table_release(&plugin.table); 73 | 74 | if (!plugin.backend.load) 75 | return; 76 | 77 | if (!chck_hash_table(&plugin.table, 0, 256, sizeof(char*))) 78 | return; 79 | 80 | size_t memb; 81 | struct pair *pairs; 82 | if (!(pairs = plugin.backend.load(pair_sig, &memb))) 83 | return; 84 | 85 | for (size_t i = 0; i < memb; ++i) { 86 | if (!validate_key(pairs[i].key)) { 87 | plog(plugin.self, PLOG_WARN, "Failed to validate key: %s", pairs[i].key); 88 | free(pairs[i].key); 89 | free(pairs[i].value); 90 | continue; 91 | } 92 | 93 | plog(plugin.self, PLOG_INFO, "%s = %s", pairs[i].key, pairs[i].value); 94 | chck_hash_table_str_set(&plugin.table, pairs[i].key, strlen(pairs[i].key), &pairs[i].value); 95 | free(pairs[i].key); 96 | } 97 | 98 | free(pairs); 99 | } 100 | 101 | static bool 102 | save_config(void) 103 | { 104 | if (!plugin.backend.save) 105 | return false; 106 | 107 | // XXX: implement 108 | return false; 109 | } 110 | 111 | static bool 112 | add_configuration_backend(plugin_h caller, const char *name, struct function *load, const struct function *save) 113 | { 114 | if (plugin.backend.name) { 115 | plog(plugin.self, PLOG_WARN, "Configuration backend '%s' already loaded.", plugin.backend.name); 116 | return false; 117 | } 118 | 119 | if (!load || !save) 120 | return false; 121 | 122 | if (chck_cstr_is_empty(name)) { 123 | plog(plugin.self, PLOG_ERROR, "Configuration backend must have a nonempty name."); 124 | return false; 125 | } 126 | 127 | if (!chck_cstreq(load->signature, load_sig)) { 128 | plog(plugin.self, PLOG_ERROR, "Wrong signature provided for configuration backend load() (%s != %s)", load->signature, load_sig); 129 | return false; 130 | } 131 | 132 | if (!chck_cstreq(save->signature, save_sig)) { 133 | plog(plugin.self, PLOG_ERROR, "Wrong signature provided for configuration backend save() (%s != %s)", save->signature, save_sig); 134 | return false; 135 | } 136 | 137 | plugin.backend.load = load->function; 138 | plugin.backend.save = save->function; 139 | plugin.backend.name = name; 140 | plugin.backend.handle = caller; 141 | load_config(); 142 | return true; 143 | } 144 | 145 | static bool 146 | get(const char *key, char type, void *value_out) 147 | { 148 | if (!validate_key(key)) { 149 | plog(plugin.self, PLOG_WARN, "Cannot get key '%s': invalid key format.", key); 150 | return false; 151 | } 152 | 153 | if (!type || !strchr("uidsb", type)) { /* Integer, Unsigned Integer, Double, String, Boolean */ 154 | plog(plugin.self, PLOG_WARN, "Cannot get key '%s': invalid type character '%c'.", key, type); 155 | return false; 156 | } 157 | 158 | const char *data = chck_hash_table_str_get(&plugin.table, key, strlen(key)); 159 | data = (data ? *(const char**)data : NULL); 160 | 161 | if (chck_cstr_is_empty(data)) 162 | return false; 163 | 164 | switch (type) { 165 | case 's': 166 | *(const char**)value_out = data; 167 | return true; 168 | break; 169 | 170 | case 'u': return chck_cstr_to_u32(data, value_out); 171 | case 'i': return chck_cstr_to_i32(data, value_out); 172 | case 'd': return chck_cstr_to_d(data, value_out); 173 | case 'b': return chck_cstr_to_bool(data, value_out); 174 | 175 | default: 176 | assert(false && "there should always be a valid type"); 177 | } 178 | 179 | return false; 180 | } 181 | 182 | static void 183 | plugin_deloaded(plugin_h ph) 184 | { 185 | if (ph != plugin.backend.handle) 186 | return; 187 | 188 | memset(&plugin.backend, 0, sizeof(plugin.backend)); 189 | } 190 | 191 | #pragma GCC diagnostic ignored "-Wmissing-prototypes" 192 | 193 | void 194 | plugin_deinit(plugin_h self) 195 | { 196 | (void)self; 197 | save_config(); 198 | 199 | chck_hash_table_for_each_call(&plugin.table, free_ptr); 200 | chck_hash_table_release(&plugin.table); 201 | } 202 | 203 | bool 204 | plugin_init(plugin_h self) 205 | { 206 | plugin.self = self; 207 | 208 | plugin_h orbment; 209 | if (!(orbment = import_plugin(self, "orbment"))) 210 | return false; 211 | 212 | if (!(add_hook = import_method(self, orbment, "add_hook", "b(h,c[],fun)|1"))) 213 | return false; 214 | 215 | return (add_hook(self, "plugin.deloaded", FUN(plugin_deloaded, "v(h)|1"))); 216 | } 217 | 218 | PCONST const struct plugin_info* 219 | plugin_register(void) 220 | { 221 | static const struct method methods[] = { 222 | REGISTER_METHOD(add_configuration_backend, "b(h,c[],fun,fun)|1"), 223 | REGISTER_METHOD(get, "b(c[],c,v)|1"), 224 | {0} 225 | }; 226 | 227 | static const char *groups[] = { 228 | "configuration", 229 | NULL, 230 | }; 231 | 232 | static const struct plugin_info info = { 233 | .name = "configuration", 234 | .description = "Configuration api.", 235 | .version = VERSION, 236 | .methods = methods, 237 | .groups = groups 238 | }; 239 | 240 | return &info; 241 | } 242 | -------------------------------------------------------------------------------- /plugins/configuration/configuration.rst: -------------------------------------------------------------------------------- 1 | Configuration system 2 | ==================== 3 | 4 | Plugin architecture 5 | ------------------- 6 | 7 | The ``configuration`` plugin exposes a common API intended for use by other 8 | plugins, and also handles common tasks such as validation. Actual data storage 9 | is handled by format-specific plugins. 10 | 11 | configuration-ini is the reference implementation for configuration backend. 12 | 13 | ``configuration`` API 14 | --------------------- 15 | 16 | - ``get()``: Fetches a configuration value. Takes the following parameters: 17 | 18 | * ``key``: refers to a particular configuration value. 19 | * ``type``: A character representing the required type of the configuration 20 | value: ``i`` for an integer, ``u`` for an unsigned integer, ``d`` for a double, and ``s`` for a string. 21 | * ``value_out``: The location in which to store the configuration value. This 22 | parameter can be ``NULL``; this allows testing for the existence of 23 | configuration keys without requiring the use of a temporary variable. 24 | 25 | Returns a boolean value; ``true`` indicates success. ``false`` may indicate 26 | that a value with the given key is not present, or that it has the wrong type. 27 | 28 | - ``add_configuration_backend``: Adds a configuration backend, responsible for 29 | data storage and retrieval. Takes the following parameters: 30 | 31 | * ``caller``: The handle of the configuration backend plugin. 32 | * ``name``: A human-readable format name, e.g. 'INI'. 33 | * ``load``: Returns list of key/value pairs to the configuration plugin. 34 | * ``save``: Stores list of key/value pairs. 35 | 36 | Returns a boolean value; ``true`` indicates success. 37 | 38 | Configuration keys and values 39 | ----------------------------- 40 | 41 | - Each key must consist of letters, digits, underscores, and dashes 42 | only, with the forward slash (``/``) acting as a hierarchical delimiter. 43 | No two forward slashes may be adjacent. A leading slash is required, and 44 | a trailing slash is not allowed. 45 | 46 | - Each keys corresponds to one and only one value. 47 | 48 | - An integral value must be within in the range [−2147483647,+2147483647], 49 | inclusive. 50 | 51 | - A string must be encoded in UTF8 52 | -------------------------------------------------------------------------------- /plugins/core-dpms/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_library(orbment-plugin-core-dpms MODULE core-dpms.c) 2 | target_link_libraries(orbment-plugin-core-dpms PRIVATE ${ORBMENT_LIBRARIES} ${CHCK_LIBRARIES}) 3 | add_plugins(orbment-plugin-core-dpms) 4 | -------------------------------------------------------------------------------- /plugins/core-dpms/core-dpms.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include "config.h" 6 | #include 7 | 8 | typedef void (*keybind_fun_t)(wlc_handle view, uint32_t time, intptr_t arg); 9 | static bool (*add_keybind)(plugin_h, const char *name, const char **syntax, const struct function*, intptr_t arg); 10 | static bool (*add_hook)(plugin_h, const char *name, const struct function*); 11 | 12 | static struct { 13 | struct { 14 | struct wlc_event_source *sleep; 15 | } timers; 16 | 17 | struct { 18 | // Sleep delay in seconds 19 | uint32_t delay; 20 | } config; 21 | 22 | // Force sleep from keybind 23 | // Skips next activity event 24 | bool force; 25 | 26 | plugin_h self; 27 | } plugin; 28 | 29 | static bool 30 | contains_fullscreen_view(wlc_handle output) 31 | { 32 | size_t memb; 33 | const wlc_handle *views = wlc_output_get_views(output, &memb); 34 | for (size_t i = 0; i < memb; ++i) { 35 | if (wlc_view_get_state(views[i]) & WLC_BIT_FULLSCREEN) 36 | return true; 37 | } 38 | 39 | return false; 40 | } 41 | 42 | static int 43 | timer_cb_sleep(void *arg) 44 | { 45 | (void)arg; 46 | 47 | size_t memb; 48 | const wlc_handle *outputs = wlc_get_outputs(&memb); 49 | 50 | if (!plugin.force) { 51 | for (size_t i = 0; i < memb; ++i) { 52 | if (contains_fullscreen_view(outputs[i])) 53 | goto restart; 54 | } 55 | } 56 | 57 | plog(plugin.self, PLOG_INFO, "Going to sleep"); 58 | 59 | for (size_t i = 0; i < memb; ++i) 60 | wlc_output_set_sleep(outputs[i], true); 61 | 62 | plugin.force = false; 63 | return 1; 64 | 65 | restart: 66 | plog(plugin.self, PLOG_INFO, "Preventing sleep"); 67 | wlc_event_source_timer_update(plugin.timers.sleep, 1000 * plugin.config.delay); 68 | return 1; 69 | } 70 | 71 | static void 72 | key_cb_toggle_sleep(wlc_handle view, uint32_t time, intptr_t arg) 73 | { 74 | (void)time, (void)arg, (void)view; 75 | plugin.force = wlc_event_source_timer_update(plugin.timers.sleep, 1); 76 | } 77 | 78 | static bool 79 | handle_activity(bool pressed) 80 | { 81 | if (!pressed) 82 | return false; 83 | 84 | size_t memb; 85 | bool was_sleeping = false; 86 | const wlc_handle *outputs = wlc_get_outputs(&memb); 87 | for (size_t i = 0; i < memb; ++i) { 88 | if (wlc_output_get_sleep(outputs[i])) { 89 | wlc_output_set_sleep(outputs[i], false); 90 | was_sleeping = true; 91 | } 92 | } 93 | 94 | if (was_sleeping) 95 | plog(plugin.self, PLOG_INFO, "Woke up"); 96 | 97 | if (!plugin.force) 98 | wlc_event_source_timer_update(plugin.timers.sleep, 1000 * plugin.config.delay); 99 | 100 | return was_sleeping; 101 | } 102 | 103 | /** 104 | * Handle keyboard.key separately, to pass key state information. 105 | */ 106 | static bool 107 | keyboard_key(wlc_handle view, uint32_t time, const struct wlc_modifiers *modifiers, uint32_t key, enum wlc_key_state state) 108 | { 109 | (void)view, (void)time, (void)modifiers, (void)key; 110 | return handle_activity(state == WLC_KEY_STATE_PRESSED); 111 | } 112 | 113 | /** 114 | * Handle pointer.button separately, to pass button state information. 115 | */ 116 | static bool 117 | pointer_button(wlc_handle view, uint32_t time, const struct wlc_modifiers *modifiers, uint32_t button, enum wlc_button_state state) 118 | { 119 | (void)view, (void)time, (void)modifiers, (void)button; 120 | return handle_activity(state == WLC_BUTTON_STATE_PRESSED); 121 | } 122 | 123 | /** 124 | * Variadic () function. 125 | * All hooks have same return value (bool) so this is valid. 126 | */ 127 | static bool 128 | activity() 129 | { 130 | return handle_activity(true); 131 | } 132 | 133 | static const struct { 134 | const char *name, **syntax; 135 | keybind_fun_t function; 136 | intptr_t arg; 137 | } keybinds[] = { 138 | { "Go to sleep", (const char*[]){ "", NULL }, key_cb_toggle_sleep, 0 }, 139 | {0}, 140 | }; 141 | 142 | static void 143 | load_config(plugin_h self) 144 | { 145 | // defaults 146 | plugin.config.delay = 60 * 5; // 5 mins; 147 | 148 | plugin_h configuration; 149 | bool (*configuration_get)(const char *key, const char type, void *value_out); 150 | if (!(configuration = import_plugin(self, "configuration")) || 151 | !(configuration_get = import_method(self, configuration, "get", "b(c[],c,v)|1"))) 152 | return; 153 | 154 | configuration_get("/dpms/delay", 'u', &plugin.config.delay); 155 | } 156 | 157 | #pragma GCC diagnostic ignored "-Wmissing-prototypes" 158 | 159 | void 160 | plugin_deinit(plugin_h self) 161 | { 162 | (void)self; 163 | 164 | if (plugin.timers.sleep) 165 | wlc_event_source_remove(plugin.timers.sleep); 166 | } 167 | 168 | bool 169 | plugin_init(plugin_h self) 170 | { 171 | plugin.self = self; 172 | 173 | if (!(plugin.timers.sleep = wlc_event_loop_add_timer(timer_cb_sleep, NULL))) 174 | return false; 175 | 176 | plugin_h orbment, keybind; 177 | if (!(orbment = import_plugin(self, "orbment")) || 178 | !(keybind = import_plugin(self, "keybind"))) 179 | return false; 180 | 181 | if (!(add_hook = import_method(self, orbment, "add_hook", "b(h,c[],fun)|1")) || 182 | !(add_keybind = import_method(self, keybind, "add_keybind", "b(h,c[],c*[],fun,ip)|1"))) 183 | return false; 184 | 185 | for (size_t i = 0; keybinds[i].name; ++i) 186 | if (!add_keybind(self, keybinds[i].name, keybinds[i].syntax, FUN(keybinds[i].function, "v(h,u32,ip)|1"), keybinds[i].arg)) 187 | return false; 188 | 189 | if (!add_hook(self, "keyboard.key", FUN(keyboard_key, "b(h,u32,*,u32,e)|1")) || 190 | !add_hook(self, "pointer.button", FUN(pointer_button, "b(h,u32,*,u32,e,*)|1")) || 191 | !add_hook(self, "pointer.scroll", FUN(activity, "b(h,u32,*,u8,d[2])|1")) || 192 | !add_hook(self, "pointer.motion", FUN(activity, "b(h,u32,*)|1")) || 193 | !add_hook(self, "touch", FUN(activity, "b(h,u32,*,e,i32,*)|1"))) 194 | return false; 195 | 196 | load_config(self); 197 | return wlc_event_source_timer_update(plugin.timers.sleep, 1000 * plugin.config.delay); 198 | } 199 | 200 | PCONST const struct plugin_info* 201 | plugin_register(void) 202 | { 203 | static const char *requires[] = { 204 | "keybind", 205 | NULL, 206 | }; 207 | 208 | static const char *after[] = { 209 | "configuration", 210 | NULL, 211 | }; 212 | 213 | static const struct plugin_info info = { 214 | .name = "core-dpms", 215 | .description = "Core display power management.", 216 | .version = VERSION, 217 | .requires = requires, 218 | .after = after, 219 | }; 220 | 221 | return &info; 222 | } 223 | -------------------------------------------------------------------------------- /plugins/core-functionality/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_library(orbment-plugin-core-functionality MODULE core-functionality.c) 2 | target_link_libraries(orbment-plugin-core-functionality PRIVATE ${ORBMENT_LIBRARIES} ${CHCK_LIBRARIES}) 3 | add_plugins(orbment-plugin-core-functionality) 4 | -------------------------------------------------------------------------------- /plugins/core-functionality/core-functionality.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include "common.h" 8 | #include "config.h" 9 | 10 | #define DEFAULT_TERMINAL "weston-terminal" 11 | #define DEFAULT_MENU "bemenu-run" 12 | 13 | static void (*relayout)(wlc_handle output); 14 | 15 | typedef void (*keybind_fun_t)(wlc_handle view, uint32_t time, intptr_t arg); 16 | static bool (*add_keybind)(plugin_h, const char *name, const char **syntax, const struct function*, intptr_t arg); 17 | static bool (*add_hook)(plugin_h, const char *name, const struct function*); 18 | 19 | static struct { 20 | struct { 21 | wlc_handle view; 22 | struct wlc_point grab; 23 | uint32_t edges; 24 | } action; 25 | 26 | struct { 27 | wlc_handle view; 28 | } active; 29 | 30 | struct { 31 | bool follow_focus; 32 | } config; 33 | 34 | struct chck_string terminal; 35 | plugin_h self; 36 | } plugin; 37 | 38 | static bool 39 | start_interactive_action(wlc_handle view, const struct wlc_point *origin) 40 | { 41 | if (plugin.action.view) 42 | return false; 43 | 44 | plugin.action.view = view; 45 | plugin.action.grab = *origin; 46 | wlc_view_bring_to_front(view); 47 | return true; 48 | } 49 | 50 | static void 51 | start_interactive_move(wlc_handle view, const struct wlc_point *origin) 52 | { 53 | start_interactive_action(view, origin); 54 | } 55 | 56 | static void 57 | start_interactive_resize(wlc_handle view, uint32_t edges, const struct wlc_point *origin) 58 | { 59 | const struct wlc_geometry *g; 60 | if (!(g = wlc_view_get_geometry(view)) || !start_interactive_action(view, origin)) 61 | return; 62 | 63 | const int32_t halfw = g->origin.x + g->size.w / 2; 64 | const int32_t halfh = g->origin.y + g->size.h / 2; 65 | 66 | if (!(plugin.action.edges = edges)) { 67 | plugin.action.edges = (origin->x < halfw ? WLC_RESIZE_EDGE_LEFT : (origin->x > halfw ? WLC_RESIZE_EDGE_RIGHT : 0)) | 68 | (origin->y < halfh ? WLC_RESIZE_EDGE_TOP : (origin->y > halfh ? WLC_RESIZE_EDGE_BOTTOM : 0)); 69 | } 70 | 71 | wlc_view_set_state(view, WLC_BIT_RESIZING, true); 72 | } 73 | 74 | static void 75 | stop_interactive_action(void) 76 | { 77 | if (!plugin.action.view) 78 | return; 79 | 80 | wlc_view_set_state(plugin.action.view, WLC_BIT_RESIZING, false); 81 | memset(&plugin.action, 0, sizeof(plugin.action)); 82 | } 83 | 84 | static wlc_handle 85 | get_next_view(wlc_handle view, size_t offset, enum direction dir) 86 | { 87 | size_t memb, i; 88 | wlc_handle *views = wlc_output_get_mutable_views((view ? wlc_view_get_output(view) : wlc_get_focused_output()), &memb); 89 | for (i = 0; i < memb && views[i] != view; ++i); 90 | return (memb > 0 ? views[(dir == PREV ? chck_clampsz(i - offset, 0, memb - 1) : i + offset) % memb] : 0); 91 | } 92 | 93 | PPURE static uint32_t 94 | rotate_mask(uint32_t mask, size_t offset, enum direction dir) 95 | { 96 | // Shifting with the total bits or more of mask variable causes UB 97 | assert(sizeof(mask) * CHAR_BIT >= offset); 98 | 99 | // XXX: Hardcoded to rotate range of 10 spaces. 100 | // Not yet sure how Orbment space handling eventually works. 101 | // But right now this is convenient due to Orbment giving you keybindings to 10 spaces. 102 | const uint32_t range = 10; 103 | 104 | // Circular shift from https://en.wikipedia.org/wiki/Circular_shift 105 | // Modified to shift only a subsection of the bits 106 | 107 | // Bitmask for the spaces in range, toggled bits define the spaces to shift, untoggled bits are left alone 108 | const uint32_t whitemask = (uint32_t)((uint64_t)1 << range) - 1; 109 | 110 | // Take the spaces in range and bitrotate them while ignoring spaces outside of the range 111 | const uint32_t spaces = mask & whitemask; 112 | 113 | uint32_t newmask; 114 | if (PREV == dir) 115 | newmask = (spaces >> offset) | (spaces << (range - offset)); 116 | else 117 | newmask = (spaces << offset) | (spaces >> (range - offset)); 118 | 119 | // Truncate bitmask to the range and append the bits outside of the range. 120 | return (newmask & whitemask) | (~whitemask & mask); 121 | } 122 | 123 | static wlc_handle 124 | get_next_output(wlc_handle output, size_t offset, enum direction dir) 125 | { 126 | size_t memb, i; 127 | const wlc_handle *outputs = wlc_get_outputs(&memb); 128 | for (i = 0; i < memb && outputs[i] != output; ++i); 129 | return (memb > 0 ? outputs[(dir == PREV ? chck_clampsz(i - offset, 0, memb - 1) : i + offset) % memb] : 0); 130 | } 131 | 132 | static bool 133 | should_focus_on_create(wlc_handle view) 134 | { 135 | // Do not allow unmanaged views to steal focus (tooltips, dnds, etc..) 136 | // Do not allow parented windows to steal focus, if current window wasn't parent. 137 | const wlc_handle parent = wlc_view_get_parent(view); 138 | return (is_managed(view) && (!plugin.active.view || !parent || parent == plugin.active.view)); 139 | } 140 | 141 | static void 142 | raise_all(wlc_handle view) 143 | { 144 | assert(view); 145 | 146 | if (!is_managed(view)) 147 | return; 148 | 149 | // Raise view and all related views to top honoring the stacking order. 150 | wlc_handle parent; 151 | if ((parent = wlc_view_get_parent(view))) { 152 | raise_all(parent); 153 | 154 | size_t memb; 155 | const wlc_handle *views = wlc_output_get_views(wlc_view_get_output(view), &memb); 156 | for (size_t i = 0; i < memb; ++i) { 157 | if (views[i] == view || wlc_view_get_parent(views[i]) != parent) 158 | continue; 159 | 160 | wlc_view_bring_to_front(views[i]); 161 | } 162 | } 163 | 164 | wlc_view_bring_to_front(view); 165 | } 166 | 167 | static void 168 | focus_view(wlc_handle view) 169 | { 170 | if (plugin.active.view == view) 171 | return; 172 | 173 | if (!is_managed(view)) 174 | return; 175 | 176 | // Bemenu should always have focus when open. 177 | if (plugin.active.view && (wlc_view_get_type(plugin.active.view) & BIT_BEMENU)) { 178 | wlc_view_bring_to_front(plugin.active.view); 179 | return; 180 | } 181 | 182 | if (view) { 183 | { 184 | size_t memb; 185 | const wlc_handle *views = wlc_output_get_views(wlc_view_get_output(view), &memb); 186 | for (size_t i = memb; i > 0; --i) { 187 | if (wlc_view_get_parent(views[i - 1]) == view) { 188 | // If window has parent, focus it instead of this. 189 | // By reverse searching views list, we get the topmost parent. 190 | focus_view(views[i - 1]); 191 | return; 192 | } 193 | } 194 | } 195 | 196 | // Only raise fullscreen views when focused view is managed 197 | if (is_managed(view) && !is_or(view)) { 198 | size_t memb; 199 | const wlc_handle *views = wlc_output_get_views(wlc_view_get_output(view), &memb); 200 | for (size_t i = memb; i > 0; --i) { 201 | if (wlc_view_get_state(views[i - 1]) & WLC_BIT_FULLSCREEN) { 202 | // Bring the first topmost found fullscreen wlc_view to front. 203 | // This way we get a "peek" effect when we cycle other views. 204 | // Meaning the active view is always over fullscreen view, 205 | // but fullscreen view is on top of the other views. 206 | wlc_view_bring_to_front(views[i - 1]); 207 | break; 208 | } 209 | } 210 | } 211 | 212 | raise_all(view); 213 | 214 | { 215 | size_t memb; 216 | const wlc_handle *views = wlc_output_get_views(wlc_view_get_output(view), &memb); 217 | for (size_t i = memb; i > 0; --i) { 218 | if ((wlc_view_get_type(views[i - 1]) & BIT_BEMENU)) { 219 | // Always bring bemenu to front when exists. 220 | wlc_view_bring_to_front(views[i - 1]); 221 | break; 222 | } 223 | } 224 | } 225 | } 226 | 227 | wlc_view_focus(view); 228 | plugin.active.view = view; 229 | } 230 | 231 | static void 232 | focus_next_or_previous_view(wlc_handle view, enum direction direction) 233 | { 234 | wlc_handle first = get_next_view(view, 0, direction), v = first, old = plugin.active.view; 235 | 236 | if (!first) 237 | return; 238 | 239 | do { 240 | while ((v = get_next_view(v, 1, direction)) && v != first && wlc_view_get_mask(v) != wlc_output_get_mask(wlc_view_get_output(view))); 241 | if (wlc_view_get_mask(v) == wlc_output_get_mask(wlc_get_focused_output())) 242 | focus_view(v); 243 | } while (plugin.active.view && plugin.active.view == old && v != old); 244 | } 245 | 246 | static void 247 | focus_topmost(wlc_handle output) 248 | { 249 | size_t memb; 250 | const wlc_handle *views = wlc_output_get_views(output, &memb); 251 | for (size_t i = memb; i > 0; --i) { 252 | if (wlc_view_get_mask(views[i - 1]) != wlc_output_get_mask(output)) 253 | continue; 254 | 255 | focus_view(views[i - 1]); 256 | return; 257 | } 258 | 259 | // There is no topmost 260 | focus_view(0); 261 | } 262 | 263 | static void 264 | focus_space(uint32_t index) 265 | { 266 | assert(sizeof(index) * CHAR_BIT >= index); 267 | wlc_output_set_mask(wlc_get_focused_output(), (1 << index)); 268 | focus_topmost(wlc_get_focused_output()); 269 | relayout(wlc_get_focused_output()); 270 | } 271 | 272 | static void 273 | move_to_space(wlc_handle view, uint32_t index) 274 | { 275 | assert(sizeof(index) * CHAR_BIT >= index); 276 | wlc_view_set_mask(view, (1 << index)); 277 | focus_space(index); 278 | } 279 | 280 | static void 281 | focus_next_or_previous_space(enum direction direction) 282 | { 283 | const wlc_handle output = wlc_get_focused_output(); 284 | wlc_output_set_mask(output, rotate_mask(wlc_output_get_mask(output), 1, direction)); 285 | focus_topmost(output); 286 | relayout(output); 287 | } 288 | 289 | static wlc_handle 290 | output_for_index(uint32_t index) 291 | { 292 | size_t memb; 293 | const wlc_handle *outputs = wlc_get_outputs(&memb); 294 | return (index < memb ? outputs[index] : 0); 295 | } 296 | 297 | static void 298 | focus_output(wlc_handle output) 299 | { 300 | wlc_output_focus(output); 301 | focus_topmost(wlc_get_focused_output()); 302 | relayout(output); 303 | } 304 | 305 | static void 306 | move_to_output(wlc_handle view, uint32_t index) 307 | { 308 | wlc_handle output; 309 | if (!(output = output_for_index(index))) 310 | return; 311 | 312 | wlc_view_set_mask(view, wlc_output_get_mask(output)); 313 | wlc_view_set_output(view, output); 314 | focus_output(output); 315 | } 316 | 317 | static void 318 | focus_next_or_previous_output(enum direction direction) 319 | { 320 | focus_output(get_next_output(wlc_get_focused_output(), 1, direction)); 321 | } 322 | 323 | static void 324 | set_active_view_on_output(wlc_handle output, wlc_handle view) 325 | { 326 | size_t memb; 327 | const wlc_handle *views = wlc_output_get_views(output, &memb); 328 | for (size_t i = 0; i < memb; ++i) 329 | wlc_view_set_state(views[i], WLC_BIT_ACTIVATED, (views[i] == view)); 330 | } 331 | 332 | static void 333 | view_move_to_output(wlc_handle view, wlc_handle from, wlc_handle to) 334 | { 335 | (void)view; 336 | 337 | relayout(from); 338 | relayout(to); 339 | plog(plugin.self, PLOG_INFO, "view %zu moved from output %zu to %zu", view, from, to); 340 | 341 | if (wlc_view_get_state(view) & WLC_BIT_ACTIVATED) 342 | set_active_view_on_output(to, view); 343 | 344 | focus_topmost(from); 345 | } 346 | 347 | static void 348 | view_move_request(wlc_handle view, const struct wlc_point *origin) 349 | { 350 | start_interactive_move(view, origin); 351 | } 352 | 353 | static void 354 | view_resize_request(wlc_handle view, uint32_t edges, const struct wlc_point *origin) 355 | { 356 | start_interactive_resize(view, edges, origin); 357 | } 358 | 359 | static void 360 | view_focus(wlc_handle view, bool focus) 361 | { 362 | if (wlc_view_get_output(view) == wlc_get_focused_output()) 363 | wlc_view_set_state(view, WLC_BIT_ACTIVATED, focus); 364 | } 365 | 366 | static bool 367 | view_created(wlc_handle view) 368 | { 369 | if (chck_cstreq(wlc_view_get_class(view), "bemenu") || chck_cstreq(wlc_view_get_title(view), "bemenu") || chck_cstreq(wlc_view_get_app_id(view), "bemenu")) { 370 | // Do not allow more than one bemenu instance 371 | if (plugin.active.view && wlc_view_get_type(plugin.active.view) & BIT_BEMENU) 372 | return false; 373 | 374 | wlc_view_set_type(view, BIT_BEMENU, true); // XXX: Hack 375 | } 376 | 377 | if (should_focus_on_create(view)) { 378 | if (wlc_view_get_output(view) == wlc_get_focused_output()) { 379 | focus_view(view); 380 | } else { 381 | set_active_view_on_output(wlc_view_get_output(view), view); 382 | } 383 | } 384 | 385 | wlc_view_set_mask(view, wlc_output_get_mask(wlc_view_get_output(view))); 386 | relayout(wlc_view_get_output(view)); 387 | return true; 388 | } 389 | 390 | static void 391 | view_destroyed(wlc_handle view) 392 | { 393 | if (plugin.active.view == view) { 394 | plugin.active.view = 0; 395 | 396 | wlc_handle v; 397 | if ((v = wlc_view_get_parent(view))) { 398 | // Focus the parent view, if there was one 399 | // Set parent 0 before this to avoid focusing back to dying view 400 | wlc_view_set_parent(view, 0); 401 | focus_view(v); 402 | } else { 403 | // Otherwise focus previous one. 404 | focus_topmost(wlc_view_get_output(view)); 405 | } 406 | } 407 | 408 | relayout(wlc_view_get_output(view)); 409 | } 410 | 411 | static bool 412 | pointer_motion(wlc_handle view, uint32_t time, const struct wlc_point *motion) 413 | { 414 | (void)time; 415 | 416 | wlc_pointer_set_position(motion); 417 | 418 | if (plugin.action.view) { 419 | const int32_t dx = motion->x - plugin.action.grab.x; 420 | const int32_t dy = motion->y - plugin.action.grab.y; 421 | struct wlc_geometry g = *wlc_view_get_geometry(plugin.action.view); 422 | 423 | if (plugin.action.edges) { 424 | const struct wlc_size min = { 80, 40 }; 425 | 426 | struct wlc_geometry n = g; 427 | if (plugin.action.edges & WLC_RESIZE_EDGE_LEFT) { 428 | n.size.w -= dx; 429 | n.origin.x += dx; 430 | } else if (plugin.action.edges & WLC_RESIZE_EDGE_RIGHT) { 431 | n.size.w += dx; 432 | } 433 | 434 | if (plugin.action.edges & WLC_RESIZE_EDGE_TOP) { 435 | n.size.h -= dy; 436 | n.origin.y += dy; 437 | } else if (plugin.action.edges & WLC_RESIZE_EDGE_BOTTOM) { 438 | n.size.h += dy; 439 | } 440 | 441 | if (n.size.w >= min.w) { 442 | g.origin.x = n.origin.x; 443 | g.size.w = n.size.w; 444 | } 445 | 446 | if (n.size.h >= min.h) { 447 | g.origin.y = n.origin.y; 448 | g.size.h = n.size.h; 449 | } 450 | 451 | wlc_view_set_geometry(plugin.action.view, plugin.action.edges, &g); 452 | } else { 453 | g.origin.x += dx; 454 | g.origin.y += dy; 455 | wlc_view_set_geometry(plugin.action.view, 0, &g); 456 | } 457 | 458 | plugin.action.grab = *motion; 459 | } else if (plugin.config.follow_focus) { 460 | focus_view(view); 461 | } 462 | 463 | return (plugin.action.view ? true : false); 464 | } 465 | 466 | static bool 467 | pointer_button(wlc_handle view, uint32_t time, const struct wlc_modifiers *modifiers, uint32_t button, enum wlc_button_state state, const struct wlc_point *position) 468 | { 469 | (void)view, (void)time, (void)modifiers, (void)button, (void)position; 470 | 471 | if (state == WLC_BUTTON_STATE_RELEASED) 472 | stop_interactive_action(); 473 | 474 | return (plugin.action.view ? true : false); 475 | } 476 | 477 | static void 478 | key_cb_exit(wlc_handle view, uint32_t time, intptr_t arg) 479 | { 480 | (void)view, (void)time, (void)arg; 481 | wlc_terminate(); 482 | } 483 | 484 | static void 485 | key_cb_close_client(wlc_handle view, uint32_t time, intptr_t arg) 486 | { 487 | (void)time, (void)arg; 488 | 489 | if (!view) 490 | return; 491 | 492 | wlc_view_close(view); 493 | } 494 | 495 | static void 496 | key_cb_spawn_terminal(wlc_handle view, uint32_t time, intptr_t arg) 497 | { 498 | (void)view, (void)time, (void)arg; 499 | wlc_exec(plugin.terminal.data, (char *const[]){ plugin.terminal.data, NULL }); 500 | } 501 | 502 | static void 503 | key_cb_spawn_bemenu(wlc_handle view, uint32_t time, intptr_t arg) 504 | { 505 | (void)view, (void)time, (void)arg; 506 | if (plugin.active.view && wlc_view_get_type(plugin.active.view) & BIT_BEMENU) 507 | return; 508 | wlc_exec(DEFAULT_MENU, (char *const[]){ DEFAULT_MENU, NULL }); 509 | } 510 | 511 | static void 512 | key_cb_toggle_fullscreen(wlc_handle view, uint32_t time, intptr_t arg) 513 | { 514 | (void)time, (void)arg; 515 | 516 | if (!view) 517 | return; 518 | 519 | wlc_view_set_state(view, WLC_BIT_FULLSCREEN, !(wlc_view_get_state(view) & WLC_BIT_FULLSCREEN)); 520 | relayout(wlc_view_get_output(view)); 521 | } 522 | 523 | static void 524 | key_cb_focus_space(wlc_handle view, uint32_t time, intptr_t arg) 525 | { 526 | (void)view, (void)time; 527 | focus_space((uint32_t)arg); 528 | } 529 | 530 | static void 531 | key_cb_focus_previous_space(wlc_handle view, uint32_t time, intptr_t arg) 532 | { 533 | (void)view, (void)time, (void)arg; 534 | focus_next_or_previous_space(PREV); 535 | } 536 | 537 | static void 538 | key_cb_focus_next_space(wlc_handle view, uint32_t time, intptr_t arg) 539 | { 540 | (void)view, (void)time, (void)arg; 541 | focus_next_or_previous_space(NEXT); 542 | } 543 | 544 | static void 545 | key_cb_move_to_output(wlc_handle view, uint32_t time, intptr_t arg) 546 | { 547 | (void)time; 548 | 549 | if (!view) 550 | return; 551 | 552 | move_to_output(view, (uint32_t)arg); 553 | } 554 | 555 | static void 556 | key_cb_move_to_space(wlc_handle view, uint32_t time, intptr_t arg) 557 | { 558 | (void)time; 559 | 560 | if (!view) 561 | return; 562 | 563 | move_to_space(view, (uint32_t)arg); 564 | } 565 | 566 | static void 567 | key_cb_focus_next_output(wlc_handle view, uint32_t time, intptr_t arg) 568 | { 569 | (void)view, (void)time, (void)arg; 570 | focus_next_or_previous_output(NEXT); 571 | } 572 | 573 | static void 574 | key_cb_focus_previous_client(wlc_handle view, uint32_t time, intptr_t arg) 575 | { 576 | (void)view, (void)time, (void)arg; 577 | focus_next_or_previous_view(plugin.active.view, PREV); 578 | } 579 | 580 | static void 581 | key_cb_focus_next_client(wlc_handle view, uint32_t time, intptr_t arg) 582 | { 583 | (void)view, (void)time, (void)arg; 584 | focus_next_or_previous_view(plugin.active.view, NEXT); 585 | } 586 | 587 | static void 588 | key_cb_focus_view(wlc_handle view, uint32_t time, intptr_t arg) 589 | { 590 | (void)time, (void)arg; 591 | 592 | if (!view) 593 | return; 594 | 595 | focus_view(view); 596 | } 597 | 598 | static void 599 | key_cb_move_view(wlc_handle view, uint32_t time, intptr_t arg) 600 | { 601 | (void)time, (void)arg; 602 | 603 | if (!view) 604 | return; 605 | 606 | struct wlc_point o; 607 | wlc_pointer_get_position(&o); 608 | start_interactive_move(view, &o); 609 | } 610 | 611 | static void 612 | key_cb_resize_view(wlc_handle view, uint32_t time, intptr_t arg) 613 | { 614 | (void)time, (void)arg; 615 | 616 | if (!view) 617 | return; 618 | 619 | struct wlc_point o; 620 | wlc_pointer_get_position(&o); 621 | start_interactive_resize(view, 0, &o); 622 | } 623 | 624 | static const struct { 625 | const char *name, **syntax; 626 | keybind_fun_t function; 627 | intptr_t arg; 628 | } keybinds[] = { 629 | { "exit", (const char*[]){ "", NULL }, key_cb_exit, 0 }, 630 | { "close client", (const char*[]){ "", NULL }, key_cb_close_client, 0 }, 631 | { "spawn terminal", (const char*[]){ "", NULL }, key_cb_spawn_terminal, 0 }, 632 | { "spawn bemenu", (const char*[]){ "", NULL }, key_cb_spawn_bemenu, 0 }, 633 | { "toggle fullscreen", (const char*[]){ "", NULL }, key_cb_toggle_fullscreen, 0 }, 634 | { "focus next output", (const char*[]){ "", NULL }, key_cb_focus_next_output, 0 }, 635 | { "focus next client", (const char*[]){ "", NULL }, key_cb_focus_next_client, 0 }, 636 | { "focus previous client", (const char*[]){ "", NULL }, key_cb_focus_previous_client, 0 }, 637 | { "focus space 0", (const char*[]){ "", "", NULL }, key_cb_focus_space, 0 }, 638 | { "focus space 1", (const char*[]){ "", "", NULL }, key_cb_focus_space, 1 }, 639 | { "focus space 2", (const char*[]){ "", "", NULL }, key_cb_focus_space, 2 }, 640 | { "focus space 3", (const char*[]){ "", "", NULL }, key_cb_focus_space, 3 }, 641 | { "focus space 4", (const char*[]){ "", "", NULL }, key_cb_focus_space, 4 }, 642 | { "focus space 5", (const char*[]){ "", "", NULL }, key_cb_focus_space, 5 }, 643 | { "focus space 6", (const char*[]){ "", "", NULL }, key_cb_focus_space, 6 }, 644 | { "focus space 7", (const char*[]){ "", "", NULL }, key_cb_focus_space, 7 }, 645 | { "focus space 8", (const char*[]){ "", "", NULL }, key_cb_focus_space, 8 }, 646 | { "focus space 9", (const char*[]){ "", "", NULL }, key_cb_focus_space, 9 }, 647 | { "focus left space", (const char*[]){ "", NULL }, key_cb_focus_previous_space, 0 }, 648 | { "focus right space", (const char*[]){ "", NULL }, key_cb_focus_next_space, 0 }, 649 | { "move to space 0", (const char*[]){ "", NULL }, key_cb_move_to_space, 0 }, 650 | { "move to space 1", (const char*[]){ "", NULL }, key_cb_move_to_space, 1 }, 651 | { "move to space 2", (const char*[]){ "", NULL }, key_cb_move_to_space, 2 }, 652 | { "move to space 3", (const char*[]){ "", NULL }, key_cb_move_to_space, 3 }, 653 | { "move to space 4", (const char*[]){ "", NULL }, key_cb_move_to_space, 4 }, 654 | { "move to space 5", (const char*[]){ "", NULL }, key_cb_move_to_space, 5 }, 655 | { "move to space 6", (const char*[]){ "", NULL }, key_cb_move_to_space, 6 }, 656 | { "move to space 7", (const char*[]){ "", NULL }, key_cb_move_to_space, 7 }, 657 | { "move to space 8", (const char*[]){ "", NULL }, key_cb_move_to_space, 8 }, 658 | { "move to space 9", (const char*[]){ "", NULL }, key_cb_move_to_space, 9 }, 659 | { "move to output 0", (const char*[]){ "", NULL }, key_cb_move_to_output, 0 }, 660 | { "move to output 1", (const char*[]){ "", NULL }, key_cb_move_to_output, 1 }, 661 | { "move to output 2", (const char*[]){ "", NULL }, key_cb_move_to_output, 2 }, 662 | { "focus view", (const char*[]){ "", NULL }, key_cb_focus_view, 0 }, 663 | { "move view", (const char*[]){ "", NULL }, key_cb_move_view, 0 }, 664 | { "resize view", (const char*[]){ "", NULL }, key_cb_resize_view, 0 }, 665 | {0}, 666 | }; 667 | 668 | static bool 669 | setup_default_keybinds(plugin_h self) 670 | { 671 | const char *terminal = getenv("TERMINAL"); 672 | chck_string_set_cstr(&plugin.terminal, (chck_cstr_is_empty(terminal) ? DEFAULT_TERMINAL : terminal), true); 673 | 674 | for (size_t i = 0; keybinds[i].name; ++i) 675 | if (!add_keybind(self, keybinds[i].name, keybinds[i].syntax, FUN(keybinds[i].function, "v(h,u32,ip)|1"), keybinds[i].arg)) 676 | return false; 677 | 678 | return true; 679 | } 680 | 681 | static void 682 | load_config(plugin_h self) 683 | { 684 | plugin_h configuration; 685 | bool (*configuration_get)(const char *key, const char type, void *value_out); 686 | if (!(configuration = import_plugin(self, "configuration")) || 687 | !(configuration_get = import_method(self, configuration, "get", "b(c[],c,v)|1"))) 688 | return; 689 | 690 | configuration_get("/core/follow-focus", 'b', &plugin.config.follow_focus); 691 | } 692 | 693 | #pragma GCC diagnostic ignored "-Wmissing-prototypes" 694 | 695 | void 696 | plugin_deinit(plugin_h self) 697 | { 698 | (void)self; 699 | chck_string_release(&plugin.terminal); 700 | } 701 | 702 | bool 703 | plugin_init(plugin_h self) 704 | { 705 | plugin.self = self; 706 | 707 | plugin_h orbment, keybind, layout; 708 | if (!(orbment = import_plugin(self, "orbment")) || 709 | !(keybind = import_plugin(self, "keybind")) || 710 | !(layout = import_plugin(self, "layout"))) 711 | return false; 712 | 713 | if (!(add_hook = import_method(self, orbment, "add_hook", "b(h,c[],fun)|1")) || 714 | !(add_keybind = import_method(self, keybind, "add_keybind", "b(h,c[],c*[],fun,ip)|1")) || 715 | !(relayout = import_method(self, layout, "relayout", "v(h)|1"))) 716 | return false; 717 | 718 | if (!setup_default_keybinds(self)) 719 | return false; 720 | 721 | load_config(self); 722 | return (add_hook(self, "view.created", FUN(view_created, "b(h)|1")) && 723 | add_hook(self, "view.destroyed", FUN(view_destroyed, "v(h)|1")) && 724 | add_hook(self, "view.focus", FUN(view_focus, "v(h,b)|1")) && 725 | add_hook(self, "view.move_to_output", FUN(view_move_to_output, "v(h,h,h)|1")) && 726 | add_hook(self, "view.move_request", FUN(view_move_request, "v(h,*)|1")) && 727 | add_hook(self, "view.resize_request", FUN(view_resize_request, "v(h,u32,*)|1")) && 728 | add_hook(self, "pointer.motion", FUN(pointer_motion, "b(h,u32,*)|1")) && 729 | add_hook(self, "pointer.button", FUN(pointer_button, "b(h,u32,*,u32,e,*)|1"))); 730 | } 731 | 732 | PCONST const struct plugin_info* 733 | plugin_register(void) 734 | { 735 | static const char *requires[] = { 736 | "keybind", 737 | "layout", 738 | NULL, 739 | }; 740 | 741 | static const char *after[] = { 742 | "configuration", 743 | NULL, 744 | }; 745 | 746 | static const struct plugin_info info = { 747 | .name = "core-functionality", 748 | .description = "Core functionality.", 749 | .version = VERSION, 750 | .requires = requires, 751 | .after = after, 752 | }; 753 | 754 | return &info; 755 | } 756 | -------------------------------------------------------------------------------- /plugins/core-input/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_library(orbment-plugin-core-input MODULE core-input.c) 2 | target_link_libraries(orbment-plugin-core-input PRIVATE ${ORBMENT_LIBRARIES} ${LIBINPUT_LIBRARIES} ${CHCK_LIBRARIES}) 3 | add_plugins(orbment-plugin-core-input) 4 | -------------------------------------------------------------------------------- /plugins/core-input/core-input.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include "config.h" 6 | 7 | static bool (*add_hook)(plugin_h, const char *name, const struct function*); 8 | 9 | static struct { 10 | plugin_h self; 11 | } plugin; 12 | 13 | static void 14 | configure_device(struct libinput_device *device) 15 | { 16 | const uint32_t id_product = libinput_device_get_id_product(device); 17 | const uint32_t id_vendor = libinput_device_get_id_vendor(device); 18 | if (id_product == 1 && id_vendor == 0) 19 | return; // power button 20 | 21 | plugin_h configuration; 22 | bool (*configuration_get)(const char *key, const char type, void *value_out); 23 | if (!(configuration = import_plugin(plugin.self, "configuration")) || 24 | !(configuration_get = import_method(plugin.self, configuration, "get", "b(c[],c,v)|1"))) 25 | return; 26 | 27 | const char *name = libinput_device_get_name(device); 28 | const char *sysname = libinput_device_get_sysname(device); 29 | plog(plugin.self, PLOG_INFO, "Configuring input device: %s (%s) (%u-%u)", name, sysname, id_product, id_vendor); 30 | 31 | struct chck_string str = {0}, id = {0}; 32 | if (!chck_string_set_format(&id, "input-%u-%u", id_product, id_vendor)) 33 | return; 34 | 35 | { 36 | bool v; 37 | if (chck_string_set_format(&str, "/%s/tap-to-click", id.data) && configuration_get(str.data, 'b', &v)) 38 | libinput_device_config_tap_set_enabled(device, (v ? LIBINPUT_CONFIG_TAP_ENABLED : LIBINPUT_CONFIG_TAP_DISABLED)); 39 | 40 | if (chck_string_set_format(&str, "/%s/drag-lock", id.data) && configuration_get(str.data, 'b', &v)) 41 | libinput_device_config_tap_set_drag_lock_enabled(device, (v ? LIBINPUT_CONFIG_DRAG_LOCK_ENABLED : LIBINPUT_CONFIG_DRAG_LOCK_DISABLED)); 42 | 43 | if (chck_string_set_format(&str, "/%s/natural-scroll", id.data) && configuration_get(str.data, 'b', &v)) 44 | libinput_device_config_scroll_set_natural_scroll_enabled(device, v); 45 | 46 | if (chck_string_set_format(&str, "/%s/left-handed", id.data) && configuration_get(str.data, 'b', &v)) 47 | libinput_device_config_left_handed_set(device, v); 48 | 49 | if (chck_string_set_format(&str, "/%s/emulate-middle", id.data) && configuration_get(str.data, 'b', &v)) 50 | libinput_device_config_middle_emulation_set_enabled(device, (v ? LIBINPUT_CONFIG_MIDDLE_EMULATION_ENABLED : LIBINPUT_CONFIG_MIDDLE_EMULATION_DISABLED)); 51 | 52 | if (chck_string_set_format(&str, "/%s/disable-while-typing", id.data) && configuration_get(str.data, 'b', &v)) 53 | libinput_device_config_dwt_set_enabled(device, (v ? LIBINPUT_CONFIG_DWT_ENABLED : LIBINPUT_CONFIG_DWT_DISABLED)); 54 | } 55 | 56 | { 57 | const char *v; 58 | if (chck_string_set_format(&str, "/%s/disabled", id.data) && configuration_get(str.data, 's', &v)) { 59 | libinput_device_config_send_events_set_mode(device, 60 | (chck_cstreq(v, "always") ? LIBINPUT_CONFIG_SEND_EVENTS_DISABLED : 61 | (chck_cstreq(v, "on-external-device") ? LIBINPUT_CONFIG_SEND_EVENTS_DISABLED_ON_EXTERNAL_MOUSE : 62 | LIBINPUT_CONFIG_SEND_EVENTS_ENABLED))); 63 | } 64 | 65 | if (chck_string_set_format(&str, "/%s/click-method", id.data) && configuration_get(str.data, 's', &v)) { 66 | libinput_device_config_click_set_method(device, 67 | (chck_cstreq(v, "finger") ? LIBINPUT_CONFIG_CLICK_METHOD_CLICKFINGER : 68 | (chck_cstreq(v, "button-areas") ? LIBINPUT_CONFIG_CLICK_METHOD_BUTTON_AREAS : 69 | LIBINPUT_CONFIG_CLICK_METHOD_NONE))); 70 | } 71 | 72 | if (chck_string_set_format(&str, "/%s/scroll-method", id.data) && configuration_get(str.data, 's', &v)) { 73 | libinput_device_config_scroll_set_method(device, 74 | (chck_cstreq(v, "two-fingers") ? LIBINPUT_CONFIG_SCROLL_2FG : 75 | (chck_cstreq(v, "edge") ? LIBINPUT_CONFIG_SCROLL_EDGE : 76 | (chck_cstreq(v, "button") ? LIBINPUT_CONFIG_SCROLL_ON_BUTTON_DOWN : 77 | LIBINPUT_CONFIG_SCROLL_NO_SCROLL)))); 78 | } 79 | } 80 | 81 | { 82 | uint32_t v; 83 | if (chck_string_set_format(&str, "/%s/scroll-button", id.data) && configuration_get(str.data, 'u', &v)) 84 | libinput_device_config_scroll_set_button(device, v); 85 | } 86 | 87 | { 88 | double v; 89 | if (chck_string_set_format(&str, "/%s/accel", id.data) && configuration_get(str.data, 'd', &v)) { 90 | if (v <= 1 && v >= -1) { 91 | libinput_device_config_accel_set_speed(device, v); 92 | } else { 93 | plog(plugin.self, PLOG_WARN, "Accel must be normalized range [-1, 1]"); 94 | } 95 | } 96 | } 97 | 98 | chck_string_release(&str); 99 | chck_string_release(&id); 100 | } 101 | 102 | static bool 103 | input_created(struct libinput_device *device) 104 | { 105 | configure_device(device); 106 | return true; 107 | } 108 | 109 | #pragma GCC diagnostic ignored "-Wmissing-prototypes" 110 | 111 | bool 112 | plugin_init(plugin_h self) 113 | { 114 | plugin_h orbment; 115 | if (!(orbment = import_plugin(self, "orbment"))) 116 | return false; 117 | 118 | if (!(add_hook = import_method(self, orbment, "add_hook", "b(h,c[],fun)|1"))) 119 | return false; 120 | 121 | plugin.self = self; 122 | return add_hook(self, "input.created", FUN(input_created, "b(*)|1")); 123 | } 124 | 125 | PCONST const struct plugin_info* 126 | plugin_register(void) 127 | { 128 | static const char *requires[] = { 129 | "configuration", 130 | NULL, 131 | }; 132 | 133 | static const struct plugin_info info = { 134 | .name = "core-input", 135 | .description = "Input device configuration.", 136 | .version = VERSION, 137 | .requires = requires, 138 | }; 139 | 140 | return &info; 141 | } 142 | -------------------------------------------------------------------------------- /plugins/core-layouts/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_library(orbment-plugin-core-layouts MODULE core-layouts.c) 2 | target_link_libraries(orbment-plugin-core-layouts PRIVATE ${ORBMENT_LIBRARIES} ${CHCK_LIBRARIES}) 3 | set_target_properties(orbment-plugin-core-layouts PROPERTIES PREFIX "") 4 | install(TARGETS orbment-plugin-core-layouts DESTINATION ${ORBMENT_PLUGINS_PATH}) 5 | -------------------------------------------------------------------------------- /plugins/core-layouts/core-layouts.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include "config.h" 7 | 8 | static struct { 9 | struct { 10 | float cut; 11 | } nmaster; 12 | } config = { 13 | .nmaster = { 14 | .cut = 0.5f, 15 | }, 16 | }; 17 | 18 | static void (*relayout)(wlc_handle output); 19 | 20 | typedef void (*layout_fun_t)(const struct wlc_geometry *r, const wlc_handle *views, size_t memb); 21 | static bool (*add_layout)(plugin_h, const char *name, const struct function*); 22 | 23 | typedef void (*keybind_fun_t)(wlc_handle view, uint32_t time, intptr_t arg); 24 | static bool (*add_keybind)(plugin_h, const char *name, const char **syntax, const struct function*, intptr_t arg); 25 | 26 | static void 27 | key_cb_nmaster_grow(wlc_handle view, uint32_t time, intptr_t arg) 28 | { 29 | (void)view, (void)time, (void)arg; 30 | config.nmaster.cut = chck_minf(config.nmaster.cut + 0.01, 1.0); 31 | relayout(wlc_get_focused_output()); 32 | } 33 | 34 | static void 35 | key_cb_nmaster_shrink(wlc_handle view, uint32_t time, intptr_t arg) 36 | { 37 | (void)view, (void)time, (void)arg; 38 | config.nmaster.cut = chck_maxf(config.nmaster.cut - 0.01, 0.0); 39 | relayout(wlc_get_focused_output()); 40 | } 41 | 42 | static void 43 | nmaster(const struct wlc_geometry *r, const wlc_handle *views, size_t memb) 44 | { 45 | bool toggle = false; 46 | uint32_t y = 0, height = r->size.h / (memb > 1 ? memb - 1 : 1); 47 | uint32_t fheight = (r->size.h > height * (memb - 1) ? height + (r->size.h - height * (memb - 1)) : height); 48 | 49 | for (size_t i = 0; i < memb; ++i) { 50 | uint32_t slave = r->size.w * config.nmaster.cut; 51 | wlc_view_set_state(views[i], WLC_BIT_MAXIMIZED, true); 52 | 53 | struct wlc_geometry g = { 54 | .origin = { r->origin.x + (toggle ? r->size.w - slave : 0), r->origin.y + y }, 55 | .size = { (memb > 1 ? (toggle ? slave : r->size.w - slave) : r->size.w), (toggle ? (y == 0 ? fheight : height) : r->size.h) }, 56 | }; 57 | 58 | wlc_view_set_geometry(views[i], 0, &g); 59 | 60 | if (toggle) 61 | y += (y == 0 ? fheight : height); 62 | 63 | toggle = true; 64 | } 65 | } 66 | 67 | static void 68 | grid(const struct wlc_geometry *r, const wlc_handle *views, size_t memb) 69 | { 70 | bool toggle = false; 71 | uint32_t y = 0; 72 | uint32_t w = r->size.w / 2, h = r->size.h / chck_maxu32((1 + memb) / 2, 1); 73 | for (size_t i = 0; i < memb; ++i) { 74 | struct wlc_geometry g = { { r->origin.x + (toggle ? w : 0), r->origin.y + y }, { (!toggle && i == memb - 1 ? r->size.w : w), h } }; 75 | wlc_view_set_geometry(views[i], 0, &g); 76 | y = y + (!(toggle = !toggle) ? h : 0); 77 | } 78 | } 79 | 80 | static void 81 | monocle(const struct wlc_geometry *r, const wlc_handle *views, size_t memb) 82 | { 83 | for (size_t i = 0; i < memb; ++i) 84 | wlc_view_set_geometry(views[i], 0, r); 85 | } 86 | 87 | static const struct { 88 | const char *name, **syntax; 89 | keybind_fun_t function; 90 | } keybinds[] = { 91 | { "grow nmaster", (const char*[]){ "", NULL }, key_cb_nmaster_grow }, 92 | { "shrink nmaster", (const char*[]){ "", NULL }, key_cb_nmaster_shrink }, 93 | {0}, 94 | }; 95 | 96 | static const struct { 97 | const char *name; 98 | layout_fun_t function; 99 | } layouts[] = { 100 | { "nmaster", nmaster }, 101 | { "grid", grid }, 102 | { "monocle", monocle }, 103 | {0}, 104 | }; 105 | 106 | #pragma GCC diagnostic ignored "-Wmissing-prototypes" 107 | 108 | bool 109 | plugin_init(plugin_h self) 110 | { 111 | plugin_h keybind, layout; 112 | if (!(keybind = import_plugin(self, "keybind")) || 113 | !(layout = import_plugin(self, "layout"))) 114 | return false; 115 | 116 | if (!(add_keybind = import_method(self, keybind, "add_keybind", "b(h,c[],c*[],fun,ip)|1")) || 117 | !(relayout = import_method(self, layout, "relayout", "v(h)|1")) || 118 | !(add_layout = import_method(self, layout, "add_layout", "b(h,c[],fun)|1"))) 119 | return false; 120 | 121 | for (size_t i = 0; layouts[i].name; ++i) 122 | if (!add_layout(self, layouts[i].name, FUN(layouts[i].function, "v(*,h[],sz)|1"))) 123 | return false; 124 | 125 | for (size_t i = 0; keybinds[i].name; ++i) 126 | if (!add_keybind(self, keybinds[i].name, keybinds[i].syntax, FUN(keybinds[i].function, "v(h,u32,ip)|1"), 0)) 127 | return false; 128 | 129 | return true; 130 | } 131 | 132 | PCONST const struct plugin_info* 133 | plugin_register(void) 134 | { 135 | static const char *requires[] = { 136 | "keybind", 137 | "layout", 138 | NULL, 139 | }; 140 | 141 | static const struct plugin_info info = { 142 | .name = "core-layouts", 143 | .description = "Core set of layouts.", 144 | .version = VERSION, 145 | .requires = requires, 146 | }; 147 | 148 | return &info; 149 | } 150 | -------------------------------------------------------------------------------- /plugins/core-screenshot/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_library(orbment-plugin-core-screenshot MODULE core-screenshot.c) 2 | target_link_libraries(orbment-plugin-core-screenshot PRIVATE ${CHCK_LIBRARIES}) 3 | add_plugins(orbment-plugin-core-screenshot) 4 | -------------------------------------------------------------------------------- /plugins/core-screenshot/core-screenshot.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include "config.h" 13 | 14 | static const char *compress_signature = "u8[](p,u8[],sz*)|1"; 15 | typedef uint8_t* (*compress_fun)(const struct wlc_size*, uint8_t*, size_t*); 16 | 17 | static const char *struct_signature = "c[],c[],*|1"; 18 | struct compressor { 19 | const char *name; 20 | const char *ext; 21 | compress_fun function; 22 | }; 23 | 24 | struct compressor* (*list_compressors)(const char *type, const char *stsign, const char *funsign, size_t *out_memb); 25 | 26 | typedef void (*keybind_fun_t)(wlc_handle view, uint32_t time, intptr_t arg); 27 | static bool (*add_keybind)(plugin_h, const char *name, const char **syntax, const struct function*, intptr_t arg); 28 | static bool (*add_hook)(plugin_h, const char *name, const struct function*); 29 | 30 | static struct { 31 | struct { 32 | wlc_handle output; // if != 0, screenshot will be taken in next frame and reset after to 0 33 | size_t compressor; 34 | } action; 35 | 36 | struct chck_tqueue tqueue; 37 | plugin_h self; 38 | } plugin; 39 | 40 | struct image { 41 | uint8_t *data; 42 | size_t size; 43 | }; 44 | 45 | struct work { 46 | struct wlc_size dimensions; 47 | struct compressor compressor; 48 | struct image image; 49 | }; 50 | 51 | static void 52 | work_release(struct work *work) 53 | { 54 | if (!work) 55 | return; 56 | 57 | free(work->image.data); 58 | } 59 | 60 | PPURE static void 61 | cb_did_compress(struct work *work) 62 | { 63 | (void)work; 64 | assert(work); 65 | } 66 | 67 | static void 68 | cb_compress(struct work *work) 69 | { 70 | assert(work); 71 | 72 | uint8_t *data; 73 | if (!(data = work->compressor.function(&work->dimensions, work->image.data, &work->image.size))) { 74 | plog(plugin.self, PLOG_ERROR, "Failed to compress data using '%s compressor'", work->compressor.name); 75 | return; 76 | } 77 | 78 | free(work->image.data); 79 | work->image.data = data; 80 | 81 | if (!work->image.size) 82 | return; 83 | 84 | struct chck_string name = {0}; 85 | time_t now; 86 | time(&now); 87 | char buf[sizeof("orbment-0000-00-00T00:00:00Z")]; 88 | strftime(buf, sizeof(buf), "orbment-%FT%TZ", gmtime(&now)); 89 | chck_string_set_format(&name, "%s.%s", buf, work->compressor.ext); 90 | 91 | FILE *f; 92 | if (!(f = fopen(name.data, "wb"))) { 93 | plog(plugin.self, PLOG_ERROR, "Could not open file for writing: %s", name.data); 94 | goto error1; 95 | } 96 | 97 | fwrite(data, 1, work->image.size, f); 98 | fclose(f); 99 | 100 | plog(plugin.self, PLOG_INFO, "Wrote screenshot to %s", name.data); 101 | chck_string_release(&name); 102 | 103 | return; 104 | 105 | error1: 106 | chck_string_release(&name); 107 | } 108 | 109 | static void 110 | key_cb_screenshot(wlc_handle view, uint32_t time, intptr_t arg) 111 | { 112 | (void)view, (void)time; 113 | plugin.action.output = wlc_get_focused_output(); 114 | plugin.action.compressor = arg; 115 | wlc_output_schedule_render(wlc_get_focused_output()); 116 | } 117 | 118 | static void 119 | output_post_render(wlc_handle output) 120 | { 121 | if (plugin.action.output != output) 122 | return; 123 | 124 | plugin.action.output = 0; 125 | 126 | size_t memb; 127 | struct compressor *compressors = list_compressors("image", struct_signature, compress_signature, &memb); 128 | if (!memb || plugin.action.compressor >= memb) { 129 | plog(plugin.self, PLOG_ERROR, "Could not find compressor for index (%zu)", plugin.action.compressor); 130 | return; 131 | } 132 | 133 | const struct wlc_geometry g = { .origin = { 0, 0 }, .size = *wlc_output_get_resolution(output) }; 134 | 135 | void *rgba; 136 | if (!(rgba = calloc(1, g.size.w * g.size.h * 4))) 137 | return; 138 | 139 | struct wlc_geometry out; 140 | wlc_pixels_read(WLC_RGBA8888, &g, &out, rgba); 141 | 142 | struct work work = { 143 | .image = { 144 | .data = rgba, 145 | }, 146 | .dimensions = out.size, 147 | .compressor = compressors[plugin.action.compressor], 148 | }; 149 | 150 | if (!chck_tqueue_add_task(&plugin.tqueue, &work, 0)) 151 | free(rgba); 152 | } 153 | 154 | #pragma GCC diagnostic ignored "-Wmissing-prototypes" 155 | 156 | void 157 | plugin_deinit(plugin_h self) 158 | { 159 | (void)self; 160 | chck_tqueue_release(&plugin.tqueue); 161 | } 162 | 163 | bool 164 | plugin_init(plugin_h self) 165 | { 166 | plugin.self = self; 167 | 168 | plugin_h orbment, keybind, compressor; 169 | if (!(orbment = import_plugin(self, "orbment")) || 170 | !(keybind = import_plugin(self, "keybind")) || 171 | !(compressor = import_plugin(self, "compressor"))) 172 | return false; 173 | 174 | if (!(add_hook = import_method(self, orbment, "add_hook", "b(h,c[],fun)|1")) || 175 | !(add_keybind = import_method(self, keybind, "add_keybind", "b(h,c[],c*[],fun,ip)|1")) || 176 | !(list_compressors = import_method(self, compressor, "list_compressors", "*(c[],c[],c[],sz*)|1"))) 177 | return false; 178 | 179 | if (!add_hook(self, "output.post_render", FUN(output_post_render, "v(h)|1"))) 180 | return false; 181 | 182 | size_t memb; 183 | struct compressor *compressors = list_compressors("image", struct_signature, compress_signature, &memb); 184 | for (size_t i = 0; i < memb; ++i) { 185 | struct chck_string name = {0}; 186 | chck_string_set_format(&name, "take screenshot %s", compressors[i].name); 187 | const bool ret = add_keybind(self, name.data, (chck_cstreq(compressors[i].name, "png") ? (const char*[]){ "", "", NULL } : NULL), FUN(key_cb_screenshot, "v(h,u32,ip)|1"), i); 188 | chck_string_release(&name); 189 | 190 | if (!ret) 191 | return false; 192 | } 193 | 194 | if (!chck_tqueue(&plugin.tqueue, 1, 4, sizeof(struct work), cb_compress, cb_did_compress, work_release)) 195 | return false; 196 | 197 | return true; 198 | } 199 | 200 | PCONST const struct plugin_info* 201 | plugin_register(void) 202 | { 203 | static const char *requires[] = { 204 | "keybind", 205 | "compressor", 206 | NULL, 207 | }; 208 | 209 | static const struct plugin_info info = { 210 | .name = "core-screenshot", 211 | .description = "Screenshot functionality.", 212 | .version = VERSION, 213 | .requires = requires, 214 | }; 215 | 216 | return &info; 217 | } 218 | -------------------------------------------------------------------------------- /plugins/crappy-borders/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_library(orbment-plugin-crappy-borders MODULE crappy-borders.c) 2 | target_link_libraries(orbment-plugin-crappy-borders PRIVATE ${ORBMENT_LIBRARIES} ${CHCK_LIBRARIES}) 3 | add_plugins(orbment-plugin-crappy-borders) 4 | -------------------------------------------------------------------------------- /plugins/crappy-borders/crappy-borders.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "config.h" 4 | #include 5 | #include 6 | 7 | static bool (*add_hook)(plugin_h, const char *name, const struct function*); 8 | 9 | static struct { 10 | plugin_h self; 11 | struct chck_buffer fb; 12 | } plugin; 13 | 14 | static void 15 | view_pre_render(wlc_handle view) 16 | { 17 | const size_t bsz = 2; // 2px 18 | struct wlc_geometry g; 19 | wlc_view_get_visible_geometry(view, &g); 20 | g.origin.x -= bsz; 21 | g.origin.y -= bsz; 22 | g.size.w += bsz * 2; 23 | g.size.h += bsz * 2; 24 | 25 | const size_t vsz = g.size.w * g.size.h * 4; 26 | if (plugin.fb.size < vsz && !chck_buffer_resize(&plugin.fb, vsz)) { 27 | return; 28 | } else { 29 | memset(plugin.fb.buffer, 255, vsz); // white 30 | } 31 | 32 | // LALALA I DON'T CARE ABOUT TRANSPARENT WINDOWS, CAN'T HEAR YOU LALALA 33 | wlc_pixels_write(WLC_RGBA8888, &g, plugin.fb.buffer); 34 | } 35 | 36 | #pragma GCC diagnostic ignored "-Wmissing-prototypes" 37 | 38 | void 39 | plugin_deinit(plugin_h self) 40 | { 41 | (void)self; 42 | chck_buffer_release(&plugin.fb); 43 | } 44 | 45 | bool 46 | plugin_init(plugin_h self) 47 | { 48 | plugin.self = self; 49 | 50 | plugin_h orbment; 51 | if (!(orbment = import_plugin(self, "orbment"))) 52 | return false; 53 | 54 | if (!(add_hook = import_method(self, orbment, "add_hook", "b(h,c[],fun)|1"))) 55 | return false; 56 | 57 | if (!add_hook(self, "view.pre_render", FUN(view_pre_render, "v(h)|1"))) 58 | return false; 59 | 60 | if (!chck_buffer(&plugin.fb, 0, chck_endianess())) 61 | return false; 62 | 63 | return true; 64 | } 65 | 66 | PCONST const struct plugin_info* 67 | plugin_register(void) 68 | { 69 | static const struct plugin_info info = { 70 | .name = "crappy-borders", 71 | .description = "Provides crappy window borders.", 72 | .version = VERSION, 73 | }; 74 | 75 | return &info; 76 | } 77 | -------------------------------------------------------------------------------- /plugins/keybind/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_library(orbment-plugin-keybind MODULE keybind.c) 2 | target_link_libraries(orbment-plugin-keybind PRIVATE ${ORBMENT_LIBRARIES} ${XKBCOMMON_LIBRARIES} ${CHCK_LIBRARIES}) 3 | add_plugins(orbment-plugin-keybind) 4 | -------------------------------------------------------------------------------- /plugins/keybind/keybind.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include "common.h" 10 | #include "config.h" 11 | 12 | #include 13 | 14 | static const size_t NOTINDEX = (size_t)-1; 15 | 16 | static bool (*add_hook)(plugin_h, const char *name, const struct function*); 17 | static bool (*configuration_get)(const char *key, const char type, void *value_out); 18 | 19 | typedef void (*keybind_fun_t)(wlc_handle view, uint32_t time, intptr_t arg); 20 | struct keybind { 21 | struct chck_string name; 22 | const char **defaults; 23 | keybind_fun_t function; 24 | intptr_t arg; 25 | plugin_h owner; 26 | }; 27 | 28 | static struct { 29 | struct { 30 | struct chck_pool pool; 31 | struct chck_hash_table table; 32 | } keybinds; 33 | 34 | uint32_t prefix; 35 | plugin_h self; 36 | } plugin; 37 | 38 | static uint32_t 39 | parse_prefix(const char *str) 40 | { 41 | // default prefix 42 | const uint32_t def = (wlc_get_backend_type() == WLC_BACKEND_X11 ? WLC_BIT_MOD_ALT : WLC_BIT_MOD_LOGO); 43 | 44 | if (!str) 45 | return def; 46 | 47 | static const struct { 48 | const char *name; 49 | enum wlc_modifier_bit mod; 50 | } map[] = { 51 | { "shift", WLC_BIT_MOD_SHIFT }, 52 | { "caps", WLC_BIT_MOD_CAPS }, 53 | { "ctrl", WLC_BIT_MOD_CTRL }, 54 | { "alt", WLC_BIT_MOD_ALT }, 55 | { "mod2", WLC_BIT_MOD_MOD2 }, 56 | { "mod3", WLC_BIT_MOD_MOD3 }, 57 | { "logo", WLC_BIT_MOD_LOGO }, 58 | { "mod5", WLC_BIT_MOD_MOD5 }, 59 | { NULL, 0 }, 60 | }; 61 | 62 | uint32_t prefix = 0; 63 | const char *s = str; 64 | for (int i = 0; map[i].name && *s; ++i) { 65 | if (!chck_cstreq(map[i].name, s)) 66 | continue; 67 | 68 | prefix |= map[i].mod; 69 | } 70 | 71 | return (prefix ? prefix : def); 72 | } 73 | 74 | static bool 75 | syntax_append(struct chck_string *syntax, const char *cstr, bool is_heap) 76 | { 77 | assert(syntax && cstr); 78 | 79 | if (syntax->size > 0) 80 | return chck_string_set_format(syntax, "%s-%s", syntax->data, cstr); 81 | 82 | return chck_string_set_cstr(syntax, cstr, is_heap); 83 | } 84 | 85 | static bool 86 | append_mods(struct chck_string *syntax, struct chck_string *prefixed, uint32_t mods) 87 | { 88 | assert(syntax && prefixed); 89 | 90 | if (mods == plugin.prefix && !syntax_append(prefixed, "P", false)) 91 | return false; 92 | 93 | static const struct { 94 | const char *name; 95 | enum wlc_modifier_bit mod; 96 | } map[] = { 97 | { "S", WLC_BIT_MOD_SHIFT }, 98 | { "C", WLC_BIT_MOD_CTRL }, 99 | { "M", WLC_BIT_MOD_ALT }, 100 | { "L", WLC_BIT_MOD_LOGO }, 101 | { "M2", WLC_BIT_MOD_MOD2 }, 102 | { "M3", WLC_BIT_MOD_MOD3 }, 103 | { "M5", WLC_BIT_MOD_MOD5 }, 104 | { NULL, 0 }, 105 | }; 106 | 107 | for (uint32_t i = 0; map[i].name; ++i) { 108 | if (!(mods & map[i].mod)) 109 | continue; 110 | 111 | if (!syntax_append(syntax, map[i].name, false)) 112 | return false; 113 | } 114 | 115 | return true; 116 | } 117 | 118 | static bool 119 | keybind_exists(const char *name) 120 | { 121 | const struct keybind *k; 122 | chck_pool_for_each(&plugin.keybinds.pool, k) { 123 | if (chck_string_eq_cstr(&k->name, name)) 124 | return true; 125 | } 126 | 127 | return false; 128 | } 129 | 130 | static const struct keybind* 131 | keybind_for_syntax(const char *syntax) 132 | { 133 | size_t *index; 134 | if (chck_cstr_is_empty(syntax) || !(index = chck_hash_table_str_get(&plugin.keybinds.table, syntax, strlen(syntax))) || *index == NOTINDEX) 135 | return NULL; 136 | 137 | return chck_pool_get(&plugin.keybinds.pool, *index); 138 | } 139 | 140 | static bool 141 | add_keybind_mapping(struct chck_string *mappings, const char *syntax, size_t *index) 142 | { 143 | assert(mappings && index); 144 | 145 | if (chck_cstr_is_empty(syntax)) 146 | return false; 147 | 148 | const struct keybind *o; 149 | if ((o = keybind_for_syntax(syntax))) { 150 | plog(plugin.self, PLOG_WARN, "'%s' is already mapped to keybind '%s'", syntax, o->name.data); 151 | return false; 152 | } 153 | 154 | chck_hash_table_str_set(&plugin.keybinds.table, syntax, strlen(syntax), index); 155 | chck_string_set_format(mappings, (mappings->size > 0 ? "%s, %s" : "%s%s"), (mappings->data ? mappings->data : ""), syntax); 156 | return true; 157 | } 158 | 159 | static bool 160 | add_keybind(plugin_h caller, const char *name, const char **syntax, const struct function *fun, intptr_t arg) 161 | { 162 | if (!name || !fun || !caller) 163 | return false; 164 | 165 | static const char *signature = "v(h,u32,ip)|1"; 166 | 167 | if (!chck_cstreq(fun->signature, signature)) { 168 | plog(plugin.self, PLOG_WARN, "Wrong signature provided for '%s keybind' function. (%s != %s)", name, signature, fun->signature); 169 | return false; 170 | } 171 | 172 | if (keybind_exists(name)) { 173 | plog(plugin.self, PLOG_WARN, "Keybind with name '%s' already exists", name); 174 | return false; 175 | } 176 | 177 | if (!plugin.keybinds.pool.items.member && !chck_pool(&plugin.keybinds.pool, 32, 0, sizeof(struct keybind))) 178 | return false; 179 | 180 | if (!plugin.keybinds.table.lut.table && !chck_hash_table(&plugin.keybinds.table, NOTINDEX, 256, sizeof(size_t))) 181 | return false; 182 | 183 | struct keybind k = { 184 | .defaults = syntax, 185 | .function = fun->function, 186 | .arg = arg, 187 | .owner = caller, 188 | }; 189 | 190 | if (!chck_string_set_cstr(&k.name, name, true)) 191 | return false; 192 | 193 | size_t index; 194 | if (!chck_pool_add(&plugin.keybinds.pool, &k, &index)) 195 | goto error0; 196 | 197 | struct chck_string mappings = {0}; 198 | bool mapped = false; 199 | 200 | if (configuration_get) { 201 | struct chck_string key = {0}; 202 | int name_start, name_end; 203 | chck_string_set_format(&key, "/keybindings/%n%s%n/mappings", &name_start, name, &name_end); 204 | 205 | /* Configuration keys may not contain spaces, so replace spaces with underscores */ 206 | chck_cstr_replace_char(key.data, ' ', '_'); 207 | 208 | const char *value; 209 | if (configuration_get(key.data, 's', &value)) { 210 | add_keybind_mapping(&mappings, value, &index); 211 | mapped = true; 212 | } 213 | 214 | chck_string_release(&key); 215 | } 216 | 217 | /* If no mapping was set from configuration, try to use default keybindings */ 218 | if (!mapped) { 219 | for (uint32_t i = 0; syntax && syntax[i]; ++i) 220 | add_keybind_mapping(&mappings, syntax[i], &index); 221 | } 222 | 223 | plog(plugin.self, PLOG_INFO, "Added keybind: %s (%s)", name, (chck_string_is_empty(&mappings) ? "none" : mappings.data)); 224 | chck_string_release(&mappings); 225 | return true; 226 | 227 | error0: 228 | chck_string_release(&k.name); 229 | return false; 230 | } 231 | 232 | static void 233 | keybind_release(struct keybind *keybind) 234 | { 235 | if (!keybind) 236 | return; 237 | 238 | chck_string_release(&keybind->name); 239 | } 240 | 241 | static void 242 | remove_keybind(plugin_h caller, const char *name) 243 | { 244 | struct keybind *k; 245 | chck_pool_for_each(&plugin.keybinds.pool, k) { 246 | if (k->owner != caller || !chck_string_eq_cstr(&k->name, name)) 247 | continue; 248 | 249 | plog(plugin.self, PLOG_INFO, "Removed keybind: %s", k->name.data); 250 | keybind_release(k); 251 | chck_pool_remove(&plugin.keybinds.pool, _I - 1); 252 | break; 253 | } 254 | } 255 | 256 | static void 257 | remove_keybinds_for_plugin(plugin_h caller) 258 | { 259 | struct keybind *k; 260 | chck_pool_for_each(&plugin.keybinds.pool, k) { 261 | if (k->owner != caller) 262 | continue; 263 | 264 | plog(plugin.self, PLOG_INFO, "Removed keybind: %s", k->name.data); 265 | keybind_release(k); 266 | chck_pool_remove(&plugin.keybinds.pool, _I - 1); 267 | } 268 | } 269 | 270 | static void 271 | remove_keybinds(void) 272 | { 273 | chck_pool_for_each_call(&plugin.keybinds.pool, keybind_release); 274 | chck_pool_release(&plugin.keybinds.pool); 275 | chck_hash_table_release(&plugin.keybinds.table); 276 | } 277 | 278 | static void 279 | plugin_deloaded(plugin_h ph) 280 | { 281 | remove_keybinds_for_plugin(ph); 282 | } 283 | 284 | static bool 285 | pass_key(wlc_handle view, uint32_t time, const struct wlc_modifiers *modifiers, const char name[64], bool pressed) 286 | { 287 | assert(modifiers); 288 | 289 | bool handled = false; 290 | 291 | struct chck_string syntax = {0}, prefixed = {0}; 292 | if (!append_mods(&syntax, &prefixed, modifiers->mods)) 293 | goto out; 294 | 295 | syntax_append(&syntax, name, true); 296 | chck_string_set_format(&syntax, "<%s>", syntax.data); 297 | 298 | if (!chck_string_is_empty(&prefixed)) { 299 | syntax_append(&prefixed, name, true); 300 | chck_string_set_format(&prefixed, "<%s>", prefixed.data); 301 | } 302 | 303 | plog(plugin.self, PLOG_INFO, "%s combo: %s %s", (pressed ? "pressed" : "released"), syntax.data, prefixed.data); 304 | 305 | const struct keybind *k; 306 | if (!(k = keybind_for_syntax(prefixed.data)) && !(k = keybind_for_syntax(syntax.data))) 307 | goto out; 308 | 309 | if (pressed) 310 | k->function(view, time, k->arg); 311 | 312 | handled = true; 313 | 314 | out: 315 | chck_string_release(&syntax); 316 | chck_string_release(&prefixed); 317 | return handled; 318 | } 319 | 320 | static bool 321 | keyboard_key(wlc_handle view, uint32_t time, const struct wlc_modifiers *modifiers, uint32_t key, enum wlc_key_state state) 322 | { 323 | (void)key; 324 | 325 | const uint32_t sym = wlc_keyboard_get_keysym_for_key(key, NULL); 326 | const uint32_t u32 = wlc_keyboard_get_utf32_for_key(key, NULL); 327 | 328 | char name[64] = {0}; 329 | const uint8_t mb = chck_utf32_encode(u32, name); 330 | if (!mb || (mb == 1 && (!isprint(name[0]) || isspace(name[0])))) { 331 | if (xkb_keysym_get_name(sym, name, sizeof(name)) == -1) 332 | return false; 333 | } 334 | 335 | const bool pressed = (state == WLC_KEY_STATE_PRESSED); 336 | return pass_key(view, time, modifiers, name, pressed); 337 | } 338 | 339 | static bool 340 | pointer_button(wlc_handle view, uint32_t time, const struct wlc_modifiers *modifiers, uint32_t button, enum wlc_button_state state, const struct wlc_point *point) 341 | { 342 | (void)point; 343 | 344 | bool handled = false; 345 | struct chck_string name = {0}; 346 | if (!chck_string_set_format(&name, "B%u", button - BTN_MOUSE)) 347 | goto out; 348 | 349 | const bool pressed = (state == WLC_BUTTON_STATE_PRESSED); 350 | handled = pass_key(view, time, modifiers, name.data, pressed); 351 | 352 | out: 353 | chck_string_release(&name); 354 | return (modifiers->mods ? handled : false); 355 | } 356 | 357 | static const char* 358 | load_prefix(plugin_h self) 359 | { 360 | plugin_h configuration; 361 | if (!(configuration = import_plugin(self, "configuration")) || 362 | !(configuration_get = import_method(self, configuration, "get", "b(c[],c,v)|1"))) 363 | return NULL; 364 | 365 | const char *prefix; 366 | return (configuration_get("/keybindings/prefix", 's', &prefix) ? prefix : NULL); 367 | } 368 | 369 | #pragma GCC diagnostic ignored "-Wmissing-prototypes" 370 | 371 | void 372 | plugin_deinit(plugin_h self) 373 | { 374 | (void)self; 375 | remove_keybinds(); 376 | } 377 | 378 | bool 379 | plugin_init(plugin_h self) 380 | { 381 | plugin.self = self; 382 | 383 | plugin_h orbment; 384 | if (!(orbment = import_plugin(self, "orbment"))) 385 | return false; 386 | 387 | if (!(add_hook = import_method(self, orbment, "add_hook", "b(h,c[],fun)|1"))) 388 | return false; 389 | 390 | plugin.prefix = parse_prefix(load_prefix(self)); 391 | 392 | return (add_hook(self, "plugin.deloaded", FUN(plugin_deloaded, "v(h)|1")) && 393 | add_hook(self, "keyboard.key", FUN(keyboard_key, "b(h,u32,*,u32,e)|1")) && 394 | add_hook(self, "pointer.button", FUN(pointer_button, "b(h,u32,*,u32,e,*)|1"))); 395 | } 396 | 397 | PCONST const struct plugin_info* 398 | plugin_register(void) 399 | { 400 | static const struct method methods[] = { 401 | REGISTER_METHOD(add_keybind, "b(h,c[],c*[],fun,ip)|1"), 402 | REGISTER_METHOD(remove_keybind, "v(h,c[])|1"), 403 | {0}, 404 | }; 405 | 406 | static const char *after[] = { 407 | "configuration", 408 | NULL, 409 | }; 410 | 411 | static const struct plugin_info info = { 412 | .name = "keybind", 413 | .description = "Keybind api.", 414 | .version = VERSION, 415 | .methods = methods, 416 | .after = after, 417 | }; 418 | 419 | return &info; 420 | } 421 | -------------------------------------------------------------------------------- /plugins/layout/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_library(orbment-plugin-layout MODULE layout.c) 2 | target_link_libraries(orbment-plugin-layout PRIVATE ${ORBMENT_LIBRARIES} ${CHCK_LIBRARIES}) 3 | add_plugins(orbment-plugin-layout) 4 | -------------------------------------------------------------------------------- /plugins/layout/layout.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include "common.h" 7 | #include "config.h" 8 | 9 | static const size_t NOTINDEX = (size_t)-1; 10 | 11 | typedef void (*keybind_fun_t)(wlc_handle view, uint32_t time, intptr_t arg); 12 | static bool (*add_keybind)(plugin_h, const char *name, const char **syntax, const struct function*, intptr_t arg); 13 | static bool (*add_hook)(plugin_h, const char *name, const struct function*); 14 | 15 | typedef void (*layout_fun_t)(const struct wlc_geometry *region, const wlc_handle *views, size_t memb); 16 | struct layout { 17 | struct chck_string name; 18 | layout_fun_t function; 19 | plugin_h owner; 20 | }; 21 | 22 | static struct { 23 | struct { 24 | // simplest data structure to cycle 25 | // there usually isn't many layouts so linear search is fast enough. 26 | // contigous arrays are very fast. 27 | struct chck_iter_pool pool; 28 | struct chck_hash_table active; 29 | } layouts; 30 | 31 | plugin_h self; 32 | } plugin; 33 | 34 | static size_t 35 | layout_index_for_output(wlc_handle output) 36 | { 37 | if (!plugin.layouts.active.lut.table) 38 | return 0; 39 | 40 | const char *name = wlc_output_get_name(output); 41 | const size_t *index = chck_hash_table_str_get(&plugin.layouts.active, name, strlen(name)); 42 | return (index ? *index : 0); 43 | } 44 | 45 | static struct layout* 46 | layout_for_output(wlc_handle output) 47 | { 48 | return chck_iter_pool_get(&plugin.layouts.pool, layout_index_for_output(output)); 49 | } 50 | 51 | static void 52 | set_index_for_output(wlc_handle output, size_t index) 53 | { 54 | if (!plugin.layouts.active.lut.table && !chck_hash_table(&plugin.layouts.active, 0, 8, sizeof(size_t))) 55 | return; 56 | 57 | const char *name = wlc_output_get_name(output); 58 | chck_hash_table_str_set(&plugin.layouts.active, name, strlen(name), &index); 59 | } 60 | 61 | static void 62 | next_layout(wlc_handle output, size_t offset, enum direction dir) 63 | { 64 | size_t index = layout_index_for_output(output), memb = plugin.layouts.pool.items.count; 65 | index = (dir == PREV ? chck_clampsz(index - offset, 0, memb - 1) : index + offset) % chck_maxsz(memb, 1); 66 | set_index_for_output(output, index); 67 | } 68 | 69 | static bool 70 | layout_exists(const char *name) 71 | { 72 | const struct layout *l; 73 | chck_iter_pool_for_each(&plugin.layouts.pool, l) { 74 | if (chck_string_eq_cstr(&l->name, name)) 75 | return true; 76 | } 77 | return false; 78 | } 79 | 80 | static bool 81 | add_layout(plugin_h caller, const char *name, const struct function *fun) 82 | { 83 | if (!name || !fun || !caller) 84 | return false; 85 | 86 | static const char *signature = "v(*,h[],sz)|1"; 87 | 88 | if (!chck_cstreq(fun->signature, signature)) { 89 | plog(plugin.self, PLOG_WARN, "Wrong signature provided for '%s layout' function. (%s != %s)", name, signature, fun->signature); 90 | return false; 91 | } 92 | 93 | if (layout_exists(name)) { 94 | plog(plugin.self, PLOG_WARN, "Layout with name '%s' already exists", name); 95 | return false; 96 | } 97 | 98 | if (!plugin.layouts.pool.items.member && !chck_iter_pool(&plugin.layouts.pool, 32, 0, sizeof(struct layout))) 99 | return false; 100 | 101 | struct layout l = { 102 | .function = fun->function, 103 | .owner = caller, 104 | }; 105 | 106 | if (!chck_string_set_cstr(&l.name, name, true)) 107 | return false; 108 | 109 | if (!chck_iter_pool_push_back(&plugin.layouts.pool, &l)) 110 | goto error0; 111 | 112 | plog(plugin.self, PLOG_INFO, "Added layout: %s", name); 113 | return true; 114 | 115 | error0: 116 | chck_string_release(&l.name); 117 | return false; 118 | } 119 | 120 | static void 121 | layout_release(struct layout *layout) 122 | { 123 | if (!layout) 124 | return; 125 | 126 | chck_string_release(&layout->name); 127 | } 128 | 129 | static void 130 | remove_layout_ptr(struct layout *layout, size_t index) 131 | { 132 | plog(plugin.self, PLOG_INFO, "Removed layout: %s", layout->name.data); 133 | layout_release(layout); 134 | chck_iter_pool_remove(&plugin.layouts.pool, index); 135 | 136 | size_t *i; 137 | chck_hash_table_for_each(&plugin.layouts.active, i) { 138 | if (*i >= index) 139 | *i = 0; 140 | } 141 | } 142 | 143 | static void 144 | remove_layout(plugin_h caller, const char *name) 145 | { 146 | struct layout *l; 147 | chck_iter_pool_for_each(&plugin.layouts.pool, l) { 148 | if (l->owner != caller || !chck_string_eq_cstr(&l->name, name)) 149 | continue; 150 | 151 | remove_layout_ptr(l, _I - 1); 152 | break; 153 | } 154 | } 155 | 156 | static void 157 | remove_layouts_for_plugin(plugin_h caller) 158 | { 159 | struct layout *l; 160 | chck_iter_pool_for_each(&plugin.layouts.pool, l) { 161 | if (l->owner != caller) 162 | continue; 163 | 164 | remove_layout_ptr(l, _I - 1); 165 | --_I; 166 | } 167 | } 168 | 169 | static void 170 | remove_layouts(void) 171 | { 172 | chck_iter_pool_for_each_call(&plugin.layouts.pool, layout_release); 173 | chck_iter_pool_release(&plugin.layouts.pool); 174 | chck_hash_table_release(&plugin.layouts.active); 175 | } 176 | 177 | static void 178 | layout_parent(wlc_handle view, wlc_handle parent, const struct wlc_size *size) 179 | { 180 | assert(view && parent); 181 | 182 | // Size to fit the undermost parent 183 | // TODO: Use surface height as base instead of current 184 | wlc_handle under; 185 | for (under = parent; under && wlc_view_get_parent(under); under = wlc_view_get_parent(under)); 186 | 187 | // Undermost view and parent view geometry 188 | const struct wlc_geometry *u = wlc_view_get_geometry(under); 189 | const struct wlc_geometry *p = wlc_view_get_geometry(parent); 190 | 191 | // Current constrained size 192 | const float cw = chck_maxf(size->w, u->size.w * 0.6); 193 | const float ch = chck_maxf(size->h, u->size.h * 0.6); 194 | 195 | struct wlc_geometry g; 196 | g.size.w = chck_minf(cw, u->size.w * 0.8); 197 | g.size.h = chck_minf(ch, u->size.h * 0.8); 198 | g.origin.x = p->size.w * 0.5 - g.size.w * 0.5; 199 | g.origin.y = p->size.h * 0.5 - g.size.h * 0.5; 200 | wlc_view_set_geometry(view, 0, &g); 201 | } 202 | 203 | static void 204 | relayout(wlc_handle output) 205 | { 206 | const struct wlc_size *r; 207 | if (!(r = wlc_output_get_virtual_resolution(output))) 208 | return; 209 | 210 | size_t memb; 211 | const wlc_handle *views = wlc_output_get_views(output, &memb); 212 | for (size_t i = 0; i < memb; ++i) { 213 | if (wlc_output_get_mask(output) != wlc_view_get_mask(views[i])) 214 | continue; 215 | 216 | if (wlc_view_get_type(views[i]) & BIT_BEMENU) { 217 | struct wlc_geometry g = *wlc_view_get_geometry(views[i]); 218 | g.origin = (struct wlc_point){ 0, 0 }; 219 | wlc_view_set_geometry(views[i], 0, &g); 220 | } 221 | 222 | if (wlc_view_get_state(views[i]) & WLC_BIT_FULLSCREEN) 223 | wlc_view_set_geometry(views[i], 0, &(struct wlc_geometry){ { 0, 0 }, *r }); 224 | 225 | if (wlc_view_get_type(views[i]) & WLC_BIT_SPLASH) { 226 | struct wlc_geometry g = *wlc_view_get_geometry(views[i]); 227 | g.origin = (struct wlc_point){ r->w * 0.5 - g.size.w * 0.5, r->h * 0.5 - g.size.h * 0.5 }; 228 | wlc_view_set_geometry(views[i], 0, &g); 229 | } 230 | 231 | wlc_handle parent; 232 | if (is_managed(views[i]) && !is_popup(views[i]) && !is_or(views[i]) && (parent = wlc_view_get_parent(views[i]))) 233 | layout_parent(views[i], parent, &wlc_view_get_geometry(views[i])->size); 234 | } 235 | 236 | struct layout *layout; 237 | if ((layout = layout_for_output(output))) { 238 | struct chck_iter_pool tiled = {{0}}; 239 | if (!chck_iter_pool(&tiled, memb, memb, sizeof(wlc_handle))) 240 | return; 241 | 242 | size_t memb; 243 | const wlc_handle *views = wlc_output_get_mutable_views(output, &memb); 244 | for (size_t i = 0; i < memb; ++i) { 245 | if (is_tiled(views[i]) && wlc_output_get_mask(output) == wlc_view_get_mask(views[i])) { 246 | wlc_view_set_state(views[i], WLC_BIT_MAXIMIZED, true); 247 | chck_iter_pool_push_back(&tiled, &views[i]); 248 | } 249 | } 250 | 251 | layout->function(&(struct wlc_geometry){ { 0, 0 }, *r }, (void*)tiled.items.buffer, tiled.items.count); 252 | chck_iter_pool_release(&tiled); 253 | } 254 | } 255 | 256 | static void 257 | output_resolution(wlc_handle output, const struct wlc_size *from, const struct wlc_size *to) 258 | { 259 | (void)output, (void)from, (void)to; 260 | relayout(output); 261 | } 262 | 263 | static void 264 | cycle_output(wlc_handle output, enum direction dir) 265 | { 266 | size_t memb; 267 | wlc_handle *views = wlc_output_get_mutable_views(output, &memb); 268 | if (memb < 2) 269 | return; 270 | 271 | switch (dir) { 272 | case NEXT: 273 | { 274 | size_t last = NOTINDEX; 275 | for (size_t i = 0; i < memb; ++i) { 276 | if (!is_tiled(views[i]) || wlc_view_get_mask(views[i]) != wlc_output_get_mask(output)) 277 | continue; 278 | 279 | if (last != NOTINDEX) { 280 | wlc_handle tmp = views[last]; 281 | views[last] = views[i]; 282 | views[i] = tmp; 283 | } 284 | 285 | last = i; 286 | } 287 | } 288 | break; 289 | 290 | case PREV: 291 | { 292 | size_t last = NOTINDEX; 293 | for (size_t i = memb; i > 0; --i) { 294 | if (!is_tiled(views[i - 1]) || wlc_view_get_mask(views[i - 1]) != wlc_output_get_mask(output)) 295 | continue; 296 | 297 | if (last != NOTINDEX) { 298 | wlc_handle tmp = views[last]; 299 | views[last] = views[i - 1]; 300 | views[i - 1] = tmp; 301 | } 302 | 303 | last = i - 1; 304 | } 305 | } 306 | break; 307 | } 308 | relayout(output); 309 | } 310 | 311 | static void 312 | view_geometry_request(wlc_handle view, const struct wlc_geometry *geometry) 313 | { 314 | const uint32_t type = wlc_view_get_type(view); 315 | const uint32_t state = wlc_view_get_state(view); 316 | const bool tiled = is_tiled(view); 317 | const bool action = ((state & WLC_BIT_RESIZING) || (state & WLC_BIT_MOVING)); 318 | 319 | if (tiled && !action) 320 | return; 321 | 322 | if (tiled) 323 | wlc_view_set_state(view, WLC_BIT_MAXIMIZED, false); 324 | 325 | if ((state & WLC_BIT_FULLSCREEN) || (type & WLC_BIT_SPLASH)) 326 | return; 327 | 328 | wlc_handle parent; 329 | if (is_managed(view) && !is_popup(view) && !is_or(view) && (parent = wlc_view_get_parent(view))) { 330 | layout_parent(view, parent, &geometry->size); 331 | } else { 332 | wlc_view_set_geometry(view, 0, geometry); 333 | } 334 | } 335 | 336 | static void 337 | view_state_request(wlc_handle view, const enum wlc_view_state_bit state, const bool toggle) 338 | { 339 | wlc_view_set_state(view, state, toggle); 340 | 341 | switch (state) { 342 | case WLC_BIT_MAXIMIZED: 343 | if (toggle) 344 | relayout(wlc_view_get_output(view)); 345 | break; 346 | case WLC_BIT_FULLSCREEN: 347 | relayout(wlc_view_get_output(view)); 348 | break; 349 | default: break; 350 | } 351 | } 352 | 353 | static void 354 | key_cb_cycle_clients(wlc_handle view, uint32_t time, intptr_t arg) 355 | { 356 | (void)view, (void)time, (void)arg; 357 | cycle_output(wlc_get_focused_output(), NEXT); 358 | } 359 | 360 | static void 361 | key_cb_next_layout(wlc_handle view, uint32_t time, intptr_t arg) 362 | { 363 | (void)view, (void)time, (void)arg; 364 | next_layout(wlc_get_focused_output(), 1, NEXT); 365 | relayout(wlc_get_focused_output()); 366 | } 367 | 368 | static void 369 | plugin_deloaded(plugin_h ph) 370 | { 371 | remove_layouts_for_plugin(ph); 372 | } 373 | 374 | static const struct { 375 | const char *name, **syntax; 376 | keybind_fun_t function; 377 | } keybinds[] = { 378 | { "cycle clients", (const char*[]){ "", NULL }, key_cb_cycle_clients }, 379 | { "next layout", (const char*[]){ "", NULL }, key_cb_next_layout }, 380 | {0}, 381 | }; 382 | 383 | #pragma GCC diagnostic ignored "-Wmissing-prototypes" 384 | 385 | void 386 | plugin_deinit(plugin_h self) 387 | { 388 | (void)self; 389 | remove_layouts(); 390 | } 391 | 392 | bool 393 | plugin_init(plugin_h self) 394 | { 395 | plugin.self = self; 396 | 397 | plugin_h orbment, keybind; 398 | if (!(orbment = import_plugin(self, "orbment")) || 399 | !(keybind = import_plugin(self, "keybind"))) 400 | return false; 401 | 402 | if (!(add_hook = import_method(self, orbment, "add_hook", "b(h,c[],fun)|1")) || 403 | !(add_keybind = import_method(self, keybind, "add_keybind", "b(h,c[],c*[],fun,ip)|1"))) 404 | return false; 405 | 406 | for (size_t i = 0; keybinds[i].name; ++i) 407 | if (!add_keybind(self, keybinds[i].name, keybinds[i].syntax, FUN(keybinds[i].function, "v(h,u32,ip)|1"), 0)) 408 | return false; 409 | 410 | return (add_hook(self, "plugin.deloaded", FUN(plugin_deloaded, "v(h)|1")) && 411 | add_hook(self, "output.resolution", FUN(output_resolution, "v(h,*,*)|1")) && 412 | add_hook(self, "view.geometry_request", FUN(view_geometry_request, "v(h,*)|1")) && 413 | add_hook(self, "view.state_request", FUN(view_state_request, "v(h,e,b)|1"))); 414 | } 415 | 416 | PCONST const struct plugin_info* 417 | plugin_register(void) 418 | { 419 | static const struct method methods[] = { 420 | REGISTER_METHOD(relayout, "v(h)|1"), 421 | REGISTER_METHOD(add_layout, "b(h,c[],fun)|1"), 422 | REGISTER_METHOD(remove_layout, "v(h,c[])|1"), 423 | {0}, 424 | }; 425 | 426 | static const char *requires[] = { 427 | "keybind", 428 | NULL, 429 | }; 430 | 431 | static const struct plugin_info info = { 432 | .name = "layout", 433 | .description = "Layout api.", 434 | .version = VERSION, 435 | .methods = methods, 436 | .requires = requires, 437 | }; 438 | 439 | return &info; 440 | } 441 | -------------------------------------------------------------------------------- /src/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | find_package(Math REQUIRED) 2 | 3 | include_directories( 4 | ${WLC_INCLUDE_DIRS} 5 | ${CHCK_INCLUDE_DIRS} 6 | ${PROJECT_SOURCE_DIR}/include 7 | ${CMAKE_CURRENT_BINARY_DIR} 8 | ) 9 | 10 | set(sources 11 | log.c 12 | plugin.c 13 | hooks.c 14 | signals.c 15 | orbment.c 16 | ) 17 | 18 | configure_file(config.h.in config.h @ONLY) 19 | 20 | add_executable(orbment ${sources}) 21 | target_link_libraries(orbment PRIVATE ${CHCK_LIBRARIES} ${WLC_LIBRARIES} ${MATH_LIBRARY} ${CMAKE_DL_LIBS}) 22 | 23 | # Install rules 24 | install(TARGETS orbment DESTINATION "${CMAKE_INSTALL_BINDIR}") 25 | 26 | set(ORBMENT_LIBRARIES "" CACHE STRING "Libraries for linking Orbment plugins") 27 | set(ORBMENT_INCLUDE_DIRS "${PROJECT_SOURCE_DIR}/include" CACHE STRING "Include directories of Orbment") 28 | -------------------------------------------------------------------------------- /src/config.h.in: -------------------------------------------------------------------------------- 1 | #ifndef __orbment_config__ 2 | #define __orbment_config__ 3 | 4 | #define VERSION "@PROJECT_VERSION@" 5 | #define PLUGINS_PATH "@ORBMENT_PLUGINS_FULL_PATH@" 6 | 7 | #endif /* __orbment_config__ */ 8 | -------------------------------------------------------------------------------- /src/hooks.c: -------------------------------------------------------------------------------- 1 | #include "hooks.h" 2 | #include 3 | #include 4 | #include "plugin.h" 5 | #include "config.h" 6 | 7 | enum hook_type { 8 | HOOK_PLUGIN_LOADED, 9 | HOOK_PLUGIN_DELOADED, 10 | HOOK_OUTPUT_CREATED, 11 | HOOK_OUTPUT_DESTROYED, 12 | HOOK_OUTPUT_FOCUS, 13 | HOOK_OUTPUT_RESOLUTION, 14 | HOOK_VIEW_CREATED, 15 | HOOK_VIEW_DESTROYED, 16 | HOOK_VIEW_FOCUS, 17 | HOOK_VIEW_MOVE_TO_OUTPUT, 18 | HOOK_VIEW_GEOMETRY_REQUEST, 19 | HOOK_VIEW_STATE_REQUEST, 20 | HOOK_VIEW_MOVE_REQUEST, 21 | HOOK_VIEW_RESIZE_REQUEST, 22 | HOOK_KEYBOARD_KEY, 23 | HOOK_POINTER_BUTTON, 24 | HOOK_POINTER_SCROLL, 25 | HOOK_POINTER_MOTION, 26 | HOOK_TOUCH, 27 | HOOK_COMPOSITOR_READY, 28 | HOOK_INPUT_CREATED, 29 | HOOK_INPUT_DESTROYED, 30 | HOOK_OUTPUT_PRE_RENDER, 31 | HOOK_OUTPUT_POST_RENDER, 32 | HOOK_VIEW_PRE_RENDER, 33 | HOOK_VIEW_POST_RENDER, 34 | HOOK_LAST, 35 | }; 36 | 37 | struct hook { 38 | void *function; 39 | plugin_h owner; 40 | }; 41 | 42 | static struct chck_iter_pool hooks[HOOK_LAST]; 43 | 44 | static enum hook_type 45 | hook_type_for_string(const char *type) 46 | { 47 | struct { 48 | const char *name; 49 | enum hook_type type; 50 | } map[] = { 51 | { "plugin.loaded", HOOK_PLUGIN_LOADED }, 52 | { "plugin.deloaded", HOOK_PLUGIN_DELOADED }, 53 | { "output.created", HOOK_OUTPUT_CREATED }, 54 | { "output.destroyed", HOOK_OUTPUT_DESTROYED }, 55 | { "output.focus", HOOK_OUTPUT_FOCUS }, 56 | { "output.resolution", HOOK_OUTPUT_RESOLUTION }, 57 | { "view.created", HOOK_VIEW_CREATED }, 58 | { "view.destroyed", HOOK_VIEW_DESTROYED }, 59 | { "view.focus", HOOK_VIEW_FOCUS }, 60 | { "view.move_to_output", HOOK_VIEW_MOVE_TO_OUTPUT }, 61 | { "view.geometry_request", HOOK_VIEW_GEOMETRY_REQUEST }, 62 | { "view.state_request", HOOK_VIEW_STATE_REQUEST }, 63 | { "view.move_request", HOOK_VIEW_MOVE_REQUEST }, 64 | { "view.resize_request", HOOK_VIEW_RESIZE_REQUEST }, 65 | { "keyboard.key", HOOK_KEYBOARD_KEY }, 66 | { "pointer.button", HOOK_POINTER_BUTTON }, 67 | { "pointer.scroll", HOOK_POINTER_SCROLL }, 68 | { "pointer.motion", HOOK_POINTER_MOTION }, 69 | { "touch", HOOK_TOUCH }, 70 | { "compositor.ready", HOOK_COMPOSITOR_READY }, 71 | { "input.created", HOOK_INPUT_CREATED }, 72 | { "input.destroyed", HOOK_INPUT_DESTROYED }, 73 | { "output.pre_render", HOOK_OUTPUT_PRE_RENDER }, 74 | { "output.post_render", HOOK_OUTPUT_POST_RENDER }, 75 | { "view.pre_render", HOOK_VIEW_PRE_RENDER }, 76 | { "view.post_render", HOOK_VIEW_POST_RENDER }, 77 | { NULL, HOOK_LAST }, 78 | }; 79 | 80 | for (uint32_t i = 0; map[i].name; ++i) { 81 | if (chck_cstreq(type, map[i].name)) 82 | return map[i].type; 83 | } 84 | 85 | return HOOK_LAST; 86 | } 87 | 88 | static bool 89 | hook_exists_for_plugin(plugin_h caller, enum hook_type t) 90 | { 91 | struct hook *h; 92 | chck_iter_pool_for_each(&hooks[t], h) { 93 | if (h->owner == caller) 94 | return true; 95 | } 96 | return false; 97 | } 98 | 99 | static bool 100 | add_hook(plugin_h caller, const char *type, const struct function *hook) 101 | { 102 | if (!hook || !caller) 103 | return false; 104 | 105 | enum hook_type t; 106 | if ((t = hook_type_for_string(type)) == HOOK_LAST) { 107 | plog(0, PLOG_WARN, "Invalid type '%s' provided for hook.", type); 108 | return false; 109 | } 110 | 111 | if (hook_exists_for_plugin(caller, t)) { 112 | plog(0, PLOG_WARN, "Hook of type '%s' already exists for plugin.", type); 113 | return false; 114 | } 115 | 116 | static const char *signatures[HOOK_LAST] = { 117 | "v(h)|1", // HOOK_PLUGIN_LOADED 118 | "v(h)|1", // HOOK_PLUGIN_DELOADED 119 | "b(h)|1", // HOOK_OUTPUT_CREATED 120 | "v(h)|1", // HOOK_OUTPUT_DESTROYED 121 | "v(h,b)|1", // HOOK_OUTPUT_FOCUS 122 | "v(h,*,*)|1", // HOOK_OUTPUT_RESOLUTION 123 | "b(h)|1", // HOOK_VIEW_CREATED 124 | "v(h)|1", // HOOK_VIEW_DESTROYED 125 | "v(h,b)|1", // HOOK_VIEW_FOCUS 126 | "v(h,h,h)|1", // HOOK_VIEW_MOVE_TO_OUTPUT 127 | "v(h,*)|1", // HOOK_VIEW_GEOMETRY_REQUEST 128 | "v(h,e,b)|1", // HOOK_VIEW_STATE_REQUEST 129 | "v(h,*)|1", // HOOK_VIEW_MOVE_REQUEST 130 | "v(h,u32,*)|1", // HOOK_VIEW_RESIZE_REQUEST 131 | "b(h,u32,*,u32,e)|1", // HOOK_KEYBOARD_KEY 132 | "b(h,u32,*,u32,e,*)|1", // HOOK_POINTER_BUTTON 133 | "b(h,u32,*,u8,d[2])|1", // HOOK_POINTER_SCROLL 134 | "b(h,u32,*)|1", // HOOK_POINTER_MOTION 135 | "b(h,u32,*,e,i32,*)|1", // HOOK_TOUCH 136 | "v(v)|1", // HOOK_COMPOSITOR_READY 137 | "b(*)|1", // HOOK_INPUT_CREATED 138 | "v(*)|1", // HOOK_INPUT_DESTROYED 139 | "v(h)|1", // HOOK_OUTPUT_PRE_RENDER 140 | "v(h)|1", // HOOK_OUTPUT_POST_RENDER 141 | "v(h)|1", // HOOK_VIEW_PRE_RENDER 142 | "v(h)|1", // HOOK_VIEW_POST_RENDER 143 | }; 144 | 145 | if (!chck_cstreq(hook->signature, signatures[t])) { 146 | plog(0, PLOG_WARN, "Wrong signature provided for hook '%s'. (%s != %s)", type, signatures[t], hook->signature); 147 | return false; 148 | } 149 | 150 | if (!hooks[t].items.member && !chck_iter_pool(&hooks[t], 4, 0, sizeof(struct hook))) 151 | return false; 152 | 153 | struct hook h = { 154 | .function = hook->function, 155 | .owner = caller, 156 | }; 157 | 158 | return chck_iter_pool_push_back(&hooks[t], &h); 159 | } 160 | 161 | static void 162 | remove_hook(plugin_h caller, const char *type) 163 | { 164 | if (!type || !caller) 165 | return; 166 | 167 | enum hook_type t; 168 | if ((t = hook_type_for_string(type)) == HOOK_LAST) { 169 | plog(0, PLOG_WARN, "Invalid type '%s' provided for hook.", type); 170 | return; 171 | } 172 | 173 | struct hook *h; 174 | chck_iter_pool_for_each(&hooks[t], h) { 175 | if (h->owner != caller) 176 | continue; 177 | 178 | chck_iter_pool_remove(&hooks[t], _I - 1); 179 | break; 180 | } 181 | } 182 | 183 | static void 184 | remove_hooks_for_plugin(plugin_h caller) 185 | { 186 | for (uint32_t i = 0; i < HOOK_LAST; ++i) { 187 | struct hook *h; 188 | chck_iter_pool_for_each(&hooks[i], h) { 189 | if (h->owner != caller) 190 | continue; 191 | 192 | chck_iter_pool_remove(&hooks[i], _I - 1); 193 | break; 194 | } 195 | } 196 | } 197 | 198 | static void 199 | hooks_remove_all(void) 200 | { 201 | for (uint32_t i = 0; i < HOOK_LAST; ++i) 202 | chck_iter_pool_release(&hooks[i]); 203 | } 204 | 205 | static void 206 | plugin_loaded(const struct plugin *plugin) 207 | { 208 | assert(plugin); 209 | 210 | struct hook *hook; 211 | chck_iter_pool_for_each(&hooks[HOOK_PLUGIN_LOADED], hook) { 212 | void (*fun)() = hook->function; 213 | fun(plugin->handle + 1); 214 | } 215 | } 216 | 217 | static void 218 | plugin_deloaded(const struct plugin *plugin) 219 | { 220 | assert(plugin); 221 | 222 | struct hook *hook; 223 | chck_iter_pool_for_each(&hooks[HOOK_PLUGIN_DELOADED], hook) { 224 | void (*fun)() = hook->function; 225 | fun(plugin->handle + 1); 226 | } 227 | 228 | remove_hooks_for_plugin(plugin->handle + 1); 229 | } 230 | 231 | static bool 232 | output_created(wlc_handle output) 233 | { 234 | bool created = true; 235 | struct hook *hook; 236 | chck_iter_pool_for_each(&hooks[HOOK_OUTPUT_CREATED], hook) { 237 | bool (*fun)() = hook->function; 238 | if (!fun(output)) 239 | created = false; 240 | } 241 | 242 | if (!created) 243 | plog(0, PLOG_ERROR, "output.created hook failed for output %" PRIuWLC, output); 244 | 245 | return created; 246 | } 247 | 248 | static void 249 | output_destroyed(wlc_handle output) 250 | { 251 | struct hook *hook; 252 | chck_iter_pool_for_each(&hooks[HOOK_OUTPUT_DESTROYED], hook) { 253 | void (*fun)() = hook->function; 254 | fun(output); 255 | } 256 | } 257 | 258 | static void 259 | output_focus(wlc_handle output, bool focus) 260 | { 261 | struct hook *hook; 262 | chck_iter_pool_for_each(&hooks[HOOK_OUTPUT_FOCUS], hook) { 263 | void (*fun)() = hook->function; 264 | fun(output, focus); 265 | } 266 | } 267 | 268 | static void 269 | output_resolution(wlc_handle output, const struct wlc_size *from, const struct wlc_size *to) 270 | { 271 | struct hook *hook; 272 | chck_iter_pool_for_each(&hooks[HOOK_OUTPUT_RESOLUTION], hook) { 273 | void (*fun)() = hook->function; 274 | fun(output, from, to); 275 | } 276 | } 277 | 278 | static void 279 | output_pre_render(wlc_handle output) 280 | { 281 | struct hook *hook; 282 | chck_iter_pool_for_each(&hooks[HOOK_OUTPUT_PRE_RENDER], hook) { 283 | void (*fun)() = hook->function; 284 | fun(output); 285 | } 286 | } 287 | 288 | static void 289 | output_post_render(wlc_handle output) 290 | { 291 | struct hook *hook; 292 | chck_iter_pool_for_each(&hooks[HOOK_OUTPUT_POST_RENDER], hook) { 293 | void (*fun)() = hook->function; 294 | fun(output); 295 | } 296 | } 297 | 298 | static bool 299 | view_created(wlc_handle view) 300 | { 301 | bool created = true; 302 | struct hook *hook; 303 | chck_iter_pool_for_each(&hooks[HOOK_VIEW_CREATED], hook) { 304 | bool (*fun)() = hook->function; 305 | if (!fun(view)) 306 | created = false; 307 | } 308 | 309 | if (!created) 310 | plog(0, PLOG_ERROR, "view.created hook failed for view %" PRIuWLC, view); 311 | 312 | return created; 313 | } 314 | 315 | static void 316 | view_destroyed(wlc_handle view) 317 | { 318 | struct hook *hook; 319 | chck_iter_pool_for_each(&hooks[HOOK_VIEW_DESTROYED], hook) { 320 | void (*fun)() = hook->function; 321 | fun(view); 322 | } 323 | } 324 | 325 | static void 326 | view_focus(wlc_handle view, bool focus) 327 | { 328 | struct hook *hook; 329 | chck_iter_pool_for_each(&hooks[HOOK_VIEW_FOCUS], hook) { 330 | void (*fun)() = hook->function; 331 | fun(view, focus); 332 | } 333 | } 334 | 335 | static void 336 | view_move_to_output(wlc_handle view, wlc_handle from, wlc_handle to) 337 | { 338 | struct hook *hook; 339 | chck_iter_pool_for_each(&hooks[HOOK_VIEW_MOVE_TO_OUTPUT], hook) { 340 | void (*fun)() = hook->function; 341 | fun(view, from, to); 342 | } 343 | } 344 | 345 | static void 346 | view_geometry_request(wlc_handle view, const struct wlc_geometry *geometry) 347 | { 348 | struct hook *hook; 349 | chck_iter_pool_for_each(&hooks[HOOK_VIEW_GEOMETRY_REQUEST], hook) { 350 | void (*fun)() = hook->function; 351 | fun(view, geometry); 352 | } 353 | } 354 | 355 | static void 356 | view_state_request(wlc_handle view, const enum wlc_view_state_bit state, const bool toggle) 357 | { 358 | struct hook *hook; 359 | chck_iter_pool_for_each(&hooks[HOOK_VIEW_STATE_REQUEST], hook) { 360 | void (*fun)() = hook->function; 361 | fun(view, state, toggle); 362 | } 363 | } 364 | 365 | static void 366 | view_move_request(wlc_handle view, const struct wlc_point *point) 367 | { 368 | struct hook *hook; 369 | chck_iter_pool_for_each(&hooks[HOOK_VIEW_MOVE_REQUEST], hook) { 370 | void (*fun)() = hook->function; 371 | fun(view, point); 372 | } 373 | } 374 | 375 | static void 376 | view_resize_request(wlc_handle view, uint32_t edges, const struct wlc_point *point) 377 | { 378 | struct hook *hook; 379 | chck_iter_pool_for_each(&hooks[HOOK_VIEW_RESIZE_REQUEST], hook) { 380 | void (*fun)() = hook->function; 381 | fun(view, edges, point); 382 | } 383 | } 384 | 385 | static void 386 | view_pre_render(wlc_handle view) 387 | { 388 | struct hook *hook; 389 | chck_iter_pool_for_each(&hooks[HOOK_VIEW_PRE_RENDER], hook) { 390 | void (*fun)() = hook->function; 391 | fun(view); 392 | } 393 | } 394 | 395 | static void 396 | view_post_render(wlc_handle view) 397 | { 398 | struct hook *hook; 399 | chck_iter_pool_for_each(&hooks[HOOK_VIEW_POST_RENDER], hook) { 400 | void (*fun)() = hook->function; 401 | fun(view); 402 | } 403 | } 404 | 405 | static bool 406 | keyboard_key(wlc_handle view, uint32_t time, const struct wlc_modifiers *modifiers, uint32_t key, enum wlc_key_state state) 407 | { 408 | struct hook *hook; 409 | bool handled = false; 410 | chck_iter_pool_for_each(&hooks[HOOK_KEYBOARD_KEY], hook) { 411 | bool (*fun)() = hook->function; 412 | if (fun(view, time, modifiers, key, state)) 413 | handled = true; 414 | } 415 | return handled; 416 | } 417 | 418 | static bool 419 | pointer_button(wlc_handle view, uint32_t time, const struct wlc_modifiers *modifiers, uint32_t button, enum wlc_button_state state, const struct wlc_point *point) 420 | { 421 | struct hook *hook; 422 | bool handled = false; 423 | chck_iter_pool_for_each(&hooks[HOOK_POINTER_BUTTON], hook) { 424 | bool (*fun)() = hook->function; 425 | if (fun(view, time, modifiers, button, state, point)) 426 | handled = true; 427 | } 428 | return handled; 429 | } 430 | 431 | static bool 432 | pointer_scroll(wlc_handle view, uint32_t time, const struct wlc_modifiers *modifiers, uint8_t axis_bits, double amount[2]) 433 | { 434 | struct hook *hook; 435 | bool handled = false; 436 | chck_iter_pool_for_each(&hooks[HOOK_POINTER_SCROLL], hook) { 437 | bool (*fun)() = hook->function; 438 | if (fun(view, time, modifiers, axis_bits, amount)) 439 | handled = true; 440 | } 441 | return handled; 442 | } 443 | 444 | static bool 445 | pointer_motion(wlc_handle view, uint32_t time, const struct wlc_point *motion) 446 | { 447 | struct hook *hook; 448 | bool handled = false; 449 | chck_iter_pool_for_each(&hooks[HOOK_POINTER_MOTION], hook) { 450 | bool (*fun)() = hook->function; 451 | if (fun(view, time, motion)) 452 | handled = true; 453 | } 454 | return handled; 455 | } 456 | 457 | static bool 458 | touch(wlc_handle view, uint32_t time, const struct wlc_modifiers *modifiers, enum wlc_touch_type type, int32_t slot, const struct wlc_point *touch) 459 | { 460 | struct hook *hook; 461 | bool handled = false; 462 | chck_iter_pool_for_each(&hooks[HOOK_TOUCH], hook) { 463 | bool (*fun)() = hook->function; 464 | if (fun(view, time, modifiers, type, slot, touch)) 465 | handled = true; 466 | } 467 | return handled; 468 | } 469 | 470 | static void 471 | compositor_ready(void) 472 | { 473 | plog(0, PLOG_INFO, "-- Orbment is ready --"); 474 | 475 | struct hook *hook; 476 | chck_iter_pool_for_each(&hooks[HOOK_COMPOSITOR_READY], hook) { 477 | void (*fun)() = hook->function; 478 | fun(); 479 | } 480 | } 481 | 482 | static void 483 | compositor_terminate(void) 484 | { 485 | plog(0, PLOG_INFO, "-- Orbment is terminating --"); 486 | plugin_remove_all(); 487 | hooks_remove_all(); 488 | } 489 | 490 | static bool 491 | input_created(struct libinput_device *device) 492 | { 493 | struct hook *hook; 494 | chck_iter_pool_for_each(&hooks[HOOK_INPUT_CREATED], hook) { 495 | bool (*fun)() = hook->function; 496 | fun(device); 497 | } 498 | return true; 499 | } 500 | 501 | static void 502 | input_destroyed(struct libinput_device *device) 503 | { 504 | struct hook *hook; 505 | chck_iter_pool_for_each(&hooks[HOOK_INPUT_DESTROYED], hook) { 506 | bool (*fun)() = hook->function; 507 | fun(device); 508 | } 509 | } 510 | 511 | bool 512 | hooks_setup(void) 513 | { 514 | wlc_set_output_created_cb(output_created); 515 | wlc_set_output_destroyed_cb(output_destroyed); 516 | wlc_set_output_focus_cb(output_focus); 517 | wlc_set_output_resolution_cb(output_resolution); 518 | wlc_set_output_render_pre_cb(output_pre_render); 519 | wlc_set_output_render_post_cb(output_post_render); 520 | wlc_set_view_created_cb(view_created); 521 | wlc_set_view_destroyed_cb(view_destroyed); 522 | wlc_set_view_focus_cb(view_focus); 523 | wlc_set_view_move_to_output_cb(view_move_to_output); 524 | wlc_set_view_request_geometry_cb(view_geometry_request); 525 | wlc_set_view_request_state_cb(view_state_request); 526 | wlc_set_view_request_move_cb(view_move_request); 527 | wlc_set_view_request_resize_cb(view_resize_request); 528 | wlc_set_view_render_pre_cb(view_pre_render); 529 | wlc_set_view_render_post_cb(view_post_render); 530 | wlc_set_keyboard_key_cb(keyboard_key); 531 | wlc_set_pointer_button_cb(pointer_button); 532 | wlc_set_pointer_motion_cb(pointer_motion); 533 | wlc_set_pointer_scroll_cb(pointer_scroll); 534 | wlc_set_touch_cb(touch); 535 | wlc_set_compositor_ready_cb(compositor_ready); 536 | wlc_set_compositor_terminate_cb(compositor_terminate); 537 | wlc_set_input_created_cb(input_created); 538 | wlc_set_input_destroyed_cb(input_destroyed); 539 | 540 | plugin_set_callbacks(plugin_loaded, plugin_deloaded); 541 | 542 | { 543 | static const struct method methods[] = { 544 | REGISTER_METHOD(add_hook, "b(h,c[],fun)|1"), 545 | REGISTER_METHOD(remove_hook, "v(h,c[])|1"), 546 | {0}, 547 | }; 548 | 549 | struct plugin core = { 550 | .info = { 551 | .name = "orbment", 552 | .description = "Hook api.", 553 | .version = VERSION, 554 | .methods = methods, 555 | }, 556 | }; 557 | 558 | if (!plugin_register(&core, NULL)) 559 | return false; 560 | } 561 | 562 | return true; 563 | } 564 | -------------------------------------------------------------------------------- /src/hooks.h: -------------------------------------------------------------------------------- 1 | #ifndef __orbment_hooks_h__ 2 | #define __orbment_hooks_h__ 3 | 4 | #include 5 | 6 | struct wlc_interface; 7 | 8 | bool hooks_setup(void); 9 | 10 | #endif /* __orbment_hooks_h__ */ 11 | -------------------------------------------------------------------------------- /src/log.c: -------------------------------------------------------------------------------- 1 | #include "log.h" 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include "plugin.h" 12 | 13 | #if defined(__linux__) 14 | # include 15 | // FIXME: detect runtime instead 16 | # if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 4, 0) 17 | # include /* for yama */ 18 | # define HAS_YAMA_PRCTL 1 19 | # endif 20 | #endif 21 | 22 | static struct { 23 | FILE *file; 24 | } logger; 25 | 26 | static inline void 27 | log_timestamp(FILE *out) 28 | { 29 | assert(out); 30 | 31 | struct timeval tv; 32 | struct tm *brokendown_time; 33 | gettimeofday(&tv, NULL); 34 | 35 | if (!(brokendown_time = localtime(&tv.tv_sec))) { 36 | fprintf(out, "[(NULL)localtime] "); 37 | return; 38 | } 39 | 40 | char string[128]; 41 | static int cached_tm_mday; 42 | if (brokendown_time->tm_mday != cached_tm_mday) { 43 | strftime(string, sizeof(string), "%Y-%m-%d %Z", brokendown_time); 44 | fprintf(out, "Date: %s\n", string); 45 | cached_tm_mday = brokendown_time->tm_mday; 46 | } 47 | 48 | strftime(string, sizeof(string), "%H:%M:%S", brokendown_time); 49 | fprintf(out, "[%s.%03li] ", string, tv.tv_usec / 1000); 50 | } 51 | 52 | static inline void 53 | cb_log(enum wlc_log_type type, const char *str) 54 | { 55 | assert(str); 56 | const enum plugin_log_type ntype = (type == WLC_LOG_WAYLAND ? PLOG_INFO : (enum plugin_log_type)type); 57 | plog(0, ntype, "%s: %s", (type == WLC_LOG_WAYLAND ? "wayland" : "wlc"), str); 58 | } 59 | 60 | void 61 | logv(enum plugin_log_type type, const char *prefix, const char *fmt, va_list ap) 62 | { 63 | assert(fmt); 64 | 65 | FILE *out = (logger.file ? logger.file : stderr); 66 | 67 | if (out != stderr && out != stdout) 68 | log_timestamp(out); 69 | 70 | switch (type) { 71 | case PLOG_WARN: 72 | fprintf(out, "(WARN) "); 73 | break; 74 | case PLOG_ERROR: 75 | fprintf(out, "(ERROR) "); 76 | break; 77 | 78 | default: break; 79 | } 80 | 81 | if (prefix) 82 | fprintf(out, "%s: ", prefix); 83 | 84 | vfprintf(out, fmt, ap); 85 | fprintf(out, "\n"); 86 | fflush(out); 87 | } 88 | 89 | void 90 | log_set_file(const char *path) 91 | { 92 | logger.file = (path ? fopen(path, "a") : NULL); 93 | } 94 | 95 | void 96 | log_open(void) 97 | { 98 | wlc_log_set_handler(cb_log); 99 | } 100 | 101 | void 102 | log_close(void) 103 | { 104 | if (logger.file && logger.file != stdout && logger.file != stderr) 105 | fclose(logger.file); 106 | 107 | logger.file = NULL; 108 | } 109 | 110 | void 111 | log_backtrace(void) 112 | { 113 | if (clearenv() != 0) 114 | return; 115 | 116 | /* GDB */ 117 | #if defined(__linux__) || defined(__APPLE__) 118 | pid_t child_pid = fork(); 119 | 120 | #if HAS_YAMA_PRCTL 121 | /* tell yama that we allow our child_pid to trace our process */ 122 | if (child_pid > 0) { 123 | if (!prctl(PR_GET_DUMPABLE)) { 124 | plog(0, PLOG_WARN, "Compositor binary is suid/sgid, most likely since you are running from TTY."); 125 | plog(0, PLOG_WARN, "Kernel ptracing security policy does not allow attaching to suid/sgid processes."); 126 | plog(0, PLOG_WARN, "If you don't get backtrace below, try `setcap cap_sys_ptrace=eip gdb` temporarily."); 127 | } 128 | prctl(PR_SET_DUMPABLE, 1); 129 | prctl(PR_SET_PTRACER, child_pid); 130 | } 131 | #endif 132 | 133 | if (child_pid < 0) { 134 | plog(0, PLOG_ERROR, "Fork failed for gdb backtrace"); 135 | } else if (child_pid == 0) { 136 | /* 137 | * NOTE: gdb-7.8 does not seem to work with this, 138 | * either downgrade to 7.7 or use gdb from master. 139 | */ 140 | 141 | /* sed -n '/bar/h;/bar/!H;$!b;x;p' (another way, if problems) */ 142 | char buf[255]; 143 | const int fd = fileno((logger.file ? logger.file : stderr)); 144 | snprintf(buf, sizeof(buf) - 1, "gdb -p %d -n -batch -ex bt 2>/dev/null | sed -n '/&%d", getppid(), fd); 145 | execl("/bin/sh", "/bin/sh", "-c", buf, NULL); 146 | plog(0, PLOG_ERROR, "Failed to launch gdb for backtrace"); 147 | _exit(EXIT_FAILURE); 148 | } else { 149 | waitpid(child_pid, NULL, 0); 150 | } 151 | #endif 152 | } 153 | -------------------------------------------------------------------------------- /src/log.h: -------------------------------------------------------------------------------- 1 | #ifndef __orbment_log_h__ 2 | #define __orbment_log_h__ 3 | 4 | #include 5 | #include 6 | 7 | enum plugin_log_type; 8 | 9 | void log_set_file(const char *path); 10 | void log_open(void); 11 | void log_close(void); 12 | void log_backtrace(void); 13 | 14 | /** this is exposed for plugin.c, use plog instead. */ 15 | PNONULLV(3) void logv(enum plugin_log_type type, const char *prefix, const char *fmt, va_list ap); 16 | 17 | #endif /* __orbment_log_h__ */ 18 | -------------------------------------------------------------------------------- /src/orbment.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include "config.h" 7 | #include "signals.h" 8 | #include "plugin.h" 9 | #include "hooks.h" 10 | #include "log.h" 11 | 12 | static void 13 | register_plugins_from_path(void) 14 | { 15 | if (chck_cstr_is_empty(PLUGINS_PATH)) { 16 | plog(0, PLOG_ERROR, "Could not find plugins path. PLUGINS_PATH was not set during compile."); 17 | return; 18 | } 19 | 20 | { 21 | struct chck_string xdg = {0}; 22 | { 23 | char *tmp = xdg_get_path("XDG_DATA_HOME", ".local/share"); 24 | chck_string_set_cstr(&xdg, tmp, true); 25 | free(tmp); 26 | } 27 | 28 | chck_string_set_format(&xdg, "%s/orbment/plugins", xdg.data); 29 | 30 | #ifndef NDEBUG 31 | // allows running without install, as long as you build in debug mode 32 | // NOTE: $PWD/plugins is first in load order 33 | const char *paths[] = { "plugins", xdg.data, PLUGINS_PATH, NULL }; 34 | #else 35 | const char *paths[] = { xdg.data, PLUGINS_PATH, NULL }; 36 | #endif 37 | 38 | // FIXME: add portable directory code to chck/fs/fs.c 39 | for (uint32_t i = 0; paths[i]; ++i) { 40 | DIR *d; 41 | if (!(d = opendir(paths[i]))) { 42 | plog(0, PLOG_WARN, "Could not open plugins directory: %s", paths[i]); 43 | continue; 44 | } 45 | 46 | struct dirent *dir; 47 | while ((dir = readdir(d))) { 48 | if (!chck_cstr_starts_with(dir->d_name, "orbment-plugin-")) 49 | continue; 50 | 51 | struct chck_string tmp = {0}; 52 | if (chck_string_set_format(&tmp, "%s/%s", paths[i], dir->d_name)) 53 | plugin_register_from_path(tmp.data); 54 | chck_string_release(&tmp); 55 | } 56 | 57 | closedir(d); 58 | } 59 | 60 | chck_string_release(&xdg); 61 | } 62 | } 63 | 64 | static bool 65 | setup_plugins(void) 66 | { 67 | if (!hooks_setup()) 68 | return false; 69 | 70 | register_plugins_from_path(); 71 | plugin_load_all(); 72 | return true; 73 | } 74 | 75 | static void 76 | handle_arguments(int argc, char *argv[]) 77 | { 78 | for (int i = 1; i < argc; ++i) { 79 | if (chck_cstreq(argv[i], "--log")) { 80 | if (i + 1 >= argc) { 81 | plog(0, PLOG_ERROR, "--log takes an argument (filename)"); 82 | abort(); 83 | } 84 | log_set_file(argv[++i]); 85 | } 86 | } 87 | } 88 | 89 | int 90 | main(int argc, char *argv[]) 91 | { 92 | (void)argc, (void)argv; 93 | 94 | signals_setup_debug(); 95 | 96 | // XXX: Potentially dangerous under suid 97 | handle_arguments(argc, argv); 98 | log_open(); 99 | 100 | if (!wlc_init()) 101 | return EXIT_FAILURE; 102 | 103 | signals_setup(); 104 | 105 | if (!setup_plugins()) 106 | return EXIT_FAILURE; 107 | 108 | plog(0, PLOG_INFO, "-- Orbment started --"); 109 | 110 | wlc_run(); 111 | 112 | plog(0, PLOG_INFO, "-- Orbment is gone, bye bye! --"); 113 | log_close(); 114 | return EXIT_SUCCESS; 115 | } 116 | -------------------------------------------------------------------------------- /src/plugin.c: -------------------------------------------------------------------------------- 1 | #include "plugin.h" 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include "log.h" 9 | 10 | static const size_t NOTINDEX = (size_t)-1; 11 | 12 | static struct chck_pool plugins; 13 | static struct chck_hash_table names; 14 | static struct chck_hash_table groups; 15 | 16 | static struct { 17 | void (*loaded)(const struct plugin *plugin); 18 | void (*deloaded)(const struct plugin *plugin); 19 | } callbacks; 20 | 21 | static plugin_h 22 | get_handle(const char *name) 23 | { 24 | assert(name); 25 | plugin_h *h = chck_hash_table_str_get(&names, name, strlen(name)); 26 | return (h ? *h : NOTINDEX); 27 | } 28 | 29 | static struct plugin* 30 | get(const char *name) 31 | { 32 | assert(name); 33 | return chck_pool_get(&plugins, get_handle(name)); 34 | } 35 | 36 | static void 37 | unlink_name(const char *name) 38 | { 39 | assert(name); 40 | chck_hash_table_str_set(&names, name, strlen(name), &NOTINDEX); 41 | } 42 | 43 | static bool 44 | link_name(const char *name, plugin_h handle) 45 | { 46 | assert(name); 47 | 48 | if (handle == NOTINDEX || (!names.lut.table && !chck_hash_table(&names, NOTINDEX, 256, sizeof(plugin_h)))) 49 | return false; 50 | 51 | return chck_hash_table_str_set(&names, name, strlen(name), &handle); 52 | } 53 | 54 | static struct chck_iter_pool* 55 | get_group(const char *name) 56 | { 57 | assert(name); 58 | return (groups.lut.table ? chck_hash_table_str_get(&groups, name, strlen(name)) : NULL); 59 | } 60 | 61 | static void 62 | remove_from_group(const char *name, plugin_h handle) 63 | { 64 | assert(name); 65 | 66 | struct chck_iter_pool *pool; 67 | if (!(pool = get_group(name))) 68 | return; 69 | 70 | plugin_h *h; 71 | chck_iter_pool_for_each(pool, h) { 72 | if (*h != handle) 73 | continue; 74 | 75 | chck_iter_pool_remove(pool, _I - 1); 76 | break; 77 | } 78 | } 79 | 80 | static bool 81 | exists_in_pool(struct chck_iter_pool *pool, plugin_h handle) 82 | { 83 | assert(pool); 84 | plugin_h *h; 85 | chck_iter_pool_for_each(pool, h) { 86 | if (*h == handle) 87 | return true; 88 | } 89 | return false; 90 | } 91 | 92 | static bool 93 | add_to_group(const char *name, plugin_h handle) 94 | { 95 | assert(name); 96 | 97 | if (handle == NOTINDEX || (!groups.lut.table && !chck_hash_table(&groups, NOTINDEX, 256, sizeof(struct chck_iter_pool)))) 98 | return false; 99 | 100 | { 101 | struct chck_iter_pool *pool; 102 | if ((pool = get_group(name))) 103 | return (exists_in_pool(pool, handle) ? false : chck_iter_pool_push_back(pool, &handle)); 104 | } 105 | 106 | struct chck_iter_pool pool; 107 | if (!chck_iter_pool(&pool, 1, 0, sizeof(plugin_h))) 108 | return false; 109 | 110 | if (!chck_iter_pool_push_back(&pool, &handle) || !chck_hash_table_str_set(&groups, name, strlen(name), &pool)) 111 | goto error0; 112 | 113 | return true; 114 | 115 | error0: 116 | chck_iter_pool_release(&pool); 117 | return false; 118 | } 119 | 120 | static bool 121 | deload_plugin(struct plugin *p, bool force) 122 | { 123 | assert(p); 124 | 125 | if (!p->loaded && !p->dl) 126 | return true; 127 | 128 | struct chck_string *s; 129 | chck_iter_pool_for_each(&p->needed, s) { 130 | struct plugin *d; 131 | if ((d = get(s->data)) && d->loaded) { 132 | if (force) { 133 | deload_plugin(d, force); 134 | } else { 135 | plog(0, PLOG_ERROR, "Could not deload plugin, needed by '%s'", s->data); 136 | return false; 137 | } 138 | } 139 | } 140 | 141 | if (callbacks.deloaded) 142 | callbacks.deloaded(p); 143 | 144 | chck_iter_pool_for_each_call(&p->needed, chck_string_release); 145 | chck_iter_pool_release(&p->needed); 146 | 147 | if (p->info.name) 148 | unlink_name(p->info.name); 149 | 150 | if (p->loaded && p->deinit) 151 | p->deinit(p->handle + 1); 152 | 153 | if (p->dl) 154 | chck_dl_unload(p->dl); 155 | 156 | p->dl = NULL; 157 | p->loaded = false; 158 | return true; 159 | } 160 | 161 | static bool load_plugin(struct plugin *p); 162 | 163 | static bool 164 | load_dep(struct plugin *p, struct plugin *d, bool hard) 165 | { 166 | assert(p && d); 167 | 168 | if (!d->loaded && !load_plugin(d) && hard) 169 | return false; 170 | 171 | struct chck_string name = {0}; 172 | if (!chck_string_set_cstr(&name, p->info.name, true)) 173 | return false; 174 | 175 | if (!chck_iter_pool_push_back(&d->needed, &name)) 176 | goto error0; 177 | 178 | return true; 179 | 180 | error0: 181 | chck_string_release(&name); 182 | return false; 183 | } 184 | 185 | static bool 186 | load_deps_from_group(struct plugin *p, struct chck_iter_pool *pool, bool hard) 187 | { 188 | assert(p && pool); 189 | plugin_h *h; 190 | chck_iter_pool_for_each(pool, h) { 191 | struct plugin *d = chck_pool_get(&plugins, *h); 192 | if (d && !load_dep(p, d, hard)) 193 | return false; 194 | } 195 | return true; 196 | } 197 | 198 | PPURE static bool 199 | belongs_to_pool(const char **pool, const char *name) 200 | { 201 | assert(name); 202 | for (uint32_t i = 0; pool && pool[i]; ++i) { 203 | if (chck_cstreq(pool[i], name)) 204 | return true; 205 | } 206 | return false; 207 | } 208 | 209 | static bool 210 | load_deps_from_array(struct plugin *p, const char **array, bool hard) 211 | { 212 | assert(p); 213 | 214 | for (uint32_t i = 0; array && array[i]; ++i) { 215 | struct chck_iter_pool *pool; 216 | if (!belongs_to_pool(p->info.groups, array[i]) && (pool = get_group(array[i]))) 217 | return load_deps_from_group(p, pool, hard); 218 | 219 | struct plugin *d; 220 | if (!(d = get(array[i])) && hard) { 221 | plog(0, PLOG_ERROR, "Dependency '%s' for plugin '%s' was not found", array[i], p->info.name); 222 | return false; 223 | } 224 | 225 | if (d && (belongs_to_pool(d->info.requires, p->info.name) || belongs_to_pool(d->info.after, p->info.name))) { 226 | plog(0, PLOG_ERROR, "Circular dependency detected for plugins '%s' and '%s'", p->info.name, d->info.name); 227 | return false; 228 | } 229 | 230 | if (d && !load_dep(p, d, hard)) 231 | return false; 232 | } 233 | 234 | return true; 235 | } 236 | 237 | static bool 238 | load_plugin(struct plugin *p) 239 | { 240 | assert(p); 241 | 242 | // plugins with path need handle, or they point garbage 243 | if (!chck_string_is_empty(&p->path) && !p->dl) 244 | return false; 245 | 246 | if (p->loaded) 247 | return true; 248 | 249 | if (!load_deps_from_array(p, p->info.requires, true)) 250 | goto error0; 251 | 252 | // optional deps 253 | load_deps_from_array(p, p->info.after, false); 254 | 255 | p->loaded = true; 256 | 257 | plog(0, PLOG_INFO, "Loading plugin '%s'", p->info.name); 258 | 259 | if (p->init && !p->init(p->handle + 1)) 260 | goto error0; 261 | 262 | if (callbacks.loaded) 263 | callbacks.loaded(p); 264 | 265 | return true; 266 | 267 | error0: 268 | plog(0, PLOG_INFO, "Plugin '%s' failed to load", p->info.name); 269 | deload_plugin(p, false); 270 | return false; 271 | } 272 | 273 | static void 274 | plugin_release(struct plugin *p) 275 | { 276 | assert(p); 277 | deload_plugin(p, true); 278 | chck_string_release(&p->path); 279 | } 280 | 281 | static inline void 282 | pvlog(plugin_h caller, enum plugin_log_type type, const char *fmt, va_list ap) 283 | { 284 | struct plugin *c; 285 | if (!(c = (caller ? chck_pool_get(&plugins, caller - 1) : NULL))) { 286 | logv(type, NULL, fmt, ap); 287 | return; 288 | } 289 | 290 | logv(type, c->info.name, fmt, ap); 291 | } 292 | 293 | void 294 | plog(plugin_h caller, enum plugin_log_type type, const char *fmt, ...) 295 | { 296 | va_list args; 297 | va_start(args, fmt); 298 | pvlog(caller, type, fmt, args); 299 | va_end(args); 300 | } 301 | 302 | void 303 | plugin_set_callbacks(void (*loaded)(const struct plugin*), void (*deloaded)(const struct plugin*)) 304 | { 305 | callbacks.loaded = loaded; 306 | callbacks.deloaded = deloaded; 307 | } 308 | 309 | void 310 | plugin_remove_all(void) 311 | { 312 | struct plugin *p; 313 | chck_pool_for_each(&plugins, p) 314 | plugin_release(p); 315 | 316 | chck_pool_release(&plugins); 317 | chck_hash_table_release(&names); 318 | plog(0, PLOG_INFO, "Deloaded plugins"); 319 | } 320 | 321 | void 322 | plugin_load_all(void) 323 | { 324 | struct plugin *p; 325 | size_t loaded = 0, count = plugins.items.count; 326 | chck_pool_for_each(&plugins, p) { 327 | if (!load_plugin(p)) { 328 | chck_pool_remove(&plugins, _I - 1); 329 | continue; 330 | } 331 | ++loaded; 332 | } 333 | 334 | plog(0, PLOG_INFO, "Loaded %zu/%zu plugins", loaded, count); 335 | } 336 | 337 | enum conflict_msg { 338 | registered, 339 | conflict, 340 | group 341 | }; 342 | 343 | static bool 344 | exists_in_info_array(const char *name, const char **array, bool check_group, enum conflict_msg msg) 345 | { 346 | assert(name); 347 | 348 | for (uint32_t i = 0; array && array[i]; ++i) { 349 | struct plugin *p; 350 | if ((p = get(array[i])) || (check_group && get_group(array[i]))) { 351 | if (msg == registered) { 352 | plog(0, PLOG_ERROR, "%s with name '%s' is already registered", (p ? "Plugin" : "Group"), array[i]); 353 | } else if (msg == conflict) { 354 | plog(0, PLOG_ERROR, "Plugin '%s' conflicts with %s '%s'", (p ? "plugin" : "group"), name, array[i]); 355 | } else if (msg == group) { 356 | if (get_group(array[i])) // check if the conflicted package belongs to the group as well 357 | return false; // if that is true, we can ignore this conflict. 358 | plog(0, PLOG_ERROR, "Group '%s' conflicts with plugin '%s'", array[i], (p ? p->info.name : "unknown")); 359 | } 360 | return true; 361 | } 362 | } 363 | 364 | return false; 365 | } 366 | 367 | bool 368 | plugin_register(struct plugin *plugin, const struct plugin_info* (*reg)(void)) 369 | { 370 | assert(plugin); 371 | 372 | if (reg) { 373 | const struct plugin_info *info; 374 | if (!(info = reg())) 375 | goto error0; 376 | 377 | if (!info->name || !info->description) { 378 | plog(0, PLOG_ERROR, "Plugin with no name or description is not allowed"); 379 | goto error0; 380 | } 381 | 382 | struct plugin *p; 383 | if ((p = get(info->name))) { 384 | plog(0, PLOG_ERROR, "Plugin with name '%s' is already registered", info->name); 385 | goto error0; 386 | } 387 | 388 | if (exists_in_info_array(info->name, info->provides, true, registered) || 389 | exists_in_info_array(info->name, info->conflicts, true, conflict) || 390 | exists_in_info_array(info->name, info->groups, false, group)) 391 | goto error0; 392 | 393 | memcpy(&plugin->info, info, sizeof(plugin->info)); 394 | } 395 | 396 | if (!plugins.items.member && !chck_pool(&plugins, 1, 0, sizeof(struct plugin))) 397 | goto error0; 398 | 399 | plugin_h handle; 400 | if (!chck_iter_pool(&plugin->needed, 1, 0, sizeof(struct chck_string)) || !(plugin = chck_pool_add(&plugins, plugin, &handle))) 401 | goto error0; 402 | 403 | plugin->handle = handle; 404 | 405 | if (!link_name(plugin->info.name, handle)) 406 | goto error1; 407 | 408 | if (plugin->info.groups) { 409 | for (uint32_t i = 0; plugin->info.groups[i]; ++i) { 410 | if (!add_to_group(plugin->info.groups[i], handle)) 411 | goto error2; 412 | } 413 | } 414 | 415 | plog(0, PLOG_INFO, "registered plugin %s (%s) %s", plugin->info.name, plugin->info.version, plugin->info.description); 416 | return true; 417 | 418 | error2: 419 | unlink_name(plugin->info.name); 420 | for (uint32_t i = 0; plugin->info.groups[i]; ++i) 421 | remove_from_group(plugin->info.groups[i], handle); 422 | error1: 423 | chck_pool_remove(&plugins, handle); 424 | error0: 425 | plugin_release(plugin); 426 | return false; 427 | } 428 | 429 | bool 430 | plugin_register_from_path(const char *path) 431 | { 432 | assert(path); 433 | 434 | void *dl; 435 | const char *error; 436 | if (!(dl = chck_dl_load(path, &error))) { 437 | plog(0, PLOG_ERROR, "%s", error); 438 | return false; 439 | } 440 | 441 | void *methods[3]; 442 | const void *functions[3] = { "plugin_register", "plugin_init", "plugin_deinit" }; 443 | for (int32_t i = 0; i < 3; ++i) 444 | methods[i] = chck_dl_load_symbol(dl, functions[i], NULL); 445 | 446 | if (!methods[0]) { 447 | plog(0, PLOG_ERROR, "Could not find 'plugin_register' function from: %s", path); 448 | chck_dl_unload(dl); 449 | return false; 450 | } 451 | 452 | struct plugin p; 453 | memset(&p, 0, sizeof(p)); 454 | p.init = methods[1]; 455 | p.deinit = methods[2]; 456 | p.dl = dl; 457 | return (chck_string_set_cstr(&p.path, path, true) && plugin_register(&p, methods[0])); 458 | } 459 | 460 | plugin_h 461 | import_plugin(plugin_h caller, const char *name) 462 | { 463 | (void)caller; 464 | 465 | if (!name) 466 | return 0; 467 | 468 | const plugin_h h = get_handle(name); 469 | return (h == NOTINDEX ? 0 : h + 1); 470 | } 471 | 472 | bool 473 | has_methods(plugin_h caller, plugin_h handle, const struct method_info *methods) 474 | { 475 | if (!caller || !handle || !methods) 476 | return false; 477 | 478 | struct plugin *c, *p; 479 | if (!(c = chck_pool_get(&plugins, caller - 1)) || 480 | !(p = chck_pool_get(&plugins, handle - 1))) 481 | return false; 482 | 483 | for (size_t x = 0; methods[x].name; ++x) { 484 | bool found = false; 485 | for (uint32_t i = 0; p->info.methods[i].info.name && p->info.methods[i].info.signature; ++i) { 486 | const struct method *m = &p->info.methods[i]; 487 | if (!chck_cstreq(m->info.name, methods[x].name)) 488 | continue; 489 | 490 | found = chck_cstreq(m->info.signature, methods[x].signature); 491 | break; 492 | } 493 | 494 | if (!found) { 495 | plog(0, PLOG_WARN, "%s: No such method %s in %s (%s) or wrong signature", c->info.name, methods[x].name, p->info.name, p->info.version); 496 | return false; 497 | } 498 | } 499 | 500 | return true; 501 | } 502 | 503 | void* 504 | import_method(plugin_h caller, plugin_h handle, const char *name, const char *signature) 505 | { 506 | if (!caller || !handle || !name || !signature) 507 | return NULL; 508 | 509 | struct plugin *c, *p; 510 | if (!(c = chck_pool_get(&plugins, caller - 1)) || 511 | !(p = chck_pool_get(&plugins, handle - 1))) 512 | return false; 513 | 514 | for (uint32_t i = 0; p->info.methods[i].info.name && p->info.methods[i].info.signature; ++i) { 515 | const struct method *m = &p->info.methods[i]; 516 | if (!chck_cstreq(m->info.name, name)) 517 | continue; 518 | 519 | if (!chck_cstreq(m->info.signature, signature)) { 520 | plog(0, PLOG_WARN, "%s: Method '%s' '%s' != '%s' signature mismatch in %s (%s)", c->info.name, name, signature, m->info.signature, p->info.name, p->info.version); 521 | return NULL; 522 | } 523 | 524 | if (m->deprecated) 525 | plog(0, PLOG_WARN, "%s: Method '%s' is deprecated in %s (%s)", c->info.name, name, p->info.name, p->info.version); 526 | 527 | return m->function; 528 | } 529 | 530 | plog(0, PLOG_WARN, "%s: No such method '%s' in %s (%s)", c->info.name, name, p->info.name, p->info.version); 531 | return NULL; 532 | } 533 | -------------------------------------------------------------------------------- /src/plugin.h: -------------------------------------------------------------------------------- 1 | #ifndef __orbment_plugin_private_h__ 2 | #define __orbment_plugin_private_h__ 3 | 4 | #include 5 | #include 6 | #include "chck/string/string.h" 7 | #include "chck/pool/pool.h" 8 | 9 | struct plugin { 10 | struct chck_iter_pool needed; 11 | struct chck_string path; 12 | struct plugin_info info; 13 | bool (*init)(plugin_h self); 14 | void (*deinit)(plugin_h self); 15 | plugin_h handle; 16 | void *dl; 17 | bool loaded; 18 | }; 19 | 20 | void plugin_set_callbacks(void (*loaded)(const struct plugin*), void (*deloaded)(const struct plugin*)); 21 | void plugin_remove_all(void); 22 | void plugin_load_all(void); 23 | PNONULLV(1) bool plugin_register(struct plugin *plugin, const struct plugin_info* (*reg)(void)); 24 | PNONULL bool plugin_register_from_path(const char *path); 25 | 26 | #endif /* __orbment_plugin_private_h__ */ 27 | -------------------------------------------------------------------------------- /src/signals.c: -------------------------------------------------------------------------------- 1 | #include "signals.h" 2 | #include 3 | #include 4 | #include 5 | #include "plugin.h" 6 | #include "log.h" 7 | 8 | #if defined(__linux__) && defined(__GNUC__) 9 | # include 10 | int feenableexcept(int excepts); 11 | #endif 12 | 13 | #if (defined(__APPLE__) && (defined(__i386__) || defined(__x86_64__))) 14 | # define OSX_SSE_FPE 15 | # include 16 | #endif 17 | 18 | #ifndef NDEBUG 19 | 20 | static void 21 | fpehandler(int signal) 22 | { 23 | (void)signal; 24 | plog(0, PLOG_ERROR, "SIGFPE signal received"); 25 | abort(); 26 | } 27 | 28 | static void 29 | fpesetup(struct sigaction *action) 30 | { 31 | #if defined(__linux__) || defined(_WIN32) || defined(OSX_SSE_FPE) 32 | action->sa_handler = fpehandler; 33 | sigaction(SIGFPE, action, NULL); 34 | # if defined(__linux__) && defined(__GNUC__) 35 | feenableexcept(FE_DIVBYZERO | FE_INVALID | FE_OVERFLOW); 36 | # endif /* defined(__linux__) && defined(__GNUC__) */ 37 | # if defined(OSX_SSE_FPE) 38 | return; /* causes issues */ 39 | /* OSX uses SSE for floating point by default, so here 40 | * use SSE instructions to throw floating point exceptions */ 41 | _MM_SET_EXCEPTION_MASK(_MM_MASK_MASK & ~(_MM_MASK_OVERFLOW | _MM_MASK_INVALID | _MM_MASK_DIV_ZERO)); 42 | # endif /* OSX_SSE_FPE */ 43 | # if defined(_WIN32) && defined(_MSC_VER) 44 | _controlfp_s(NULL, 0, _MCW_EM); /* enables all fp exceptions */ 45 | _controlfp_s(NULL, _EM_DENORMAL | _EM_UNDERFLOW | _EM_INEXACT, _MCW_EM); /* hide the ones we don't care about */ 46 | # endif /* _WIN32 && _MSC_VER */ 47 | #endif 48 | } 49 | 50 | static void 51 | backtrace(int signal) 52 | { 53 | (void)signal; 54 | log_backtrace(); 55 | 56 | /* SIGABRT || SIGSEGV */ 57 | exit(EXIT_FAILURE); 58 | } 59 | 60 | #endif /* NDEBUG */ 61 | 62 | static void 63 | sigterm(int signal) 64 | { 65 | (void)signal; 66 | plog(0, PLOG_INFO, "Got %s", (signal == SIGTERM ? "SIGTERM" : "SIGINT")); 67 | wlc_terminate(); 68 | } 69 | 70 | 71 | #ifdef NDEBUG 72 | PCONST 73 | #endif 74 | void 75 | signals_setup_debug(void) 76 | { 77 | #ifndef NDEBUG 78 | { 79 | struct sigaction action = { 80 | .sa_handler = backtrace 81 | }; 82 | 83 | sigaction(SIGABRT, &action, NULL); 84 | sigaction(SIGSEGV, &action, NULL); 85 | fpesetup(&action); 86 | } 87 | #endif 88 | } 89 | 90 | void 91 | signals_setup(void) 92 | { 93 | { 94 | struct sigaction action = { 95 | .sa_handler = SIG_DFL, 96 | .sa_flags = SA_NOCLDWAIT 97 | }; 98 | 99 | // do not care about childs 100 | sigaction(SIGCHLD, &action, NULL); 101 | } 102 | 103 | { 104 | struct sigaction action = { 105 | .sa_handler = sigterm, 106 | }; 107 | 108 | sigaction(SIGTERM, &action, NULL); 109 | sigaction(SIGINT, &action, NULL); 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /src/signals.h: -------------------------------------------------------------------------------- 1 | #ifndef __orbment_signals_h__ 2 | #define __orbment_signals_h__ 3 | 4 | void signals_setup(void); 5 | void signals_setup_debug(void); 6 | 7 | #endif /* __orbment_signals_h__ */ 8 | -------------------------------------------------------------------------------- /uncrustify.conf: -------------------------------------------------------------------------------- 1 | # Uncrustify 0.61 2 | # Generated from wlc source 3 | newlines = lf 4 | tok_split_gte = false 5 | utf8_bom = remove 6 | utf8_force = true 7 | indent_columns = 3 8 | indent_with_tabs = 0 9 | indent_braces = false 10 | indent_braces_no_func = false 11 | indent_braces_no_struct = false 12 | indent_brace_parent = false 13 | indent_else_if = false 14 | indent_var_def_cont = false 15 | indent_func_def_force_col1 = false 16 | indent_func_call_param = false 17 | indent_func_def_param = false 18 | indent_func_proto_param = false 19 | indent_func_param_double = false 20 | indent_relative_single_line_comments = false 21 | indent_switch_case = indent_columns 22 | indent_col1_comment = true 23 | indent_label = 1 24 | indent_access_spec = 1 25 | indent_access_spec_body = false 26 | indent_paren_nl = false 27 | indent_comma_paren = false 28 | indent_bool_paren = false 29 | indent_first_bool_expr = false 30 | indent_square_nl = false 31 | indent_preserve_sql = false 32 | indent_align_assign = true 33 | sp_arith = force 34 | sp_assign = force 35 | sp_assign_default = force 36 | sp_before_assign = force 37 | sp_after_assign = force 38 | sp_enum_assign = force 39 | sp_pp_concat = ignore 40 | sp_bool = force 41 | sp_compare = force 42 | sp_inside_paren = remove 43 | sp_paren_paren = remove 44 | sp_paren_brace = remove 45 | sp_before_ptr_star = force 46 | sp_before_unnamed_ptr_star = remove 47 | sp_between_ptr_star = remove 48 | sp_after_ptr_star = remove 49 | sp_after_ptr_star_func = force 50 | sp_after_type = force 51 | sp_before_sparen = force 52 | sp_inside_sparen = remove 53 | sp_after_sparen = force 54 | sp_sparen_brace = force 55 | sp_before_semi = remove 56 | sp_before_semi_for = remove 57 | sp_after_semi = add 58 | sp_after_semi_for = force 59 | sp_before_square = remove 60 | sp_before_squares = remove 61 | sp_inside_square = remove 62 | sp_after_comma = force 63 | sp_before_comma = remove 64 | sp_paren_comma = force 65 | sp_before_case_colon = remove 66 | # buggy with (type){ ... } 67 | # sp_after_cast = remove 68 | sp_inside_paren_cast = remove 69 | sp_sizeof_paren = remove 70 | sp_inside_braces = ignore 71 | sp_inside_braces_empty = remove 72 | sp_type_func = force 73 | sp_func_proto_paren = remove 74 | sp_func_def_paren = remove 75 | sp_inside_fparens = remove 76 | sp_inside_fparen = remove 77 | sp_func_call_paren = remove 78 | sp_func_call_paren_empty = remove 79 | sp_attribute_paren = remove 80 | sp_defined_paren = remove 81 | sp_else_brace = force 82 | sp_brace_else = force 83 | sp_word_brace = add 84 | sp_word_brace_ns = add 85 | sp_before_nl_cont = add 86 | sp_cond_colon = force 87 | sp_cond_question = force 88 | sp_cond_ternary_short = remove 89 | sp_cmt_cpp_start = add 90 | align_keep_tabs = false 91 | align_with_tabs = false 92 | align_on_tabstop = false 93 | align_number_left = false 94 | align_keep_extra_space = false 95 | align_func_params = false 96 | align_same_func_call_params = false 97 | nl_assign_leave_one_liners = true 98 | nl_start_of_file = remove 99 | nl_end_of_file = force 100 | nl_end_of_file_min = 1 101 | nl_fcall_brace = remove 102 | nl_enum_brace = remove 103 | nl_struct_brace = remove 104 | nl_union_brace = remove 105 | nl_if_brace = remove 106 | nl_brace_else = remove 107 | nl_elseif_brace = remove 108 | nl_else_brace = remove 109 | nl_else_if = remove 110 | nl_for_brace = remove 111 | nl_while_brace = remove 112 | nl_do_brace = remove 113 | nl_brace_while = remove 114 | nl_switch_brace = remove 115 | nl_multi_line_cond = false 116 | nl_multi_line_define = false 117 | nl_before_case = false 118 | nl_after_case = false 119 | nl_case_colon_brace = force 120 | nl_func_type_name = force 121 | nl_func_proto_type_name = remove 122 | nl_func_paren = remove 123 | nl_func_def_paren = remove 124 | nl_func_decl_start = remove 125 | nl_func_def_start = remove 126 | nl_func_decl_args = remove 127 | nl_func_def_args = remove 128 | nl_func_decl_end = remove 129 | nl_func_def_end = remove 130 | nl_func_decl_empty = remove 131 | nl_func_def_empty = remove 132 | nl_fdef_brace = force 133 | nl_return_expr = remove 134 | nl_after_semicolon = remove 135 | nl_max = 2 136 | nl_before_block_comment = 1 137 | nl_before_c_comment = 1 138 | nl_before_cpp_comment = 1 139 | nl_after_struct = 1 140 | eat_blanks_after_open_brace = true 141 | eat_blanks_before_close_brace = true 142 | mod_full_brace_do = force 143 | mod_remove_extra_semicolon = true 144 | mod_case_brace = remove 145 | mod_remove_empty_return = true 146 | cmt_convert_tab_to_spaces = true 147 | cmt_star_cont = true 148 | --------------------------------------------------------------------------------