diff -Nru herbstluftwm-0.6.2/CMakeLists.txt herbstluftwm-0.7.0/CMakeLists.txt --- herbstluftwm-0.6.2/CMakeLists.txt 1970-01-01 00:00:00.000000000 +0000 +++ herbstluftwm-0.7.0/CMakeLists.txt 2015-10-14 13:27:40.000000000 +0000 @@ -0,0 +1,335 @@ +# vim: set ts=4 sw=4 et: + +cmake_minimum_required(VERSION 3.1) + +set(CMAKE_BUILD_TYPE_INIT "Release") + +cmake_policy(SET CMP0005 NEW) # Escape preprocessor strings +cmake_policy(SET CMP0010 NEW) # So syntax problems are errors + +set(CMAKE_C_STANDARD 99) +set(CMAKE_CXX_STANDARD 11) + +# redirect output +set(EXECUTABLE_OUTPUT_PATH ${CMAKE_BINARY_DIR}/bin CACHE INTERNAL "" FORCE) +set(LIBRARY_OUTPUT_PATH ${CMAKE_BINARY_DIR}/lib CACHE INTERNAL "" FORCE) + +if(${CMAKE_SOURCE_DIR} STREQUAL ${CMAKE_BINARY_DIR}) + message(FATAL_ERROR "In-source builds aren't supported. Remove the CMakeCache.txt and run from another directory.") +endif() + +project(Herbstluftwm) + + +# ---------------------------------------------------------------------------- +# Options + +option(WITH_DOCUMENTATION "Build with documentation" ON) +option(WITH_XINERAMA "Use multi-monitor support" ON) + +set(DESTDIR "" CACHE PATH "Root directory, prefix for CMAKE_INSTALL_PREFIX and CMAKE_INSTALL_SYSCONF_PREFIX when set") +set(CMAKE_INSTALL_SYSCONF_PREFIX "/etc" CACHE PATH "Directory to install configuration files") + +set(SYSCONFDIR "${DESTDIR}/etc") +set(CONFIGDIR "${SYSCONFDIR}/xdg/herbstluftwm") + + +if(CMAKE_COMPILER_IS_GNUCC) + set(CMAKE_C_FLAGS "-pedantic -Wall") + set(CMAKE_CXX_FLAGS "-pedantic -Wall -Wno-sign-compare -Wno-narrowing -Wno-deprecated-register") +endif() + + +# ---------------------------------------------------------------------------- +# Find Libraries + +include(FindPkgConfig) +pkg_check_modules(GLIB2 REQUIRED glib-2.0) + +if(WITH_XINERAMA) + find_package(X11 REQUIRED) + + if(NOT X11_Xinerama_FOUND) + set(WITH_XINERAMA OFF) + endif() +endif() + + +# ---------------------------------------------------------------------------- +# Find Vars + +# VERSION_GIT +set(VERSION_GIT " (unknown)") +if(EXISTS ${CMAKE_SOURCE_DIR}/.git) + find_package(Git) + if(GIT_FOUND) + execute_process( + COMMAND git rev-parse --short HEAD + WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} + OUTPUT_VARIABLE VERSION_GIT + OUTPUT_STRIP_TRAILING_WHITESPACE + ) + set(VERSION_GIT " (${VERSION_GIT})") + endif() +endif() + +# VERSION_* from 'version.mk' +file(STRINGS "${CMAKE_SOURCE_DIR}/version.mk" _contents REGEX "^VERSION_.*$") +string(REGEX REPLACE ".*VERSION_MAJOR[ \t]*=[ \t]*([0-9]+).*" "\\1" VERSION_MAJOR "${_contents}") +string(REGEX REPLACE ".*VERSION_MINOR[ \t]*=[ \t]*([0-9]+).*" "\\1" VERSION_MINOR "${_contents}") +string(REGEX REPLACE ".*VERSION_PATCH[ \t]*=[ \t]*([0-9]+).*" "\\1" VERSION_PATCH "${_contents}") +string(REGEX REPLACE ".*VERSION_SUFFIX[ \t]*=[ \t]*\"(.*)\".*" "\\1" VERSION_SUFFIX "${_contents}") +set(SHORTVERSION "${VERSION_MAJOR}\.${VERSION_MINOR}\.${VERSION_PATCH}${VERSION_SUFFIX}") +set(VERSION "${SHORTVERSION}${VERSION_GIT}") +unset(_contents) + + +# ---------------------------------------------------------------------------- +# Program: 'herbstluftwm' + +set(SRC + src/clientlist.cpp src/clientlist.h + src/command.cpp src/command.h + src/decoration.cpp src/decoration.h + src/desktopwindow.cpp src/desktopwindow.h + src/ewmh.cpp src/ewmh.h + src/floating.cpp src/floating.h + src/glib-backports.h + src/globals.h + src/hook.cpp src/hook.h + src/ipc-protocol.h + src/ipc-server.cpp src/ipc-server.h + src/key.cpp src/key.h + src/layout.cpp src/layout.h + src/main.cpp + src/monitor.cpp src/monitor.h + src/mouse.cpp src/mouse.h + src/object.cpp src/object.h + src/rules.cpp src/rules.h + src/settings.cpp src/settings.h + src/stack.cpp src/stack.h + src/tag.cpp src/tag.h + src/utils.cpp src/utils.h + src/x11-types.h + src/x11-utils.cpp src/x11-utils.h +) + +set(INC_SYS + ${X11_X11_INCLUDE_PATH} + ${GLIB2_INCLUDE_DIRS} +) + +set(DEF + -D_XOPEN_SOURCE=600 + -DHERBSTLUFT_VERSION=\"${VERSION}\" + -DHERBSTLUFT_VERSION_MAJOR=\"${VERSION_MAJOR}\" + -DHERBSTLUFT_VERSION_MINOR=\"${VERSION_MINOR}\" + -DHERBSTLUFT_VERSION_PATCH=\"${VERSION_PATCH}\" + -DHERBSTLUFT_GLOBAL_AUTOSTART="${CONFIGDIR}/autostart" +) + +set(LIB + ${X11_X11_LIB} + # for Xshape + ${X11_Xext_LIB} + ${GLIB2_LIBRARIES} +) + +if(WITH_XINERAMA) + list(APPEND INC_SYS ${X11_Xinerama_INCLUDE_PATH}) + list(APPEND DEF -DXINERAMA) + list(APPEND LIB ${X11_Xinerama_LIB}) +endif() + +add_executable(herbstluftwm ${SRC}) + +target_include_directories(herbstluftwm SYSTEM PUBLIC ${INC_SYS}) +target_compile_definitions(herbstluftwm PUBLIC ${DEF}) +target_link_libraries(herbstluftwm ${LIB}) + + +# ---------------------------------------------------------------------------- +# Program: 'herbstclient' + +set(INC_SYS + ${X11_X11_INCLUDE_PATH} + ${GLIB2_INCLUDE_DIRS} +) + +set(DEF + -D_XOPEN_SOURCE=600 + -DHERBSTLUFT_VERSION=\"${VERSION}\" + -DHERBSTLUFT_VERSION_MAJOR=\"${VERSION_MAJOR}\" + -DHERBSTLUFT_VERSION_MINOR=\"${VERSION_MINOR}\" + -DHERBSTLUFT_VERSION_PATCH=\"${VERSION_PATCH}\" +) + +set(LIB + ${X11_X11_LIB} + ${GLIB2_LIBRARIES} +) + +set(SRC + ipc-client/client-utils.c ipc-client/client-utils.h + ipc-client/ipc-client.c ipc-client/ipc-client.h + ipc-client/main.c +) + +add_executable(herbstclient ${SRC}) + +target_include_directories(herbstclient SYSTEM PUBLIC ${INC_SYS}) +target_compile_definitions(herbstclient PUBLIC ${DEF}) +target_link_libraries(herbstclient ${LIB}) + + +# ---------------------------------------------------------------------------- +# Install + +set(BINDIR ${DESTDIR}${CMAKE_INSTALL_PREFIX}/bin) +set(DATADIR ${DESTDIR}${CMAKE_INSTALL_PREFIX}/share) +set(MANDIR ${DATADIR}/man) +set(DOCDIR ${DATADIR}/doc/herbstluftwm) +set(EXAMPLESDIR ${DOCDIR}/examples) +set(LICENSEDIR ${DOCDIR}) +set(XSESSIONSDIR ${DATADIR}/xsessions) +set(ZSHCOMPLETIONDIR ${DATADIR}/zsh/functions/Completion/X) +set(BASHCOMPLETIONDIR ${SYSCONFDIR}/bash_completion.d) + + +install( + TARGETS + herbstluftwm + herbstclient + DESTINATION + ${BINDIR} +) +install( + PROGRAMS + ${CMAKE_SOURCE_DIR}/share/dmenu_run_hlwm + DESTINATION + ${BINDIR} +) + +install( + FILES + ${CMAKE_SOURCE_DIR}/BUGS + ${CMAKE_SOURCE_DIR}/NEWS + ${CMAKE_SOURCE_DIR}/INSTALL + DESTINATION + ${DOCDIR} +) + +install( + FILES + ${CMAKE_SOURCE_DIR}/LICENSE + DESTINATION + ${LICENSEDIR} +) + +install( + PROGRAMS + ${CMAKE_SOURCE_DIR}/share/autostart + ${CMAKE_SOURCE_DIR}/share/panel.sh + ${CMAKE_SOURCE_DIR}/share/restartpanels.sh + DESTINATION + ${CONFIGDIR} +) + +install( + FILES + ${CMAKE_SOURCE_DIR}/share/herbstclient-completion + DESTINATION + ${BASHCOMPLETIONDIR} +) +install( + FILES + ${CMAKE_SOURCE_DIR}/share/_herbstclient + DESTINATION + ${ZSHCOMPLETIONDIR} +) +install( + FILES + ${CMAKE_SOURCE_DIR}/share/herbstluftwm.desktop + DESTINATION + ${XSESSIONSDIR} +) + + +install( + FILES + ${CMAKE_SOURCE_DIR}/scripts/README + DESTINATION + ${EXAMPLESDIR} +) +install( + PROGRAMS + ${CMAKE_SOURCE_DIR}/scripts/dmenu.sh + ${CMAKE_SOURCE_DIR}/scripts/dumpbeautify.sh + ${CMAKE_SOURCE_DIR}/scripts/exec_on_tag.sh + ${CMAKE_SOURCE_DIR}/scripts/execwith.sh + ${CMAKE_SOURCE_DIR}/scripts/floatmon.sh + ${CMAKE_SOURCE_DIR}/scripts/herbstcommander.sh + ${CMAKE_SOURCE_DIR}/scripts/keychain.sh + ${CMAKE_SOURCE_DIR}/scripts/lasttag.sh + ${CMAKE_SOURCE_DIR}/scripts/layout.sh + ${CMAKE_SOURCE_DIR}/scripts/loadstate.sh + ${CMAKE_SOURCE_DIR}/scripts/q3terminal.sh + ${CMAKE_SOURCE_DIR}/scripts/savestate.sh + ${CMAKE_SOURCE_DIR}/scripts/scratchpad.sh + ${CMAKE_SOURCE_DIR}/scripts/wselect.sh + DESTINATION + ${EXAMPLESDIR} +) + +if(WITH_DOCUMENTATION) + find_program(ASCIIDOC_A2X NAMES a2x DOC "Path to AsciiDoc a2x command") + find_program(ASCIIDOC NAMES asciidoc DOC "Path to AsciiDoc command") + + function(doc_manpage_gen sourcefile man_nr) + set(sourcefile_target "doc_man_${sourcefile}") + + set(src_orig "${CMAKE_SOURCE_DIR}/doc/${sourcefile}.txt") + set(src "${CMAKE_BINARY_DIR}/doc/${sourcefile}.txt") + set(dst "${CMAKE_BINARY_DIR}/doc/${sourcefile}.${man_nr}") + + add_custom_target(${sourcefile_target} ALL DEPENDS ${dst}) + add_custom_command( + OUTPUT ${dst} + COMMAND ${CMAKE_COMMAND} -E make_directory "${CMAKE_BINARY_DIR}/doc/" + # asciidoc doesn't support destination directory for manpages + COMMAND ${CMAKE_COMMAND} -E copy ${src_orig} ${src} + COMMAND ${ASCIIDOC_A2X} + -f manpage + -a \"herbstluftwmversion=herbstluftwm ${VERSION}\" + -a \"date=`date +%Y-%m-%d`\" + ${src} + DEPENDS ${src_orig} + ) + install(FILES ${dst} DESTINATION "${MANDIR}/man${man_nr}") + endfunction() + + function(doc_html_gen sourcefile) + set(sourcefile_target "doc_html_${sourcefile}") + + set(src "${CMAKE_SOURCE_DIR}/doc/${sourcefile}.txt") + set(dst "${CMAKE_BINARY_DIR}/doc/${sourcefile}.html") + + add_custom_target(${sourcefile_target} ALL DEPENDS ${dst}) + add_custom_command( + OUTPUT ${dst} + COMMAND ${CMAKE_COMMAND} -E make_directory "${CMAKE_BINARY_DIR}/doc/" + COMMAND ${ASCIIDOC} -o ${dst} ${src} + DEPENDS ${src} + ) + install(FILES ${dst} DESTINATION ${DOCDIR}) + endfunction() + + doc_manpage_gen(herbstclient 1) + doc_manpage_gen(herbstluftwm 1) + doc_manpage_gen(herbstluftwm-tutorial 7) + + doc_html_gen(herbstclient) + doc_html_gen(herbstluftwm) + doc_html_gen(herbstluftwm-tutorial) +endif() + diff -Nru herbstluftwm-0.6.2/config.mk herbstluftwm-0.7.0/config.mk --- herbstluftwm-0.6.2/config.mk 2013-12-22 12:35:42.000000000 +0000 +++ herbstluftwm-0.7.0/config.mk 2015-10-14 13:27:40.000000000 +0000 @@ -2,12 +2,14 @@ X11INC = /usr/X11R6/include X11LIB = /usr/X11R6/lib +PKG_CONFIG ?= pkg-config + # Xinerama -XINERAMALIBS = `pkg-config --silence-errors --libs xinerama` -XINERAMAFLAGS = `pkg-config --exists xinerama && echo -DXINERAMA` +XINERAMALIBS = `$(PKG_CONFIG) --silence-errors --libs xinerama` +XINERAMAFLAGS = `$(PKG_CONFIG) --exists xinerama && echo -DXINERAMA` -INCS = -Isrc/ -I/usr/include -I${X11INC} `pkg-config --cflags glib-2.0` -LIBS = -lc -L${X11LIB} -lXext -lX11 $(XINERAMALIBS) `pkg-config --libs glib-2.0` +INCS = -Isrc/ -I/usr/include -I${X11INC} `$(PKG_CONFIG) --cflags glib-2.0` +LIBS = -lc -L${X11LIB} -lXext -lX11 $(XINERAMALIBS) `$(PKG_CONFIG) --libs glib-2.0` ifeq ($(shell uname),Linux) LIBS += -lrt @@ -16,8 +18,11 @@ # FLAGS CC ?= gcc LD = $(CC) +LDXX ?= g++ CFLAGS ?= -g CFLAGS += -pedantic -Wall -std=c99 +CXXFLAGS ?= -g +CXXFLAGS += -pedantic -Wall -std=c++11 -Wno-sign-compare -Wno-narrowing -Wno-deprecated-register VERSIONFLAGS = \ -D HERBSTLUFT_VERSION=\"$(VERSION)\" \ -D HERBSTLUFT_VERSION_MAJOR=$(VERSION_MAJOR) \ diff -Nru herbstluftwm-0.6.2/debian/changelog herbstluftwm-0.7.0/debian/changelog --- herbstluftwm-0.6.2/debian/changelog 2014-03-27 23:42:49.000000000 +0000 +++ herbstluftwm-0.7.0/debian/changelog 2016-02-22 15:26:09.000000000 +0000 @@ -1,3 +1,20 @@ +herbstluftwm (0.7.0-1~ubuntu15.10.1) wily; urgency=medium + + * No-change backport to wily + + -- Christoph Egger Mon, 22 Feb 2016 16:26:09 +0100 + +herbstluftwm (0.7.0-1) unstable; urgency=medium + + * New upstream release + * Add GPG key to uscan mechanism + * Enable hardening + * install bash completion to /usr/share (See #776954) + * enable parallel build + * Do not install redundant INSTALL file + + -- Christoph Egger Mon, 08 Feb 2016 22:31:16 +0100 + herbstluftwm (0.6.2-1) unstable; urgency=medium * New upstream bugfix release diff -Nru herbstluftwm-0.6.2/debian/rules herbstluftwm-0.7.0/debian/rules --- herbstluftwm-0.6.2/debian/rules 2014-01-01 20:18:36.000000000 +0000 +++ herbstluftwm-0.7.0/debian/rules 2016-02-08 21:51:46.000000000 +0000 @@ -1,18 +1,21 @@ #!/usr/bin/make -f +export DEB_BUILD_MAINT_OPTIONS = hardening=+all + %: - dh $@ + dh $@ --parallel override_dh_auto_clean: dh_auto_clean -- VERBOSE= COLOR=0 override_dh_auto_build: - dh_auto_build -- VERBOSE= COLOR=0 + dh_auto_build -- VERBOSE= COLOR=0 LDXXFLAGS="$(LDFLAGS)" override_dh_auto_install: - $(MAKE) PREFIX=/usr DESTDIR=$(CURDIR)/debian/herbstluftwm/ install + $(MAKE) PREFIX=/usr DESTDIR=$(CURDIR)/debian/herbstluftwm/ BASHCOMPLETIONDIR=/usr/share/bash-completion/completions install rm -rf $(CURDIR)/debian/herbstluftwm/usr/share/licenses \ - $(CURDIR)/debian/herbstluftwm/usr/share/doc/herbstluftwm/LICENSE + $(CURDIR)/debian/herbstluftwm/usr/share/doc/herbstluftwm/LICENSE \ + $(CURDIR)/debian/herbstluftwm/usr/share/doc/herbstluftwm/INSTALL override_dh_installwm: dh_installwm --priority=20 herbstluftwm diff -Nru herbstluftwm-0.6.2/debian/upstream/signing-key.asc herbstluftwm-0.7.0/debian/upstream/signing-key.asc --- herbstluftwm-0.6.2/debian/upstream/signing-key.asc 1970-01-01 00:00:00.000000000 +0000 +++ herbstluftwm-0.7.0/debian/upstream/signing-key.asc 2016-02-08 21:30:48.000000000 +0000 @@ -0,0 +1,163 @@ +-----BEGIN PGP PUBLIC KEY BLOCK----- +Version: GnuPG v1 + +mQINBEvHUwQBEAC2iD97lqnB71hOKCIuKQ3eEv4H7cKCo6LqPsM12yOOiyUAyxVx +FtvrJVP71JQKKZW8XeFD181UZFHzobH5Z3CD50mZN0sfbJAAcwODtg+s3X7JwGiw +oXtu8ywAYItIBk0RDmnfTq8o9FDyppumq5z9D9ot+IdrT2Cvs6c0LsK337dBM8cJ +27jWetIljrT96whyN2U5XX0YD5dNAiYYSXhzTeoAOXVZ/CWiF5SgxMTZzjrHPGne +MfLz90Qy2Mp97xOdS14ONnhkqAOQdkCOstVHwbOV+Cu8BeBh9vyqHE4nt6iLX6bL +g8SgW3FY17SF7fon9QoO61mxDK/9dQq7vRz/LJCLNpVDWlSRsqcQfU2XGaJT/ZLP +rYzDFK/Vnoa1uFHenkEfKuvNzKGEgjbrUjXPjmbhsIArQi14mSc4t8OcWd5zIx4v +J3+hW68AyNbDCodPsV0qra6QEPzN5xkHDfgnKtCE+2jKjWLnac2b79Kmruf6m1jh +yq0eCSjSVR/QGHgcZbZrxI+oq5ktRRs+sJA9h58ZAMC33y42i/kzCFCDEMIzQgcH +FX4q6JY4Gkh923lNxPe3XUIVIiQnjCMWTunxcnqWWFgoIV760EEHHzOfiPVUdy+J +5ydypt4OdEt4k8PfYdAlhCZrr9oN1UMhS/AZyljsxmx1pYyzVbJla9jpEQARAQAB +tCpUaG9yc3RlbiBXaXNzbWFubiA8cEB0aG9yc3Rlbi13aXNzbWFubi5kZT6JAjgE +EwECACIFAkvHUwQCGwMGCwkIBwMCBhUIAgkKCwQWAgMBAh4BAheAAAoJEGTvAoIc +r/gQ++MP/iwbOJ7nb/gsMr0xhp1mT/apHf87FI0nHa2h3Rz0zAwXBJRB4vh3mxf0 +FMSq95PRdtbDyhZUCWaJrZwKGr8k6Nbr8Gv7dBC4iZkijJUZIlMDRkxr+ImNvXYY +fJZS+H9pfM1n2YMatgZy1oozaS+ZrpLRcGu/Wm5CD+miqYSucgcIHTaSKC0Xbc8i +Q0XgOT+Xs3/0oDVfBA2rFDbtxwplWDlwtegJ/+hAIFxJOHWhqIppCFXurSUxZFm1 +puP7HeMsFeBn0d7ktYVLOgQg5ImWcn19rIqA5akKG/krx3XhC422SDzh/Bnja7VM +q79YDfhQgAqB4PrfhUxrdCJX8/rRF7toN8JmSc7BLnDORNsfN0fcG6JzPAjjNePN +ui8LIGd5+loxgeLsla5tsqINVz8+M/7u7LuFUWB5t7FBv/y/zbCqvmvBXTB5Fl1J +bRZX/x03h9xEojcQLQFKHTQ+tWwunxhHg6FDfxW4HKcORrawC5YsbKbdxGu93YDb +pvDqjuRLUMyVCm+KRRB6vS1DORSsyuxbG8LVtILbftRjHIdVVMX3Xy8ChUOnX8pq ++KwfMryf3puAPu0/19aLdcmgSaWvc55A6Uy+cfNiHFuqHbvlVCWT7bxuchnzkhMe +hk3di9IxCQIukH9xuYi3uq/pjwuWMJzUrvWLXtwOMbyfUapVIoJbtCpUaG9yc3Rl +biBXacOfbWFubiA8cEB0aG9yc3Rlbi13aXNzbWFubi5kZT6JAjgEEwEKACIFAk96 +EOECGwMGCwkIBwMCBhUIAgkKCwQWAgMBAh4BAheAAAoJEGTvAoIcr/gQsx8P/1wb +nx9B1ZO2PMB4pdAWGlvJMvp9Nftc1Hmeyl1c7+F/hUvMmvreHmHvPZeS/UtF2Ypz +P5ESYmMNeucYQVFNVBHriDzOKRRFeu4eRcFYYOgLFTE8wKgLXsSt9MTVCf/Z+56a +qGCHaOBl5esCm5BiQVmXH64erwLpuYXCs9wOKgsFhG0lQI9nsKJShdnlp+6ZLSOW +2kTWkQoS2R/iDN6pNkSBWk+nbduPXLZmKo40WjtyqmfIPBKi0KDpPnBaRYUMcIqC +rp1cwHRQh5N9C5JT0uK4Iqw7AgmtSVWDQoqB0RMHwyuyBjCp8L5lPsWwFPvpe2KC +4a9YmKjSblGU59mhn0wo7Ym6BCAYRu+QEJbR1j55KEnnCAtem6T8GOdFgQx3K+Fa +tt1IDAP8oXEOTKzq43nZPZuhqF5dJOD6RO6zJ5K+H/IOUMwvmVGjh7ObDA0MZHw6 +KySIzsyPgAxs7xxcwYbmZq5Om5Sy4ZRy+PQjdH/Jn5KFxIw+8w6lH4a4X+Xw1FeV +Qd98x3fDM8YltuNNLKNGdjyvM7YzM8Z6KHr6RttlHfIZ96W5OoeSqq+1Ey6cYtGy +NtlGQqMk7RmpC8S5XE9Y2xickc/dhQFK9wtpJPhRebz+1zLgjUZKQfNMu3o7+Czj +wcudL7rdZ5fSDksHYQe2fZo6qiOVLITbyFAxmPOYtCpUaG9yc3RlbiBXacOfbWFu +biA8cmUwNmh1eGFAY2lwLmNzLmZhdS5kZT6JAjkEEwEKACMFAlHGxIYCGwMHCwkI +BwMCAQYVCAIJCgsEFgIDAQIeAQIXgAAKCRBk7wKCHK/4EHqND/4j5YSbq8sGIQw0 +gVisW0COkEXtPSn3vC4p658hMJ96+6odmIELA8RNl//iFtWnXx+l9I7ioJiONn5M +EC+tqkO1RfxmfWX7xRFu3qZqAlBQaUvn2Yn6PElRsolYAqGjEF/rDi2BUwC013fr +yFsehkFKxVVXb/Lj3sT1xbhsk6ajIbnxmdSK+U+CQck0jM3wh9VmcyaiFzrehEkm +jCxF5SwMRRQQ1MHd57BQp4hCNHSM8dtybIQ5Lhgj65xaelE6Op6HaYQyoiv1k8r5 +3pb5Rqj8d7N+IIbDxqu2Xfv9+77biWwi7ywz+RdtxxBrnV+Lv4SvrT7PsQJv6gly +mZ87O+s+PovED4mFEieBRAtdusPCWUWBgX2XFPSB6yqRMZHiXu8Ko04OziNfw7bQ +uE4FI2LIXcrgI8GaGvr3S/8kXf3JJyJleipXu1fnqE6OoFa8cMFFU9HD+zJqmHIk +UZVqwUxv5tSDrAQVUlxEcmwurbbVld6b9/LQUrGVktn/tkeI4wUR14RckUfXaipM +AOfiUk8mr3c6Wp4b9LAhcTW1VW/JEfsp8vGyDeyxAI283GqdYzULdQxhUGgzyGI/ +zOybL1CjmwQDixMFALy6ncgoKLLQ5VScO+Zt6Yhjt0JhAPAPDuYOOdh8tyAnsh3V +GLhd7pXQuLazIfSAJWFvSU+x5B6GJbQsVGhvcnN0ZW4gV2nDn21hbm4gPGVkdUB0 +aG9yc3Rlbi13aXNzbWFubi5kZT6JAjkEEwEKACMFAlHGxZICGwMHCwkIBwMCAQYV +CAIJCgsEFgIDAQIeAQIXgAAKCRBk7wKCHK/4EJpfD/wNSK0oqarGrecBvxeKucsS +FWiDSVfRmOyr7rJHHTPQ8t+9leKehM7G2A/tG6OwlFh7qmBq7ZcJi7d6/RArSmO0 +YxKoqmbx7TaSvbzSkLxL2o9732bJi9ZlOXEW3tG39/T95eV1SZtKrEsxC3zr0YRq +ABai5FoyXmElg1LZHdwM+kVW5OnRUKUb89eMEVJvuEbIpZKTl53JBqJI2uuIX/Kl +wU0FPecOWFK1NpYpDV93btXrc97hM1KIId64srrMDR2UVwfh7tTMiA5n5HNR6GlS +fh9dVdZ3A9SNJkaH7JlCp8kEyCqaqVuRnVeuIAGcHgPLJYU3cf9sjuuXH0U14opd +pQc0/25sxVyxPXdkiNQH0yBoav+kHEa4/zyR9zsO6ij68GjHIIx98NcDT+u/DsTN +oqq6APaCd2SKX4fkmWSu9OWmXYZ9w+ii7Es7RvbSOfCCdEbiewaYbi6/IQOgh69g +vfM1jXd2C8a+zkdZmFkg6wNV8ZT5hDitAJOkaI05xQn1pRQPTuQ5sEHxPkqCIT8c +7kSWL6LTwvpxMhvyiKQbJU/wQYR8PlXiBAY2Owii1kagVvy9+dtgaG4ZlDP7WRj4 +G1WDGLPI9e4Ej9HzIwXgzSwwPTtkN+GWZJ/UYQhr6+b+zIyXMVEsZQo7Q6NzT3FA +vcLkYdz6ipnup3QldxMNM7QsVGhvcnN0ZW4gV2nDn21hbm4gPHVuaUB0aG9yc3Rl +bi13aXNzbWFubi5kZT6JAjgEEwEKACIFAk96EOECGwMGCwkIBwMCBhUIAgkKCwQW +AgMBAh4BAheAAAoJEGTvAoIcr/gQ1JQQAJ6IuobZUQHXZvHRQBT8MeiQDr/BBYwM +OyZljCMCPvWccQefhAc+qtuIo0eRZ7pomxmmc+1GACHVVCrqPQcAY9SuDwuxraJY +XycQJX+fHe/qcjlZu80O29Ig30XZOJr1ahpqyy0yOYUODUurmUwnKQS6VRZwVsbn ++6kNuz8VcC/pdL5rhrQionFI9JHrXcxHeH0URD3sPYUe2vH6jUQ1LzDEYGz/496B +invERJsc7XT4lH53G1sxtjfKcEcjqr3I6/iuYoO3NyS6oIsww3+SIzC5P+VzXKAR +QrXXss7OFVdnx4pm1vEHpD+EJnfHxOp/P0HSQhd5TDlzE+WeDYJr4fcpt3HM8X+y +/t25O4Es05ui2a//lqQLutIFHVfiLcggH2dZEuwQuT+EpHQYvjw7n78PFDPn3c1v +GYbEwtFGeY56qldA/3wfpkEAZ/TgZTDV+82X2KT+UcxZ/WVCjAt36aTvmbNWl+4C +6T88y1Vy1+9hBJjmZ/qFiDsKX4LMVnwJ6QN7Cngps5pNPQRJwFi+civElKeynItA +JVNSDSiywZ8R2EOhGo+KttTtUK3LgGdBBFKJPgg3Qa2G15QLqV5ECxj+4bKaQwfo +V49JWrGOcOW9c3b0Bo6GU6pV/VmIPt51HPkciE5wRUBHfsAkRCvM/mocgobKlMCX +xL5TeyHni2A9tCxUaG9yc3RlbiBXacOfbWFubiA8dGhvcnN0ZW4ud2lzc21hbm5A +ZmF1LmRlPokCOQQTAQoAIwUCUl3DUAIbAwcLCQgHAwIBBhUIAgkKCwQWAgMBAh4B +AheAAAoJEGTvAoIcr/gQbbcQAKM3iOaJHrSDFmheClXhhAhwmz+UusK3O4HIi7ed +939npENZ7TWSiM+gxEYAKLUJhcLKrZ35UOA9J04wHcvqg6YCMFLVDAeunIJC1I9F +8v5j3DrecgHH/bUWxLZ0YYUZcE0hsjcD8igZgo805zQ4Ok1vICSTTg+AZ8xioOPm +CpWET8nuRWGDxTGFtamk6mnrcBbRtgrH8S1YuG6IwIWwg+wApbPPtwI0Ra8hzCFG +ioTzPfvOf1erqC67TzV46bJ70vL8/XKm0eOLfmt0NpKyCnU+kMuTgMqGwUlFE11b +7C46/rLxuq7GAT94J+JcsaT0wv+38IGOtDWauAzGmKgB8Xx//qCBwWpzbN2xHaaV +4cv8NBj65K2wzuEPT3/xCiGRzrS8Ilh7Ke1yghU7EsXnCB1Ydo98zI5eeVr5hAHo +F5nEvuce75Wwet1KJnuT3OctonYfYtvE2dH7Evrp2cHIgXC1KWcS0Lr6Xqgm0xGl +iYSfx3DBebVEtwBi+qewT2EPcmy2evnXFqMJLIQJ93WlHSmR48Ko/DBLVgrW85xR +JZYclNNFsGcEf3AFNRXhZFA35xFx9JFhLqV093ve0hGp+5CW6Hw9eWbHTq/Ut0xt +LSwa5KJsCE1Yfy1vXC9lHQTWGBCdfYnEeK4jWAQ+TFXfvoYAm1HrlzxG+dRjbwaS +6D6/uQINBEvHUwQBEACYOjr964TeU0uLbQ2/Al3NHvVFh1DP+kL/yM6+whRKJYA6 +gkpTwYHcxHsp4Of+AjsUpQbb1MTLRAgQRY5l2ZUQZUA0G8WzLprCu9YmGFyw04lI +6BKKGaqhE5J8aZSdkvLLbb/0dK3etwH9VBQx3qr4uWDaOsIFCjr+8LHwUqq5upFi +tgaGhJzjA6sPsfUvRSIrtea1jFpryeVqtef3/I5zV28kv5E71rc+LInqcn12C5BO +c2YwlXGlIZFUCk6CpzEpCFSVqs8myRNKh8lRVqGEJEDWqpt+5srTATONS33Mg3vn +pM+6NBkxrN77hMpCnLBJCwaxVwCmofsiGn5S/NP4p13QEbWXOGjwuvBgMDYBIInh +usBe1pokKdl8j/XQ+iy0r7Rx7loIXkyok/Sqw1DVQkMtLkdPMvOXIQWzYlUU8zAU +UHRiV9fDuHNIeYVz5ijD/8UomYgyb1HI4aylTqd/e3RapXPTWD5yA2Q9pGxm5SDP +SwE+7Afw+LANH05fL0qDN4IEwKAfW51XtX5cS/jT/Z4fcUJ1pAi5MS12G7P40HQJ +6Z/j9wJCYQ9oq2h96fDHGe9eZUS+zEyThO8X+2dXfp6V4Cfrg9H8Wj5YBpg+e+xU +bb37+rY/LhJ5cXliIRidTVm6FkiI9JbSafEdFLawvHagLvrqySs3Acc5Og1YPQAR +AQABiQIfBBgBAgAJBQJLx1MEAhsMAAoJEGTvAoIcr/gQ7NIQAKEMV0jpNYhBjUTt +amygQ2+DV4tuV3J+TZWkZ9xHHUDE3QFqTjKTMS8/QjXzBwIt7KGsCS5xFh5Uvq2r +WJNBv5DTdAE6cGabU8/Agu2PPDpV1vTrexUnl1pBT577tJM1/58ZRlxYl/4Ia7vZ +hfKTIcgmUl5hnAnJDHtdRdmF0Xq2aINn7wkJu/Q6kkOwV/WCqXuHTfJehdoqxEqe +g920Y281qk7Cz9Wf5A7Ex/poT6DrqjNzPsQSXv8GZEk7GSsxdseXkb+D0QLqrJhL +jWaCWa2/HnIQBGOv4tI4rpWEA0GqEhzKyOIEKRwzaUTySk1UGTmjBbkE28aDMe2a +FylCXE1AkTUTpfiHA2ruLSaGC2Ywa3XKJXajEenYzwJp61xkGX7envvQhDpNH7S8 ++x23CEPWx/RUTtLzf2KKOtO+NqcPH+a4z3M2WloWLceqsVqNhP54W7wf3sOU95Ub +99Og2SAPbv5wWJ/UX3a9fWZn5MsLzPzTak0d4+c6wy8sn9YgX3rmG1YN3u+y9XRq +svrhslKb+4UR0JHdJkRwTYSHV9o3Hugsb6Q0hxjvRex8hclwXrURukrrmngkkHN2 +FdBtKtjYIcR9trUOWHvruh9Y9PcKNbBkgl4PVNRaeFFFmtED37P2jZ/2yd3Js2nl +YVw79k8m2QI3KPFsDeji1K14h4rcuQENBEzFTVYBCADFj/NWUawB+EAiAWoopXOB +xGe7ZV0WPHoRJnfMysSIOMne6f24kmdKjtO06l7PhjgFFKljW9b0o6O44Ul8fZ5l +zlaunImzfdC2LmuTYDOvcc/pjRWh9rb1Cg5qZhII2p0PcWuRSSPQqn3lylfMI6wm +zcvzHEX6ehvQ5JDhNWasYUG0Fx3BvTGsgA864S9WN5LAXLEWKfyhS5E6zE+mQJ1A +mLFF35RmnbllA67tIdxEhkqAFsOZTP3wLqCfUWSra8uvRG0VvRqVzJ7cigyIdJyc +kMK469PlcKalm2zvSda7CFZZlM21YFJLQsEJVeNnmPsdShJbrVhMP0VDj4qND1OD +ABEBAAGJAh8EGAEKAAkFAkzFTVYCGwwACgkQZO8Cghyv+BCNEw//Z7hPeng8YTiq +d/JXotu3eXZYS4Ll7++ZF0u0rJNyF07JadHygGelY4MuFH517mXjkgYiuWcLu66J +x6nQxco5qTbrKjDp7wCuxzCqMSqTNPWDIeJMeCpfA5OyXwnqb4L6uxbIknzLibM+ +Z1NPrFQuwKZSwg6MRZTu5l8ZflWGtahxXhPHZf4w2esGQ7afHxtsx1JpEKrOxn8s +ituaN7iJpVBxEavOnOeCAtHLP4R7rIxbnzLG8BC76f/Kk1+lyg52OmUSsROv3cop +ENfzojbG+GWp6VHjeDlmrg5Fzk61n/WHeLd+ggNeGoSeP1VnrPCvVGJxfydmCU18 +ufEizsE+Si1syikAixwzP5UoRtbUBpK5awhH2Ab3p4V9JJSHICimmzfYK1BWu5p/ +0BobkCviUfY7zfItqDuJyc0xwZiWpNSya9Y6LmfBKSy0DkCgBGXfDSKj2ctgLXZj +ZXgpA1T06qTTGObzk0kzi5EGABYiZPMnntEwLncOPL7MWSG04AXbfGbKLJosBNO6 +1kKuyC6UnIuuCowKwurqK7awMPJfxme0grKBMUyJ+dnYWsPYoshd2XdMCQ9f9TLC +j/Y6IzLOKQPT8E7ALnbzGjqgffkI078SnKFy1kARrBx5zFuZWSj54H7Ekqw1ewSU +ODYhukpYn6aU7J7ky2BWJxc6IMerOfW5Ay4ETMVM5hEIAMjiFddYB9PYVsOtZBPQ +9fjjWfFA3lWawz3P5ghsjUKLT1wa8IZNkkVKgK9SpqxiU7mq4a0juQxXpoCvdzOy +P2/4v/EUDMQHik9dhC7USwEqT79siPE2C+PsH4vb4qYIa1/EZeUOlB5YQcb8hMmK +wgi7ffxqeLPTDgUZKDmFKhXCjbh5LzsNANYnSzbw73RGRtAmThjAxl5ZNHAhPTJc +y32twULXFlHVYtfnhxCipc5dAlOlfHMMkB4Tc+uXj6If+w1L0Ffdd2/ptbY8UFDZ +7YRVnoOtto7eXcNMy8gBhwee3XZ3bypv6/qL0xJDRiRZy5SJvq3sdib35Wn42dW3 +ia8BAKQnaMKffcbQNJvBRL1gy+hDA87vPveLI2wxFB2upXZvB/93yUPIj5fe+qlg +DeS3hyL/6rGt36sQUZ3Lnj/hwAnMYE4+95s4u49KrkxWkzneFWz1+i3Dtv7p1w0p +VO2WQJ5l2MwTXpwhgkV8++AJ+teE8X2LFAfUPjy05y1VdKH2ulOKzEihMAfn9Cr1 +778bheT3hHZeIdCfj3+jlAo89/maDpXv03vl9mW8PRy3ANYMZdvP1e5Bsy6zjTqu +Ocgkt2ZirN3SBgPatOVkN8+liJj+xt6ixNuQtkkxN2C3Kc+ZC9ZdzxN86Y4x/k/P +2P5mqahsL4v/AKNoEmTnOjnCs1aq7/CjWL+n+4WGNKCMUIFJ1EdGMj1Wdm+jTbue +voKx+4u3B/4xLCPY1In963j1RE+9XcKoQxIv9hIhLe5+hG6sAP+7CXsKkM8TL8AN +vDsLdL5xHIXTu6U3bubByf2yepwCYmMrXayttH6B78JflPCazRsrTzhsQBcqNbC1 +Zm2ejzn3uK/6RJAfn7jZVv1T3Y10Pv+oadYaK5qtGmnCFieKtgGrhHHjp5uMZWV5 +9xEzppHpSxuCO38yeLj4zNLB5dhqvbq64A4Y0inGS+Q5DCzZXrlawD+WOsBRRJSN +UUhcfLQRGYhLYiLj4/2QJ8pE461u1gkCO0yXveVr9iNnLnvD394uIPKrCDZCUAe3 +CMU4+/+UdjhFjasVBRFFYzlywKcADTHNiQJ/BBgBCgAJBQJMxUzmAhsCAGoJEGTv +AoIcr/gQXyAEGREKAAYFAkzFTOYACgkQLH6Er6Io2LoxVwEAkBYgtdSKOL+F8Fxp +Rgu6zK2KjYesqEIIGQ0/P2dgYtgA/2yMYgrHnA+hvzQ35HgXYjEa6TDAdZguZqYz +/gA4ae/fQL4QAKes/gi5S/f0zZNUxVJKMEjU7cG9mdXvxSA73/GXiiUtcMncFuB0 +eU+vm41moYqCB+PBSR13S7pbQ79HD29e5HltB9OAV7ce0iToFCeJ1hsYTzQg8fvj +4/dZU7vkoav8y0rudZCgr7HyuYFnRFcHVN4degDPU4ABJ4j1dbg2qME0ImP6s4ed +T2Wjc1RUN0G32zUNKg3pUfKtnq9vqoxvYl7TH1NwBaXSpn6NzOQwELTRHmTl64E2 +nOFk8xB0JZBfEpQPcpFvBweKmigDxj2/xJcaKH03JcgAXsau3VJQFSkhSpaMZ1OT +zvNfS4rI0zKjwgW3xaQ6noCJpciQNQ6wndooRbROHFi4roJpmhU3eecV6alOYslV +w5AtG30A6Qq+pXIPexs+rYSGHdpR4c4y07yUBnVnJivONDG2mvsjFNFtEKqcBiSS +Jd+wlqJADM6IT2J5G5BUv4vz2QyI7AXKCgRdTeEpevtw5YMMUGf0EtbKAc0NJAvj +v247do6+ku9URLKak5RCAhgd0Y8kzzZzVajwp9oHCtH2sNBHtwJvE9dEaklqAzp7 +BW78z2VP5Mp1etCZdOnaZ829I1lbq3p2PUPXuffVvrdDlobcAdFTFjkOJKyQwlIh +UIM56HHAX/H623U8Gt8c4usQXyn9d42baAWR9MiF7qIapRF9YVa7TL65 +=ytYb +-----END PGP PUBLIC KEY BLOCK----- diff -Nru herbstluftwm-0.6.2/debian/watch herbstluftwm-0.7.0/debian/watch --- herbstluftwm-0.6.2/debian/watch 2014-01-01 20:19:02.000000000 +0000 +++ herbstluftwm-0.7.0/debian/watch 2016-02-08 21:30:48.000000000 +0000 @@ -1,3 +1,4 @@ version=3 +opts=pgpsigurlmangle=s/$/.sig/ \ http://herbstluftwm.org/tarballs/herbstluftwm-([\d.]+).tar.gz diff -Nru herbstluftwm-0.6.2/doc/herbstclient.1 herbstluftwm-0.7.0/doc/herbstclient.1 --- herbstluftwm-0.6.2/doc/herbstclient.1 2014-03-27 01:54:20.000000000 +0000 +++ herbstluftwm-0.7.0/doc/herbstclient.1 2016-02-04 17:00:31.000000000 +0000 @@ -1,13 +1,13 @@ '\" t .\" Title: herbstclient .\" Author: [see the "AUTHOR" section] -.\" Generator: DocBook XSL Stylesheets v1.78.1 -.\" Date: 2014-03-27 +.\" Generator: DocBook XSL Stylesheets v1.79.1 +.\" Date: 2016-02-04 .\" Manual: \ \& -.\" Source: \ \& herbstluftwm 0.6.2\e \e(80c5fe4\e) +.\" Source: \ \& herbstluftwm 0.7.0\e \e(c179281\e) .\" Language: English .\" -.TH "HERBSTCLIENT" "1" "2014\-03\-27" "\ \& herbstluftwm 0\&.6\&.2\e" "\ \&" +.TH "HERBSTCLIENT" "1" "2016\-02\-04" "\ \& herbstluftwm 0\&.7\&.0\e" "\ \&" .\" ----------------------------------------------------------------- .\" * Define some portability stuff .\" ----------------------------------------------------------------- @@ -48,6 +48,19 @@ Do not print a newline if output does not end with a newline\&. .RE .PP +\fB\-0\fR, \fB\-\-print0\fR +.RS 4 +Use the null character as delimiter between the output of hooks\&. +.RE +.PP +\fB\-l\fR, \fB\-\-last\-arg\fR +.RS 4 +When using +\fB\-i\fR +or +\fB\-w\fR, only print the last argument of the hook\&. +.RE +.PP \fB\-i\fR, \fB\-\-idle\fR .RS 4 Wait for hooks instead of executing commands\&. @@ -127,7 +140,7 @@ .sp Homepage: http://herbstluftwm\&.org .sp -Gitweb: http://git\&.cs\&.fau\&.de/?p=re06huxa/herbstluftwm +Github page: http://github\&.com/herbstluftwm/herbstluftwm .sp Patch submission and bug reporting: .sp diff -Nru herbstluftwm-0.6.2/doc/herbstclient.html herbstluftwm-0.7.0/doc/herbstclient.html --- herbstluftwm-0.6.2/doc/herbstclient.html 2014-03-27 01:54:22.000000000 +0000 +++ herbstluftwm-0.7.0/doc/herbstclient.html 2016-02-04 17:00:31.000000000 +0000 @@ -778,6 +778,22 @@

+-0, --print0 +
+
+

+ Use the null character as delimiter between the output of hooks. +

+
+
+-l, --last-arg +
+
+

+ When using -i or -w, only print the last argument of the hook. +

+
+
-i, --idle
@@ -895,7 +911,7 @@

RESOURCES

- +

Patch submission and bug reporting:

@@ -915,7 +931,7 @@

diff -Nru herbstluftwm-0.6.2/doc/herbstclient.txt herbstluftwm-0.7.0/doc/herbstclient.txt --- herbstluftwm-0.6.2/doc/herbstclient.txt 2014-03-26 23:54:22.000000000 +0000 +++ herbstluftwm-0.7.0/doc/herbstclient.txt 2015-10-14 13:27:40.000000000 +0000 @@ -35,6 +35,12 @@ *-n*, *--no-newline*:: Do not print a newline if output does not end with a newline. +*-0*, *--print0*:: + Use the null character as delimiter between the output of hooks. + +*-l*, *--last-arg*:: + When using *-i* or *-w*, only print the last argument of the hook. + *-i*, *--idle*:: Wait for hooks instead of executing commands. @@ -92,7 +98,7 @@ --------- Homepage: -Gitweb: +Github page: Patch submission and bug reporting: diff -Nru herbstluftwm-0.6.2/doc/herbstluftwm.1 herbstluftwm-0.7.0/doc/herbstluftwm.1 --- herbstluftwm-0.6.2/doc/herbstluftwm.1 2014-03-27 01:54:35.000000000 +0000 +++ herbstluftwm-0.7.0/doc/herbstluftwm.1 2016-02-04 17:00:33.000000000 +0000 @@ -1,13 +1,13 @@ '\" t .\" Title: herbstluftwm .\" Author: [see the "AUTHOR" section] -.\" Generator: DocBook XSL Stylesheets v1.78.1 -.\" Date: 2014-03-27 +.\" Generator: DocBook XSL Stylesheets v1.79.1 +.\" Date: 2016-02-04 .\" Manual: \ \& -.\" Source: \ \& herbstluftwm 0.6.2\e \e(80c5fe4\e) +.\" Source: \ \& herbstluftwm 0.7.0\e \e(c179281\e) .\" Language: English .\" -.TH "HERBSTLUFTWM" "1" "2014\-03\-27" "\ \& herbstluftwm 0\&.6\&.2\e" "\ \&" +.TH "HERBSTLUFTWM" "1" "2016\-02\-04" "\ \& herbstluftwm 0\&.7\&.0\e" "\ \&" .\" ----------------------------------------------------------------- .\" * Define some portability stuff .\" ----------------------------------------------------------------- @@ -930,6 +930,11 @@ Closes the focused window or removes the current frame if no window is focused\&. .RE .PP +close_and_remove +.RS 4 +Closes the focused window and removes the current frame if no other window is present in that frame\&. +.RE +.PP split \fIALIGN\fR [\fIFRACTION\fR] .RS 4 Splits the focused frame into two subframes with a specified @@ -1138,9 +1143,11 @@ \fIDIRECTION\fR will be focused\&. + The direction between frames is defined as follows: The focus is in a leaf of the binary tree\&. Each inner node in the tree remembers the last focus direction (child 0 or child 1)\&. The algorithm uses the shortest possible way from the leaf (the currently focused frame) to the root until it is possible to change focus in the specified \fIDIRECTION\fR\&. From there the focus goes back to the leaf\&. + Example: The focus is at frame A\&. After executing \fIfocus right\fR focus will be at frame C\&. @@ -1149,19 +1156,19 @@ .RS 4 .\} .nf - Tree: V,0 Screen: \&.\-\-\-\-\-\&.\&.\-\-\-\-\-\&. (before) - / \e | B || C | - / \e \*(Aq\-\-\-\-\-\*(Aq\*(Aq\-\-\-\-\-\*(Aq - H,1 H,0 \&.\-\-\-\-\-\&.\&.\-\-\-\-\-\&. - / \e / \e | A* || D | - A* B C D \*(Aq\-\-\-\-\-\*(Aq\*(Aq\-\-\-\-\-\*(Aq - - Tree: V,0 Screen: \&.\-\-\-\-\-\&.\&.\-\-\-\-\-\&. (after focus right) - / \e | B || C* | - / \e \*(Aq\-\-\-\-\-\*(Aq\*(Aq\-\-\-\-\-\*(Aq - H,1 H,0 \&.\-\-\-\-\-\&.\&.\-\-\-\-\-\&. - / \e / \e | A || D | - A B C* D \*(Aq\-\-\-\-\-\*(Aq\*(Aq\-\-\-\-\-\*(Aq + Tree: V,0 Screen: ┌─────┐┌─────┐ (before) + ╱ ╲ │ B ││ C │ + ╱ ╲ └─────┘└─────┘ + H,1 H,0 ┌─────┐┌─────┐ + ╱ ╲ ╱ ╲ │ A* ││ D │ + A* B C D └─────┘└─────┘ + + Tree: V,0 Screen: ┌─────┐┌─────┐ (after focus right) + ╱ ╲ │ B ││ C* │ + ╱ ╲ └─────┘└─────┘ + H,1 H,0 ┌─────┐┌─────┐ + ╱ ╲ ╱ ╲ │ A ││ D │ + A B C* D └─────┘└─────┘ .fi .if n \{\ .RE @@ -1192,6 +1199,7 @@ \fIfocus\fR command\&. + If \fI\-i\fR (internal) is given or default_direction_external_only is unset, then the window on the edge of the tag will be focused\&. Else, only the frame on the edge of the tag will be focused, and the window that was last focused in that frame will be focused\&. @@ -1538,12 +1546,13 @@ .RS 4 .\} .nf -11111111 11111111 -1 222222222 333222224444 -1 2 1 2 disjoin 3 32 24 4 -11121111 2 \-\-\-\-\-\-\-\-> 333222224444 - 2 2 555555555 - 222222222 555555555 +┌──────┐ ┌──────┐ +│ │ └──────┘ +│ ┌───┼───┐ ┌─┐┌───┐┌──┐ +│ │ │ │ disjoin │ ││ ││ │ +└──┼───┘ │ ─────────> └─┘└───┘└──┘ + │ │ ┌───────┐ + └───────┘ └───────┘ .fi .if n \{\ .RE @@ -2099,6 +2108,7 @@ .IP \(bu 2.3 .\} substitute MYTITLE clients\&.focus\&.title echo MYTITLE + Prints the title of the currently focused window\&. .RE .RE @@ -2129,6 +2139,7 @@ .IP \(bu 2.3 .\} sprintf STR title=%s clients\&.focus\&.title echo STR + Prints the title of the currently focused window prepended by title=\&. .RE @@ -2142,6 +2153,7 @@ .IP \(bu 2.3 .\} sprintf X tag=%s tags\&.focus\&.name rule once X + Moves the next client that appears to the tag that is currently focused\&. .RE .sp @@ -2154,6 +2166,7 @@ .IP \(bu 2.3 .\} sprintf X %s/%s tags\&.focus\&.index tags\&.count echo X + Tells which tag is focused and how many tags there are .RE .sp @@ -2166,6 +2179,7 @@ .IP \(bu 2.3 .\} sprintf l somelongstring echo l l l + Prints somelongstring three times, separated by spaces\&. @@ -2275,7 +2289,8 @@ .PP .RS 4 The -\fIOPERATORs\fR\fBle\fR,\fBlt\fR,\fBge\fR,\fBgt\fR +\fIOPERATORs\fR +\fBle\fR,\fBlt\fR,\fBge\fR,\fBgt\fR can only be used if \fIATTRIBUTE\fR is of the type integer or unsigned integer\&. Note that the first parameter must always be an attribute and the second a constant value\&. If you want to compare two attributes, use the substitute command: @@ -2508,6 +2523,7 @@ .RS 4 If set and a window is focused by mouse cursor, this window is focused (this feature is also known as sloppy focus)\&. If unset, you need to click to change the window focus by mouse\&. + If another window is hidden by the focus change (e\&.g\&. when having pseudotiled windows in the max layout) then an extra click is required to change the focus\&. .RE .PP @@ -2825,7 +2841,9 @@ hook .RS 4 emits the custom hook -rule\fIVALUE\fR\fIWINID\fR +rule +\fIVALUE\fR +\fIWINID\fR when this rule is triggered by a new window with the id \fIWINID\fR\&. This consequence can be used multiple times, which will cause a hook to be emitted for each occurrence of a hook consequence\&. .RE @@ -2905,6 +2923,7 @@ .IP \(bu 2.3 .\} rule \-\-class=Netscape \-\-tag=6 \-\-focus=off + Moves all Netscape instances to tag 6, but doesn\(cqt give focus to them\&. .RE .sp @@ -2917,6 +2936,7 @@ .IP \(bu 2.3 .\} rule not class~\&.*[Tt]erm tag=2 + Moves all clients to tag 2, if their class does not end with term or @@ -2932,6 +2952,7 @@ .IP \(bu 2.3 .\} rule class=Thunderbird index=/0 + Insert all Thunderbird instances in the tree that has no focus and there in the first child\&. .RE .sp @@ -2944,6 +2965,7 @@ .IP \(bu 2.3 .\} rule \-\-windowtype=_NET_WM_WINDOW_TYPE_DIALOG \-\-focus=on + Sets focus to new dialogs which set their _NET_WM_WINDOW_TYPE correctly\&. @@ -3258,6 +3280,18 @@ .sp -1 .IP \(bu 2.3 .\} +\fIindex\fR: the object of the tag with index +\fIindex\fR\&. +.RE +.sp +.RS 4 +.ie n \{\ +\h'-04'\(bu\h'+03'\c +.\} +.el \{\ +.sp -1 +.IP \(bu 2.3 +.\} by\-name .sp .RS 4 @@ -3268,7 +3302,7 @@ .sp -1 .IP \(bu 2.3 .\} -\fITAG\fR: a object for each tag with the name +\fITAG\fR: an object for each tag with the name \fITAG\fR .TS allbox tab(:); @@ -3954,6 +3988,44 @@ .RE .sp All monitors are managed in one large stack which only consists of the stacks of the visible tags put above each other\&. The stacking order of these monitors is independent from their indices and can be modified using the \fBraise_monitor\fR command\&. The current stack is illustrated by the \fBstack\fR command\&. +.SH "EWMH" +.sp +As far as possible, herbstluftwm tries to be EWMH compliant\&. That includes: +.sp +.RS 4 +.ie n \{\ +\h'-04'\(bu\h'+03'\c +.\} +.el \{\ +.sp -1 +.IP \(bu 2.3 +.\} +Information about tag names and client lists is provided\&. +.RE +.sp +.RS 4 +.ie n \{\ +\h'-04'\(bu\h'+03'\c +.\} +.el \{\ +.sp -1 +.IP \(bu 2.3 +.\} +Desktop windows from desktop environments are not managed and kept below the other windows\&. +.RE +.sp +.RS 4 +.ie n \{\ +\h'-04'\(bu\h'+03'\c +.\} +.el \{\ +.sp -1 +.IP \(bu 2.3 +.\} +Client requests like getting focused are only processed if the setting +\fIfocus_stealing_prevention\fR +is disabled\&. +.RE .SH "ENVIRONMENT VARIABLES" .PP DISPLAY @@ -3962,6 +4034,21 @@ \fIDISPLAY\fR to use\&. .RE +.SH "FILES" +.sp +The following files are used by herbstluftwm: +.sp +.RS 4 +.ie n \{\ +\h'-04'\(bu\h'+03'\c +.\} +.el \{\ +.sp -1 +.IP \(bu 2.3 +.\} +\fIautostart\fR, see section +\fBAUTOSTART FILE\fR\&. +.RE .SH "EXIT STATUS" .sp Returns \fB0\fR on success\&. Returns EXIT_FAILURE if it cannot startup or if \fIwmexec\fR fails\&. @@ -3978,7 +4065,7 @@ .sp Homepage: http://herbstluftwm\&.org .sp -Gitweb: http://git\&.cs\&.fau\&.de/?p=re06huxa/herbstluftwm +Github page: http://github\&.com/herbstluftwm/herbstluftwm .sp Patch submission and bug reporting: .sp diff -Nru herbstluftwm-0.6.2/doc/herbstluftwm.html herbstluftwm-0.7.0/doc/herbstluftwm.html --- herbstluftwm-0.6.2/doc/herbstluftwm.html 2014-03-27 01:54:46.000000000 +0000 +++ herbstluftwm-0.7.0/doc/herbstluftwm.html 2016-02-04 17:00:35.000000000 +0000 @@ -1461,6 +1461,15 @@

+close_and_remove +
+
+

+ Closes the focused window and removes the current frame if no other window + is present in that frame. +

+
+
split ALIGN [FRACTION]
@@ -1591,19 +1600,19 @@

-
 Tree:  V,0     Screen: .-----..-----. (before)
-        / \             |  B  ||  C  |
-       /   \            '-----''-----'
-     H,1   H,0          .-----..-----.
-     / \   / \          |  A* ||  D  |
-    A*  B C   D         '-----''-----'
+
 Tree:  V,0     Screen: ┌─────┐┌─────┐ (before)
+        ╱ ╲             │  B  ││  C  │
+       ╱   ╲            └─────┘└─────┘
+     H,1   H,0          ┌─────┐┌─────┐
+     ╱ ╲   ╱ ╲          │  A* ││  D  │
+    A*  B C   D         └─────┘└─────┘
 
- Tree:  V,0     Screen: .-----..-----. (after focus right)
-        / \             |  B  ||  C* |
-       /   \            '-----''-----'
-     H,1   H,0          .-----..-----.
-     / \   / \          |  A  ||  D  |
-    A   B C*  D         '-----''-----'
+ Tree: V,0 Screen: ┌─────┐┌─────┐ (after focus right) + ╱ ╲ │ B ││ C* │ + ╱ ╲ └─────┘└─────┘ + H,1 H,0 ┌─────┐┌─────┐ + ╱ ╲ ╱ ╲ │ A ││ D │ + A B C* D └─────┘└─────┘
@@ -1948,12 +1957,13 @@

-
11111111                 11111111
-1  222222222             333222224444
-1  2   1   2   disjoin   3 32   24  4
-11121111   2  -------->  333222224444
-   2       2                555555555
-   222222222                555555555
+
┌──────┐                  ┌──────┐
+│      │                  └──────┘
+│  ┌───┼───┐              ┌─┐┌───┐┌──┐
+│  │   │   │   disjoin    │ ││   ││  │
+└──┼───┘   │  ─────────>  └─┘└───┘└──┘
+   │       │                 ┌───────┐
+   └───────┘                 └───────┘
@@ -3464,12 +3474,17 @@
  • +index: the object of the tag with index index. +

    +
  • +
  • +

    by-name

    • -TAG: a object for each tag with the name TAG
      +TAG: an object for each tag with the name TAG

      -

      AUTOSTART FILE

      +

      AUTOSTART FILE

      There is no configuration file but an autostart file, which is executed on startup. It is also executed on command reload. If not specified by the @@ -3982,6 +3997,31 @@

      +

      EWMH

      +
      +

      As far as possible, herbstluftwm tries to be EWMH compliant. That includes:

      +
        +
      • +

        +Information about tag names and client lists is provided. +

        +
      • +
      • +

        +Desktop windows from desktop environments are not managed and kept below the + other windows. +

        +
      • +
      • +

        +Client requests like getting focused are only processed if the setting + focus_stealing_prevention is disabled. +

        +
      • +
      +
      +
      +

      ENVIRONMENT VARIABLES

      @@ -3997,6 +4037,19 @@
      +

      FILES

      +
      +

      The following files are used by herbstluftwm:

      +
      +
      +
      +

      EXIT STATUS

      Returns 0 on success. Returns EXIT_FAILURE if it cannot startup or if @@ -4026,7 +4079,7 @@

      RESOURCES

      - +

      Patch submission and bug reporting:

      @@ -4046,7 +4099,7 @@

      diff -Nru herbstluftwm-0.6.2/doc/herbstluftwm-tutorial.7 herbstluftwm-0.7.0/doc/herbstluftwm-tutorial.7 --- herbstluftwm-0.6.2/doc/herbstluftwm-tutorial.7 2014-03-27 01:54:49.000000000 +0000 +++ herbstluftwm-0.7.0/doc/herbstluftwm-tutorial.7 2016-02-04 17:00:35.000000000 +0000 @@ -1,13 +1,13 @@ '\" t .\" Title: herbstluftwm-tutorial .\" Author: [FIXME: author] [see http://docbook.sf.net/el/author] -.\" Generator: DocBook XSL Stylesheets v1.78.1 -.\" Date: 2014-03-27 +.\" Generator: DocBook XSL Stylesheets v1.79.1 +.\" Date: 2016-02-04 .\" Manual: \ \& -.\" Source: \ \& herbstluftwm 0.6.2\e \e(80c5fe4\e) +.\" Source: \ \& herbstluftwm 0.7.0\e \e(c179281\e) .\" Language: English .\" -.TH "HERBSTLUFTWM\-TUTORI" "7" "2014\-03\-27" "\ \& herbstluftwm 0\&.6\&.2\e" "\ \&" +.TH "HERBSTLUFTWM\-TUTORI" "7" "2016\-02\-04" "\ \& herbstluftwm 0\&.7\&.0\e" "\ \&" .\" ----------------------------------------------------------------- .\" * Define some portability stuff .\" ----------------------------------------------------------------- @@ -87,7 +87,7 @@ .sp -1 .IP \(bu 2.3 .\} -Multi-Monitor handling +Multi\-Monitor handling .RE .SH "BASIC INSTALLATION" .sp @@ -118,7 +118,7 @@ .RS 4 .\} .nf -git clone git://git\&.cs\&.fau\&.de/hlwm herbstluftwm +git clone git://github\&.com/herbstluftwm/herbstluftwm cd herbstluftwm make # build the binaries @@ -417,7 +417,7 @@ .if n \{\ .RE .\} -.SH "TAGS (OR WORKSPACES OR VIRTUAL DESKTOPS OR \&....)" +.SH "TAGS (OR WORKSPACES OR VIRTUAL DESKTOPS OR \&...\&.)" .sp A tag consists of a name and a frame layout with clients on it\&. With the default autostart, there are nine tags named 1 to 9\&. You can switch to the ith tag using Mod\-i, e\&.g\&. Mod\-4 to switch to tag 4 or with the command use 4\&. A window can be move to tag i via Mod\-Shift\-i, i\&.e\&. with the move command\&. .SH "MONITORS" diff -Nru herbstluftwm-0.6.2/doc/herbstluftwm-tutorial.html herbstluftwm-0.7.0/doc/herbstluftwm-tutorial.html --- herbstluftwm-0.6.2/doc/herbstluftwm-tutorial.html 2014-03-27 01:54:52.000000000 +0000 +++ herbstluftwm-0.7.0/doc/herbstluftwm-tutorial.html 2016-02-04 17:00:36.000000000 +0000 @@ -812,7 +812,7 @@ version, then you can pull directly from the main repository:

      -
      git clone git://git.cs.fau.de/hlwm herbstluftwm
      +
      git clone git://github.com/herbstluftwm/herbstluftwm
       cd herbstluftwm
       make # build the binaries
       
      @@ -1093,7 +1093,7 @@
       

      diff -Nru herbstluftwm-0.6.2/doc/herbstluftwm-tutorial.txt herbstluftwm-0.7.0/doc/herbstluftwm-tutorial.txt --- herbstluftwm-0.6.2/doc/herbstluftwm-tutorial.txt 2014-03-26 23:54:22.000000000 +0000 +++ herbstluftwm-0.7.0/doc/herbstluftwm-tutorial.txt 2015-10-14 13:27:40.000000000 +0000 @@ -56,7 +56,7 @@ version, then you can pull directly from the main repository: ---- -git clone git://git.cs.fau.de/hlwm herbstluftwm +git clone git://github.com/herbstluftwm/herbstluftwm cd herbstluftwm make # build the binaries diff -Nru herbstluftwm-0.6.2/doc/herbstluftwm.txt herbstluftwm-0.7.0/doc/herbstluftwm.txt --- herbstluftwm-0.6.2/doc/herbstluftwm.txt 2014-03-26 23:54:22.000000000 +0000 +++ herbstluftwm-0.7.0/doc/herbstluftwm.txt 2015-10-14 13:27:40.000000000 +0000 @@ -353,6 +353,10 @@ Closes the focused window or removes the current frame if no window is focused. +close_and_remove:: + Closes the focused window and removes the current frame if no other window + is present in that frame. + split 'ALIGN' ['FRACTION']:: Splits the focused frame into two subframes with a specified 'FRACTION' between 0 and 1 which defaults to 0.5. 'ALIGN' is one of @@ -405,19 +409,19 @@ be at frame C. + ---- - Tree: V,0 Screen: .-----..-----. (before) - / \ | B || C | - / \ '-----''-----' - H,1 H,0 .-----..-----. - / \ / \ | A* || D | - A* B C D '-----''-----' - - Tree: V,0 Screen: .-----..-----. (after focus right) - / \ | B || C* | - / \ '-----''-----' - H,1 H,0 .-----..-----. - / \ / \ | A || D | - A B C* D '-----''-----' + Tree: V,0 Screen: ┌─────┐┌─────┐ (before) + ╱ ╲ │ B ││ C │ + ╱ ╲ └─────┘└─────┘ + H,1 H,0 ┌─────┐┌─────┐ + ╱ ╲ ╱ ╲ │ A* ││ D │ + A* B C D └─────┘└─────┘ + + Tree: V,0 Screen: ┌─────┐┌─────┐ (after focus right) + ╱ ╲ │ B ││ C* │ + ╱ ╲ └─────┘└─────┘ + H,1 H,0 ┌─────┐┌─────┐ + ╱ ╲ ╱ ╲ │ A ││ D │ + A B C* D └─────┘└─────┘ ---- :: If the currently focused client is floated, then the next floating window in @@ -583,12 +587,13 @@ graphically means: + ---- -11111111 11111111 -1 222222222 333222224444 -1 2 1 2 disjoin 3 32 24 4 -11121111 2 --------> 333222224444 - 2 2 555555555 - 222222222 555555555 +┌──────┐ ┌──────┐ +│ │ └──────┘ +│ ┌───┼───┐ ┌─┐┌───┐┌──┐ +│ │ │ │ disjoin │ ││ ││ │ +└──┼───┘ │ ─────────> └─┘└───┘└──┘ + │ │ ┌───────┐ + └───────┘ └───────┘ ---- set_monitors 'RECTS' ...:: @@ -1348,8 +1353,9 @@ |=========================== u - count , number of tags |=========================== + ** 'index': the object of the tag with index 'index'. ** +by-name+ - *** 'TAG': a object for each tag with the name 'TAG' + + *** 'TAG': an object for each tag with the name 'TAG' + + [format="csv",cols="m,"] |=========================== @@ -1466,7 +1472,7 @@ ** +urgent+ propagates the attribute values to +tiling.urgent+ and +floating.urgent+ - +[[AUTOSTART]] AUTOSTART FILE -------------- @@ -1548,11 +1554,29 @@ independent from their indices and can be modified using the *raise_monitor* command. The current stack is illustrated by the *stack* command. +[[EWMH]] +EWMH +---- +As far as possible, herbstluftwm tries to be EWMH compliant. That includes: + + - Information about tag names and client lists is provided. + - Desktop windows from desktop environments are not managed and kept below the + other windows. + - Client requests like getting focused are only processed if the setting + 'focus_stealing_prevention' is disabled. + ENVIRONMENT VARIABLES --------------------- DISPLAY:: Specifies the 'DISPLAY' to use. + +FILES +----- +The following files are used by herbstluftwm: + + - 'autostart', see section <>. + EXIT STATUS ----------- Returns *0* on success. Returns +EXIT_FAILURE+ if it cannot startup or if @@ -1575,7 +1599,7 @@ --------- Homepage: -Gitweb: +Github page: Patch submission and bug reporting: diff -Nru herbstluftwm-0.6.2/HACKING herbstluftwm-0.7.0/HACKING --- herbstluftwm-0.6.2/HACKING 2014-03-26 23:54:22.000000000 +0000 +++ herbstluftwm-0.7.0/HACKING 2015-10-14 13:27:40.000000000 +0000 @@ -40,6 +40,30 @@ make cleandeps +Alternate build system: CMake +----------------------------- + +CMake has been included as an alternative build-system, +that has some advantages including: + + - Project file generation for IDEs. + - Flexible configuration. + - Multiple _out of source_ builds (debug, release... etc). + +You can use CMake with the following commands +(assuming you're in the herbstluftwm repository root): + + mkdir build + cd build + cmake .. + make + make install + +The choice of 'build' here is arbitrary, +you can put your build directory(s) wherever you like. + +Note that CMake support is currently experimental. + Sending patches --------------- You can use git to make commits and create patches from them via the command diff -Nru herbstluftwm-0.6.2/INSTALL herbstluftwm-0.7.0/INSTALL --- herbstluftwm-0.6.2/INSTALL 1970-01-01 00:00:00.000000000 +0000 +++ herbstluftwm-0.7.0/INSTALL 2015-06-03 21:27:47.000000000 +0000 @@ -0,0 +1,99 @@ +===== HERBSTLUFTWM ===== + +Copyright 2011-2013 Thorsten Wißmann. All rights reserved. + +This software is licensed under the "Simplified BSD License". +See LICENSE for details. + +==== Requirements ==== +Build dependencies: + - build-environment (gcc/other compiler, make) + - asciidoc (only when building from git, not when building from tarball) + - a posix system with _POSIX_TIMERS and _POSIX_MONOTONIC_CLOCK or a system + with a current mach kernel +Runtime dependencies: + - bash (if you use the default autostart file) + - glib >= 2.14 + - libx11 + +Optional run-time dependencies: + - xsetroot (to set wallpaper color in default autostart) + - xterm (used as the terminal in default autostart) + - dzen2 (used in the default panel.sh, it works best with a new dzen2 which + already supports clicking) + - dmenu (used in some example scripts) + +==== Help/Support/Bugs ==== +A list of known bugs is listed in BUGS. If you found other bugs or want to +request features then contact the mailing list. (The subscription process is +explained in the HACKING file). + +Mailing list: hlwm@lists.herbstluftwm.org + +For instant help join the IRC channel: #herbstluftwm on irc.freenode.net + +==== Steps with installing ==== +If you are using a system with a package manager, then install it via the +package manager of your distribution! If you are not allowed to install +software, then contact your system administrator. + +You only need to install it manually if you do not like package managers or if +you are creating a package for your distribution. + +The compilation and installation is configured by the following make-variables +in config.mk: + +DESTDIR = / # the path to your root-directory +PREFIX = /usr/ # the prefix +SYSCONFDIR = $(DESTDIR)/etc/ # path to etc directory + +Normally you should build it with DESTDIR=/ and install it with +DESTDIR=./path/to/fakeroot if you are building a package. + + make DESTDIR=/ + sudo make DESTDIR=./build/ install + mkdir -p ~/.config/herbstluftwm/ + cp /etc/xdg/herbstluftwm/autostart ~/.config/herbstluftwm/autostart + +==== First steps without installing ==== +1. compile it: + + make + +2. copy herbstclient to a bin-folder or adjust path in autostart file +3. copy default autostart file to the config-dir: + + mkdir -p ~/.config/herbstluftwm + cp share/autostart ~/.config/herbstluftwm/ + +4. add the share/herbstclient-completion to your /etc/bash_completion.d/ folder + or source it in your bashrc +5. run it in a session that has no windowmanager yet + +==== Starting it ==== +Start it within a running X-session with: + + herbstluftwm --locked + +The --locked causes herbstluftwm not to update the screen until you unlock it +with: herbstclient unlock (This is done automatically by the default autostart) + +==== Quirks ==== +Mac OSX: + +Problem: Mod1 is nowhere to be found. +Solution: Set left Command (Apple) key to be Mod1. +edit .Xmodmap +--- snip --- +! Make the Alt/Option key be Alt_L instead of Mode_switch +keycode 63 = Alt_L + +! Make Meta_L be a Mod4 and get rid of Mod2 +clear mod2 +clear mod4 +add mod4 = Meta_L + +! Make Alt_L be a Mod1 +clear mod1 +add mod1 = Alt_L +--- snap --- diff -Nru herbstluftwm-0.6.2/ipc-client/client-utils.c herbstluftwm-0.7.0/ipc-client/client-utils.c --- herbstluftwm-0.6.2/ipc-client/client-utils.c 1970-01-01 00:00:00.000000000 +0000 +++ herbstluftwm-0.7.0/ipc-client/client-utils.c 2015-06-03 21:27:47.000000000 +0000 @@ -0,0 +1,65 @@ + +#include "client-utils.h" +#include +#include +#include +#include +#include + +// inspired by dwm's gettextprop() +GString* window_property_to_g_string(Display* dpy, Window window, Atom atom) { + GString* result = NULL; + char** list = NULL; + int n = 0; + XTextProperty prop; + + if (0 == XGetTextProperty(dpy, window, &prop, atom)) { + return NULL; + } + // convert text property to a gstring + if (prop.encoding == XA_STRING + || prop.encoding == XInternAtom(dpy, "UTF8_STRING", False)) { + result = g_string_new((char*)prop.value); + } else { + if (XmbTextPropertyToTextList(dpy, &prop, &list, &n) >= Success + && n > 0 && *list) + { + result = g_string_new(*list); + XFreeStringList(list); + } + } + XFree(prop.value); + return result; +} + +// duplicates an argument-vector +char** argv_duplicate(int argc, char** argv) { + if (argc <= 0) return NULL; + char** new_argv = malloc(sizeof(char*) * argc); + if (!new_argv) { + die("cannot malloc - there is no memory available\n"); + } + int i; + for (i = 0; i < argc; i++) { + new_argv[i] = g_strdup(argv[i]); + } + return new_argv; +} + +// frees all entries in argument-vector and then the vector itself +void argv_free(int argc, char** argv) { + if (argc <= 0) return; + int i; + for (i = 0; i < argc; i++) { + free(argv[i]); + } + free(argv); +} + +void die(const char *errstr, ...) { + va_list ap; + va_start(ap, errstr); + vfprintf(stderr, errstr, ap); + va_end(ap); + exit(EXIT_FAILURE); +} diff -Nru herbstluftwm-0.6.2/ipc-client/client-utils.h herbstluftwm-0.7.0/ipc-client/client-utils.h --- herbstluftwm-0.6.2/ipc-client/client-utils.h 1970-01-01 00:00:00.000000000 +0000 +++ herbstluftwm-0.7.0/ipc-client/client-utils.h 2015-06-03 21:27:47.000000000 +0000 @@ -0,0 +1,15 @@ + +#ifndef __HERBSTLUFT_CLIENT_UTILS_H_ +#define __HERBSTLUFT_CLIENT_UTILS_H_ + +#include +#include +#include + +GString* window_property_to_g_string(Display* dpy, Window window, Atom atom); +char** argv_duplicate(int argc, char** argv); +void argv_free(int argc, char** argv); +void die(const char *errstr, ...); + + +#endif diff -Nru herbstluftwm-0.6.2/ipc-client/ipc-client.c herbstluftwm-0.7.0/ipc-client/ipc-client.c --- herbstluftwm-0.6.2/ipc-client/ipc-client.c 2013-12-14 17:08:13.000000000 +0000 +++ herbstluftwm-0.7.0/ipc-client/ipc-client.c 2015-06-03 21:27:47.000000000 +0000 @@ -4,8 +4,8 @@ * See LICENSE for details */ #include "../src/ipc-protocol.h" -#include "../src/utils.h" #include "ipc-client.h" +#include "client-utils.h" // standard #include @@ -236,7 +236,7 @@ return false; } *argc = count; - *argv = argv_duplicate(count, list_return); + *argv = argv_duplicate(count, list_return); // has to be freed by caller received_hook = true; // cleanup XFreeStringList(list_return); @@ -244,4 +244,3 @@ } return true; } - diff -Nru herbstluftwm-0.6.2/ipc-client/ipc-client.h herbstluftwm-0.7.0/ipc-client/ipc-client.h --- herbstluftwm-0.6.2/ipc-client/ipc-client.h 2013-12-14 17:08:13.000000000 +0000 +++ herbstluftwm-0.7.0/ipc-client/ipc-client.h 2015-06-03 21:27:47.000000000 +0000 @@ -5,6 +5,10 @@ #include #include +#include + +#ifndef __HERBSTLUFT_IPC_CLIENT_H_ +#define __HERBSTLUFT_IPC_CLIENT_H_ typedef struct HCConnection HCConnection; @@ -22,3 +26,5 @@ bool hc_hook_window_connect(HCConnection* con); bool hc_next_hook(HCConnection* con, int* argc, char** argv[]); +#endif + diff -Nru herbstluftwm-0.6.2/ipc-client/main.c herbstluftwm-0.7.0/ipc-client/main.c --- herbstluftwm-0.6.2/ipc-client/main.c 2014-03-26 23:54:22.000000000 +0000 +++ herbstluftwm-0.7.0/ipc-client/main.c 2015-06-03 21:27:47.000000000 +0000 @@ -13,8 +13,8 @@ #include "ipc-client.h" #include "../src/globals.h" -#include "../src/utils.h" #include "../src/ipc-protocol.h" +#include "client-utils.h" #define HERBSTCLIENT_VERSION_STRING \ "herbstclient " HERBSTLUFT_VERSION " (built on " __DATE__ ")\n" @@ -24,6 +24,8 @@ void destroy_hook_regex(); int g_ensure_newline = 1; // if set, output ends with a newline +bool g_null_char_as_delim = false; // if true, the null character is used as delimiter +bool g_print_last_arg_only = false; // if true, prints only the last argument of a hook int g_wait_for_hook = 0; // if set, do not execute command but wait bool g_quiet = false; regex_t* g_hook_regex = NULL; @@ -80,6 +82,9 @@ "Options:\n" "\t-n, --no-newline: Do not print a newline if output does not end " "with a newline.\n" + "\t-0, --print0: Use the null character as delimiter between the " + "output of hooks.\n" + "\t-l, --last-arg: Print only the last argument of a hook.\n" "\t-i, --idle: Wait for hooks instead of executing commands.\n" "\t-w, --wait: Same as --idle but exit after first --count hooks.\n" "\t-c, --count COUNT: Let --wait exit after COUNT hooks were " @@ -125,11 +130,22 @@ } } if (print_signal) { - // just print as list - for (int i = 0; i < hook_argc; i++) { - printf("%s%s", i ? "\t" : "", hook_argv[i]); + if (g_print_last_arg_only) { + // just drop hooks without content + if (hook_argc >= 1) { + printf("%s", hook_argv[hook_argc-1]); + } + } else { + // just print as list + for (int i = 0; i < hook_argc; i++) { + printf("%s%s", i ? "\t" : "", hook_argv[i]); + } + } + if (g_null_char_as_delim) { + putchar(0); + } else { + printf("\n"); } - printf("\n"); fflush(stdout); } argv_free(hook_argc, hook_argv); @@ -151,6 +167,8 @@ int main(int argc, char* argv[]) { static struct option long_options[] = { {"no-newline", 0, 0, 'n'}, + {"print0", 0, 0, '0'}, + {"last-arg", 0, 0, 'l'}, {"wait", 0, 0, 'w'}, {"count", 1, 0, 'c'}, {"idle", 0, 0, 'i'}, @@ -162,7 +180,7 @@ // parse options while (1) { int option_index = 0; - int c = getopt_long(argc, argv, "+nwc:iqhv", long_options, &option_index); + int c = getopt_long(argc, argv, "+n0lwc:iqhv", long_options, &option_index); if (c == -1) break; switch (c) { case 'i': @@ -179,6 +197,12 @@ case 'n': g_ensure_newline = 0; break; + case '0': + g_null_char_as_delim = true; + break; + case 'l': + g_print_last_arg_only = true; + break; case 'q': g_quiet = true; break; diff -Nru herbstluftwm-0.6.2/Makefile herbstluftwm-0.7.0/Makefile --- herbstluftwm-0.6.2/Makefile 2014-03-26 23:54:22.000000000 +0000 +++ herbstluftwm-0.7.0/Makefile 2015-06-03 21:27:47.000000000 +0000 @@ -2,11 +2,11 @@ include config.mk include colors.mk -HLWMSRC = $(wildcard src/*.c) -HLWMOBJ = $(HLWMSRC:.c=.o) +HLWMSRC = $(wildcard src/*.cpp) +HLWMOBJ = $(HLWMSRC:.cpp=.o) HLWMTARGET = herbstluftwm -HCSRC = $(wildcard ipc-client/*.c) src/utils.c +HCSRC = $(wildcard ipc-client/*.c) HCOBJ = $(HCSRC:.c=.o) HCTARGET = herbstclient @@ -23,13 +23,14 @@ all: $(TARGETS) doc all-nodoc: $(TARGETS) -$(HLWMTARGET): $(HLWMOBJ) -$(HCTARGET): $(HCOBJ) -$(TARGETS): +$(HCTARGET): $(HCOBJ) $(call colorecho,LD,$@) $(VERBOSE) $(LD) -o $@ $(CFLAGS) $(LDFLAGS) $^ $(LIBS) +$(HLWMTARGET): $(HLWMOBJ) + $(LDXX) -o $@ $(CXXFLAGS) $(LDXXFLAGS) $^ $(LIBS) + -include $(DEPS) %.o: %.c version.mk @@ -37,10 +38,17 @@ $(VERBOSE) $(CC) -c $(CPPFLAGS) $(CFLAGS) -o $@ $< $(VERBOSE) $(CC) -c $(CPPFLAGS) -o $*.d -MT $@ -MM $< +%.o: %.cpp version.mk + $(call colorecho,CXX,$<) + $(VERBOSE) $(CXX) -c $(CPPFLAGS) $(CXXFLAGS) -o $@ $< + $(VERBOSE) $(CXX) -c $(CPPFLAGS) $(CXXFLAGS) -o $*.d -MT $@ -MM $< + + info: @echo Some Info: @echo Preprocessing with: $(CC) -E $(CPPFLAGS) - @echo Compiling with: $(CC) -c $(CPPFLAGS) $(CFLAGS) -o OUT INPUT + @echo Compiling C with: $(CC) -c $(CPPFLAGS) $(CFLAGS) -o OUT INPUT + @echo Compiling C++ with: $(CXX) -c $(CPPFLAGS) $(CXXFLAGS) -o OUT INPUT @echo Linking with: $(LD) -o OUT $(LDFLAGS) INPUT clean: cleandoc cleandeps @@ -81,6 +89,7 @@ tar -xvf $(TARFILE) -C $(TMPTARDIR) tar -czf $(TARFILE) $(TMPTARDIR) rm -rf $(TMPTARDIR) + gpg --detach-sign $(TARFILE) doc/%.1 doc/%.7: doc/%.txt version.mk $(call colorecho,DOC,$@) @@ -118,13 +127,14 @@ $(INSTALL) -m 644 LICENSE '$(DESTDIR)$(LICENSEDIR)/' $(INSTALL) -m 644 BUGS '$(DESTDIR)$(DOCDIR)/' $(INSTALL) -m 644 NEWS '$(DESTDIR)$(DOCDIR)/' - $(INSTALL) -m 644 README '$(DESTDIR)$(DOCDIR)/' + $(INSTALL) -m 644 INSTALL '$(DESTDIR)$(DOCDIR)/' $(INSTALL) -m 755 share/autostart '$(DESTDIR)$(CONFIGDIR)/' $(INSTALL) -m 755 share/panel.sh '$(DESTDIR)$(CONFIGDIR)/' $(INSTALL) -m 755 share/restartpanels.sh '$(DESTDIR)$(CONFIGDIR)/' $(INSTALL) -m 644 share/herbstclient-completion '$(DESTDIR)$(BASHCOMPLETIONDIR)/' $(INSTALL) -m 644 share/_herbstclient '$(DESTDIR)$(ZSHCOMPLETIONDIR)/' $(INSTALL) -m 644 share/herbstluftwm.desktop '$(DESTDIR)$(XSESSIONSDIR)/' + $(INSTALL) -m 755 share/dmenu_run_hlwm '$(DESTDIR)$(BINDIR)/' $(INSTALL) -m 644 scripts/README '$(DESTDIR)$(EXAMPLESDIR)/' $(INSTALL) -m 755 scripts/*.sh '$(DESTDIR)$(EXAMPLESDIR)/' @@ -133,6 +143,7 @@ uninstall: @echo "==> deleting files..." -$(foreach TARGET,$(TARGETS),$(RM) '$(DESTDIR)$(BINDIR)/$(TARGET)';) + -$(RM) '$(DESTDIR)$(BINDIR)/dmenu_run_hlwm' -$(RM) '$(DESTDIR)$(LICENSEDIR)/LICENSE' -$(RM) '$(DESTDIR)$(MAN1DIR)/herbstclient.1' -$(RM) '$(DESTDIR)$(MAN1DIR)/herbstluftwm.1' @@ -142,7 +153,7 @@ -$(RM) '$(DESTDIR)$(DOCDIR)/herbstluftwm-tutorial.html' -$(RM) '$(DESTDIR)$(DOCDIR)/BUGS' -$(RM) '$(DESTDIR)$(DOCDIR)/NEWS' - -$(RM) '$(DESTDIR)$(DOCDIR)/README' + -$(RM) '$(DESTDIR)$(DOCDIR)/INSTALL' -$(RM) '$(DESTDIR)$(CONFIGDIR)/autostart' -$(RM) '$(DESTDIR)$(CONFIGDIR)/panel.sh' -$(RM) '$(DESTDIR)$(CONFIGDIR)/restartpanels.sh' diff -Nru herbstluftwm-0.6.2/NEWS herbstluftwm-0.7.0/NEWS --- herbstluftwm-0.6.2/NEWS 2014-03-27 01:54:17.000000000 +0000 +++ herbstluftwm-0.7.0/NEWS 2016-02-04 17:00:29.000000000 +0000 @@ -1,6 +1,33 @@ herbstluftwm NEWS -- History of user-visible changes ---------------------------------------------------- +Release 0.7.0 on 2016-02-04 +--------------------------- + + * Handle EWMH request _NET_WM_MOVERESIZE more conform + * Make tag objects accessible by their index + * Automatically unmanage desktop windows (e.g. xfdesktop), and force them + to stay below all other windows. + * new command: close_and_remove + * new herbstclient flags: --last-arg --print0 + * new example scripts: + + - maximize.sh + - toggledualhead.sh + - windowmenu.sh + - wselect.sh (new subcommand "select_here") + +Release 0.6.2 on 2014-03-27 +--------------------------- +Two bug fixes: + + * A crash has been fixed. It could be triggered by changing a + non-callback settings attribute e.g. settings.raise_on_focus + * The dialog re-mapping-problem has been fixed. So now, applications can + show the same dialogs again after the dialog has been closed (e.g. the + connection window of qjackctl). + + Release 0.6.1 on 2014-03-25 --------------------------- diff -Nru herbstluftwm-0.6.2/README herbstluftwm-0.7.0/README --- herbstluftwm-0.6.2/README 2014-03-26 23:54:22.000000000 +0000 +++ herbstluftwm-0.7.0/README 1970-01-01 00:00:00.000000000 +0000 @@ -1,99 +0,0 @@ -===== HERBSTLUFTWM ===== - -Copyright 2011-2013 Thorsten Wißmann. All rights reserved. - -This software is licensed under the "Simplified BSD License". -See LICENSE for details. - -==== Requirements ==== -Build dependencies: - - build-environment (gcc/other compiler, make) - - asciidoc (only when building from git, not when building from tarball) - - a posix system with _POSIX_TIMERS and _POSIX_MONOTONIC_CLOCK or a system - with a current mach kernel -Runtime dependencies: - - bash (if you use the default autostart file) - - glib >= 2.14 - - libx11 - -Optional run-time dependencies: - - xsetroot (to set wallpaper color in default autostart) - - xterm (used as the terminal in default autostart) - - dzen2 (used in the default panel.sh, it works best with a new dzen2 which - already supports clicking) - - dmenu (used in some example scripts) - -==== Help/Support/Bugs ==== -A list of known bugs is listed in BUGS. If you found other bugs or want to -request features then contact the mailing list. (The subscription process is -explained in the HACKING file). - -Mailing list: hlwm@lists.herbstluftwm.org - -For instant help join the IRC channel: #herbstluftwm on irc.freenode.net - -==== Steps with installing ==== -If you are using a system with a package manager, then install it via the -package manager of your distribution! If you are not allowed to install -software, then contact your system administrator. - -You only need to install it manually if you do not like package managers or if -you are creating a package for your distribution. - -The compilation and installation is configured by the following make-variables -in config.mk: - -DESTDIR = / # the path to your root-directory -PREFIX = /usr/ # the prefix -SYSCONFDIR = $(DESTDIR)/etc/ # path to etc directory - -Normally you should build it with DESTDIR=/ and install it with -DESTDIR=./path/to/fakeroot if you are building a package. - - make DESTDIR=/ - sudo make DESTDIR=./build/ install - mkdir -p ~/.config/herbstluftwm/ - cp /etc/xdg/herbstluftwm/autostart ~/.config/herbstluftwm/autostart - -==== First steps without installing ==== -1. compile it: - - make - -2. copy herbstclient to a bin-folder or adjust path in autostart file -3. copy default autostart file to the config-dir: - - mkdir -p ~/.config/herbstluftwm - cp share/autostart ~/.config/herbstluftwm/ - -4. add the share/herbstclient-completion to your /etc/bash_completion.d/ folder - or source it in your bashrc -5. run it in a session that has no windowmanager yet - -==== Starting it ==== -Start it within a running X-session with: - - herbstluftwm --locked - -The --locked causes herbstluftwm not to update the screen until you unlock it -with: herbstclient unlock (This is done automatically by the default autostart) - -==== Quirks ==== -Mac OSX: - -Problem: Mod1 is nowhere to be found. -Solution: Set left Command (Apple) key to be Mod1. -edit .Xmodmap ---- snip --- -! Make the Alt/Option key be Alt_L instead of Mode_switch -keycode 63 = Alt_L - -! Make Meta_L be a Mod4 and get rid of Mod2 -clear mod2 -clear mod4 -add mod4 = Meta_L - -! Make Alt_L be a Mod1 -clear mod1 -add mod1 = Alt_L ---- snap --- diff -Nru herbstluftwm-0.6.2/README.md herbstluftwm-0.7.0/README.md --- herbstluftwm-0.6.2/README.md 1970-01-01 00:00:00.000000000 +0000 +++ herbstluftwm-0.7.0/README.md 2015-06-03 21:27:47.000000000 +0000 @@ -0,0 +1,16 @@ +# herbstluftwm + +herbstluftwm is a manual tiling window manager for X. It licensed under the +"Simplified BSD License" (see `LICENSE`). + +- the layout is based on splitting frames into subframes which can be split again or can be filled with windows (similar to i3/ musca) +- tags (or workspaces or virtual desktops or …) can be added/removed at runtime. Each tag contains an own layout +- exactly one tag is viewed on each monitor. The tags are monitor independent (similar to xmonad) +- it is configured at runtime via ipc calls from herbstclient. So the configuration file is just a script which is run on startup. (similar to wmii/ musca) + +For more, see the herbstluftwm homepage http://herbstluftwm.org -- in +particular the [herbstluftwm tutorial](http://herbstluftwm.org/tutorial.html) +for the first steps (also available as `man herbstluftwm-tutorial` after +installing herbstluftwm on your system). + +You are welcome to join the IRC channel `#herbstluftwm` on `irc.freenode.net`. diff -Nru herbstluftwm-0.6.2/release.sh herbstluftwm-0.7.0/release.sh --- herbstluftwm-0.6.2/release.sh 2014-03-26 23:54:22.000000000 +0000 +++ herbstluftwm-0.7.0/release.sh 2015-06-03 21:27:47.000000000 +0000 @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash version="$1" @@ -43,8 +43,8 @@ tarball="herbstluftwm-$version.tar.gz" md5sum=$(md5sum "$tarball" | head -c 13 ) echo ":: Patching www/download.txt" -line=$(printf "| %-7s | $date | $md5sum...%15s| link:tarballs/%s[tar.gz]" \ - $version ' ' "$tarball" ) +line=$(printf "| %-7s | $date | $md5sum...%15s| link:tarballs/%s[tar.gz] |link:tarballs/%s.sig[sig]" \ + $version ' ' "$tarball" "$tarball") linerexp="// do not remove this: next version line will be added here" sed -i "s#^$linerexp\$#$line\n$linerexp#" www/download.txt echo ":: Commiting changes" diff -Nru herbstluftwm-0.6.2/scripts/dmenu.sh herbstluftwm-0.7.0/scripts/dmenu.sh --- herbstluftwm-0.6.2/scripts/dmenu.sh 2013-12-14 17:08:13.000000000 +0000 +++ herbstluftwm-0.7.0/scripts/dmenu.sh 2015-10-12 12:01:11.000000000 +0000 @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash dm() { "${dmenu_command[@]:-dmenu}" "$@" ;} hc() { "${herbstclient_command[@]:-herbstclient}" "$@" ;} diff -Nru herbstluftwm-0.6.2/scripts/dumpbeautify.sh herbstluftwm-0.7.0/scripts/dumpbeautify.sh --- herbstluftwm-0.6.2/scripts/dumpbeautify.sh 2013-12-14 17:08:13.000000000 +0000 +++ herbstluftwm-0.7.0/scripts/dumpbeautify.sh 2015-10-12 12:01:11.000000000 +0000 @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash # aligns the output of dump command as a nice tree # usage: diff -Nru herbstluftwm-0.6.2/scripts/exec_on_tag.sh herbstluftwm-0.7.0/scripts/exec_on_tag.sh --- herbstluftwm-0.6.2/scripts/exec_on_tag.sh 2013-12-14 17:08:13.000000000 +0000 +++ herbstluftwm-0.7.0/scripts/exec_on_tag.sh 2015-10-12 12:01:11.000000000 +0000 @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash hc() { "${herbstclient_command[@]:-herbstclient}" "$@" ;} diff -Nru herbstluftwm-0.6.2/scripts/execwith.sh herbstluftwm-0.7.0/scripts/execwith.sh --- herbstluftwm-0.6.2/scripts/execwith.sh 2013-12-14 17:08:13.000000000 +0000 +++ herbstluftwm-0.7.0/scripts/execwith.sh 2015-10-12 12:01:11.000000000 +0000 @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash # exec a script $2... with settings from rc-file $1 # useful for various dmenu scripts, e.g.: diff -Nru herbstluftwm-0.6.2/scripts/floatmon.sh herbstluftwm-0.7.0/scripts/floatmon.sh --- herbstluftwm-0.6.2/scripts/floatmon.sh 2013-12-14 17:08:13.000000000 +0000 +++ herbstluftwm-0.7.0/scripts/floatmon.sh 2015-10-12 12:01:11.000000000 +0000 @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash monitor=floatmon tag=fl diff -Nru herbstluftwm-0.6.2/scripts/herbstcommander.sh herbstluftwm-0.7.0/scripts/herbstcommander.sh --- herbstluftwm-0.6.2/scripts/herbstcommander.sh 2013-12-14 17:08:13.000000000 +0000 +++ herbstluftwm-0.7.0/scripts/herbstcommander.sh 2015-10-12 12:01:11.000000000 +0000 @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash # herbstcommander.sh - launch herbstluftwm-commands via dmenu # Written by Florian Bruhin diff -Nru herbstluftwm-0.6.2/scripts/keychain.sh herbstluftwm-0.7.0/scripts/keychain.sh --- herbstluftwm-0.6.2/scripts/keychain.sh 2013-12-14 17:08:13.000000000 +0000 +++ herbstluftwm-0.7.0/scripts/keychain.sh 2015-10-12 12:01:11.000000000 +0000 @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash # Execute this (e.g. from your autostart) to obtain basic key chaining like it # is known from other applications like screen. diff -Nru herbstluftwm-0.6.2/scripts/lasttag.sh herbstluftwm-0.7.0/scripts/lasttag.sh --- herbstluftwm-0.6.2/scripts/lasttag.sh 2013-12-14 17:08:13.000000000 +0000 +++ herbstluftwm-0.7.0/scripts/lasttag.sh 2015-10-12 12:01:11.000000000 +0000 @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash # usage: start this script in anywhere your autostart (but *after* the # emit_hook reload line) diff -Nru herbstluftwm-0.6.2/scripts/layout.sh herbstluftwm-0.7.0/scripts/layout.sh --- herbstluftwm-0.6.2/scripts/layout.sh 2013-12-14 17:08:13.000000000 +0000 +++ herbstluftwm-0.7.0/scripts/layout.sh 2015-10-12 12:01:11.000000000 +0000 @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash # print layout of all tags, and colorizes all window ids # it's useful to get a overview over the list of all windows diff -Nru herbstluftwm-0.6.2/scripts/loadstate.sh herbstluftwm-0.7.0/scripts/loadstate.sh --- herbstluftwm-0.6.2/scripts/loadstate.sh 2013-12-14 17:08:13.000000000 +0000 +++ herbstluftwm-0.7.0/scripts/loadstate.sh 2015-10-12 12:01:11.000000000 +0000 @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash hc() { "${herbstclient_command[@]:-herbstclient}" "$@" ;} diff -Nru herbstluftwm-0.6.2/scripts/maximize.sh herbstluftwm-0.7.0/scripts/maximize.sh --- herbstluftwm-0.6.2/scripts/maximize.sh 1970-01-01 00:00:00.000000000 +0000 +++ herbstluftwm-0.7.0/scripts/maximize.sh 2015-10-14 13:27:40.000000000 +0000 @@ -0,0 +1,43 @@ +#!/usr/bin/env bash +set -e +# A simple script for window maximization and window switching. +# Running this the first time script will: +# +# 1. remember the current layout +# 2. squeeze all windows into one frame using the layout defined in the first +# argument (defaulting to max layout). +# 3. and during that, keeping the window focus +# +# Running this script again will: +# +# 1. restore the original layout +# 2. (again keeping the then current window focus) +# +# If you call this script with "grid", then you obtain a window switcher, +# similar to that of Mac OS X. +mode=${1:-max} # just some valid layout algorithm name + +# FIXME: for some unknown reason, remove_attr always fails +# fix that in the hlwm core and remove the "try" afterwards +layout=$(herbstclient dump) +cmd=( +# remmember which client is focused +substitute FOCUS clients.focus.winid chain +. lock +. or : and # if there is more than one frame, then don't restore, but maximize again! + , compare tags.focus.frame_count = 1 + # if we have such a stored layout, then restore it, else maximize + , silent substitute STR tags.focus.my_unmaximized_layout load STR + # remove the stored layout + , try remove_attr tags.focus.my_unmaximized_layout + : chain , new_attr string tags.focus.my_unmaximized_layout + # save the current layout in the attribute + , set_attr tags.focus.my_unmaximized_layout "$layout" + # force all windows into a single frame in max layout + , load "(clients $mode:0 )" +# both load commands accidentally change the window focus, so restore the +# window focus from before the "load" command +. jumpto FOCUS +. unlock +) +herbstclient "${cmd[@]}" diff -Nru herbstluftwm-0.6.2/scripts/q3terminal.sh herbstluftwm-0.7.0/scripts/q3terminal.sh --- herbstluftwm-0.6.2/scripts/q3terminal.sh 2013-12-14 17:08:13.000000000 +0000 +++ herbstluftwm-0.7.0/scripts/q3terminal.sh 2015-10-12 12:01:11.000000000 +0000 @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash # a q3-like (or yakuake-like) terminal for arbitrary applications. # diff -Nru herbstluftwm-0.6.2/scripts/savestate.sh herbstluftwm-0.7.0/scripts/savestate.sh --- herbstluftwm-0.6.2/scripts/savestate.sh 2013-12-14 17:08:13.000000000 +0000 +++ herbstluftwm-0.7.0/scripts/savestate.sh 2015-10-12 12:01:11.000000000 +0000 @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash hc() { "${herbstclient_command[@]:-herbstclient}" "$@" ;} diff -Nru herbstluftwm-0.6.2/scripts/scratchpad.sh herbstluftwm-0.7.0/scripts/scratchpad.sh --- herbstluftwm-0.6.2/scripts/scratchpad.sh 2014-03-26 23:54:22.000000000 +0000 +++ herbstluftwm-0.7.0/scripts/scratchpad.sh 2015-10-12 12:01:11.000000000 +0000 @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash # a i3-like scratchpad for arbitrary applications. # diff -Nru herbstluftwm-0.6.2/scripts/toggledualhead.sh herbstluftwm-0.7.0/scripts/toggledualhead.sh --- herbstluftwm-0.6.2/scripts/toggledualhead.sh 1970-01-01 00:00:00.000000000 +0000 +++ herbstluftwm-0.7.0/scripts/toggledualhead.sh 2015-10-14 13:27:40.000000000 +0000 @@ -0,0 +1,71 @@ +#!/usr/bin/env bash + +# Splits the currently focused monitor into two monitors displayed side by side +# Running this on a splitted monitor joins the two monitor halfs again. + +hc() { + herbstclient "$@" +} + +array2rect() { + printf "%dx%d%+d%+d" $3 $4 $1 $2 +} + +idx=$(hc get_attr monitors.focus.index) + +if orig=$(hc get_attr monitors.${idx}.my_orig_rect 2> /dev/null ) ; then + # give original size and remove all other monitors without the leader flag + rect=$(array2rect $orig) + mon_cnt=$(hc get_attr monitors.count) + cmd=( + chain + X move_monitor $idx "$rect" + X remove_attr monitors.${idx}.my_orig_rect + X or + ) + for i in $(seq 0 $((mon_cnt - 1))) ; do + # find the other monitor half and remove it + [ $i != $idx ] && + cmd+=( v and + ∧ compare monitors.${i}.my_orig_rect = "${orig[*]}" + ∧ remove_monitor $i + ) + done + hc "${cmd[@]}" > /dev/null 2> /dev/null +else + # split original rectangle of the monitor into a left and a right half + orig=( $(hc monitor_rect $i) ) || exit 1 + left=( ${orig[0]} ${orig[1]} $((${orig[2]} / 2)) ${orig[3]} ) + x=$(( ${left[0]} + ${left[2]} )) + rightwidth=$((${orig[2]} - ${left[2]})) + right=( $x ${orig[1]} $rightwidth ${orig[3]} ) + leftrect=$(array2rect ${left[@]}) + rightrect=$(array2rect ${right[@]}) + hc chain \ + , lock \ + , new_attr string monitors.${idx}.my_orig_rect \ + , set_attr monitors.${idx}.my_orig_rect "${orig[*]}" \ + , move_monitor ${idx} "$leftrect" \ + , sprintf ATTR "monitors.%s.my_orig_rect" monitors.count \ + chain \ + . add_monitor "$rightrect" \ + . new_attr string ATTR \ + . set_attr ATTR "${orig[*]}" \ + , unlock +fi + +# restart the panels +herbstclient emit_hook quit_panel + +panelcmd=${panelcmd:-~/.config/herbstluftwm/panel.sh} +if ! [ "$panelcmd" ] ; then + # fall back to global panel if there is no user-specific panel + panelcmd=/etc/xdg/herbstluftwm/panel.sh +fi + +for monitor in $(herbstclient list_monitors | cut -d: -f1) ; do + # start it on each monitor + "$panelcmd" $monitor & +done + + diff -Nru herbstluftwm-0.6.2/scripts/windowmenu.sh herbstluftwm-0.7.0/scripts/windowmenu.sh --- herbstluftwm-0.6.2/scripts/windowmenu.sh 1970-01-01 00:00:00.000000000 +0000 +++ herbstluftwm-0.7.0/scripts/windowmenu.sh 2015-10-14 13:27:40.000000000 +0000 @@ -0,0 +1,47 @@ +#!/usr/bin/env bash +set -e +# +# dependencies: +# +# - rofi + +# offer a window menu offering possible actions on that window like +# moving to a different tag or toggling its fullscreen state + +action_list() { + local a="$1" + "$a" "Close" herbstclient close + "$a" "Toggle fullscreen" herbstclient fullscreen toggle + "$a" "Toggle pseudotile" herbstclient pseudotile toggle + for tag in $(herbstclient complete 1 move) ; do + "$a" "Move to tag $tag" herbstclient move "$tag" + done +} + +print_menu() { + echo "$1" +} + +title=$(herbstclient attr clients.focus.title) +title=${title//&/&} +rofiflags=( + -p "herbstclient:" + -mesg "$title" + -columns 3 + -location 2 + -width 100 + -no-custom +) +result=$(action_list print_menu | rofi -i -dmenu -m -2 "${rofiflags[@]}") +[ $? -ne 0 ] && exit 0 + +exec_entry() { + if [ "$1" = "$result" ] ; then + shift + "$@" + exit 0 + fi +} + +action_list exec_entry + diff -Nru herbstluftwm-0.6.2/scripts/wselect.sh herbstluftwm-0.7.0/scripts/wselect.sh --- herbstluftwm-0.6.2/scripts/wselect.sh 2013-12-14 17:08:13.000000000 +0000 +++ herbstluftwm-0.7.0/scripts/wselect.sh 2015-10-14 13:27:40.000000000 +0000 @@ -1,11 +1,11 @@ -#!/bin/bash +#!/usr/bin/env bash # a window selection utility # dependencies: wmctrl, awk, # dmenu with multiline support (command line flag -l) -hc() { "${herbstclient_command[@]:-herbstclient}" "$@" ;} -dm() { "${dmenu_command[@]:-dmenu}" "$@" ;} +hc() { ${herbstclient_command:-herbstclient} "$@" ;} +dm() { ${dmenu_command:-dmenu} "$@" ;} dmenu_lines=${dmenu_lines:-10} case "$1" in @@ -16,7 +16,24 @@ action() { hc bring "$@" ; } ;; - select|*) + select_here|*) + # first focus the tag of the selected window and then select the window + # this enforces that the setting swap_monitors_to_get_tag is respected: + # if set, the tag is brought to the focused monitor and the window gets focused. + # if unset, the focused jumps to the desired window and its position on + # the screen(s) remains the same. + name=Select: + action() { + local winid=$(sed 's,0x[0]*,0x,' <<< "$*") + local tag=$(hc attr clients."$winid".tag) + hc lock + hc use "$tag" + hc jumpto "$*" + hc unlock + } + ;; + + select) # switch to the selected window and focus it action() { hc jumpto "$@" ; } name=Select: @@ -24,5 +41,5 @@ esac id=$(wmctrl -l |cat -n| sed 's/\t/) /g'| sed 's/^[ ]*//' \ - | dm -l $dmenu_lines -p "$name") \ + | dm -i -l $dmenu_lines -p "$name") \ && action $(awk '{ print $2 ; }' <<< "$id") diff -Nru herbstluftwm-0.6.2/share/autostart herbstluftwm-0.7.0/share/autostart --- herbstluftwm-0.6.2/share/autostart 2014-03-26 23:54:22.000000000 +0000 +++ herbstluftwm-0.7.0/share/autostart 2015-10-14 13:27:40.000000000 +0000 @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash # this is a simple config for herbstluftwm @@ -21,7 +21,7 @@ hc keybind $Mod-Shift-q quit hc keybind $Mod-Shift-r reload hc keybind $Mod-Shift-c close -hc keybind $Mod-Return spawn xterm +hc keybind $Mod-Return spawn ${TERMINAL:-xterm} # use your $TERMINAL with xterm as fallback # basic movement # focusing clients @@ -82,10 +82,16 @@ # layouting hc keybind $Mod-r remove -hc keybind $Mod-space cycle_layout 1 hc keybind $Mod-s floating toggle hc keybind $Mod-f fullscreen toggle hc keybind $Mod-p pseudotile toggle +# The following cycles through the available layouts within a frame, but skips +# layouts, if the layout change wouldn't affect the actual window positions. +# I.e. if there are two windows within a frame, the grid layout is skipped. +hc keybind $Mod-space \ + or , and . compare tags.focus.curframe_wcount = 2 \ + . cycle_layout +1 vertical horizontal max vertical grid \ + , cycle_layout +1 # mouse hc mouseunbind --all diff -Nru herbstluftwm-0.6.2/share/dmenu_run_hlwm herbstluftwm-0.7.0/share/dmenu_run_hlwm --- herbstluftwm-0.6.2/share/dmenu_run_hlwm 1970-01-01 00:00:00.000000000 +0000 +++ herbstluftwm-0.7.0/share/dmenu_run_hlwm 2015-06-03 21:27:47.000000000 +0000 @@ -0,0 +1,38 @@ +#!/usr/bin/env bash + +if ! command -v dmenu > /dev/null 2>/dev/null ; then + echo "Error: Requirement dmenu not found in your PATH." >&2 + exit 1 +fi + +# Get the currently active tag +tag=$(herbstclient attr tags.focus.name) + +# Prints the path of of the binary in path selected by dmenu +dmenuPrintPath() { + cachedir=${XDG_CACHE_HOME:-"$HOME/.cache"} + if [ -d "$cachedir" ]; then + cache=$cachedir/dmenu_run + else + cache=$HOME/.dmenu_cache # if no xdg dir, fall back to dotfile in ~ + fi + IFS=: + if stest -dqr -n "$cache" $PATH; then + stest -flx $PATH | sort -u | tee "$cache" | dmenu "$@" + else + dmenu "$@" < "$cache" + fi +} + +selectedPath=$(dmenuPrintPath) + +# Ensure that the tag exists +herbstclient add "$tag" + +# Move next window from this process to this tag. Prepend the rule so +# that it may be overwritten by existing custom rules e.g. in the +# autostart. Also set a maximum age for this rule of 120 seconds and +# mark it as one-time-only rule. +herbstclient rule prepend maxage="120" pid="$$" tag="$tag" once + +exec $selectedPath diff -Nru herbstluftwm-0.6.2/share/_herbstclient herbstluftwm-0.7.0/share/_herbstclient --- herbstluftwm-0.6.2/share/_herbstclient 2013-12-14 17:08:13.000000000 +0000 +++ herbstluftwm-0.7.0/share/_herbstclient 2015-06-03 21:27:47.000000000 +0000 @@ -8,7 +8,7 @@ _herbstclient() { local IFS=$'\n' # compadd is documented in zshcompwid(1) - compadd -QS '' "$@" $(herbstclient -q complete_shell "$((CURRENT-2))" "${(@)words[@]:1}") + compadd -QS '' -- "$@" $(herbstclient -q complete_shell "$((CURRENT-2))" "${(@)words[@]:1}") } # compdef is documented in zshcompsys(1) diff -Nru herbstluftwm-0.6.2/share/panel.sh herbstluftwm-0.7.0/share/panel.sh --- herbstluftwm-0.6.2/share/panel.sh 2014-03-26 23:54:22.000000000 +0000 +++ herbstluftwm-0.7.0/share/panel.sh 2015-06-03 21:27:47.000000000 +0000 @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash hc() { "${herbstclient_command[@]:-herbstclient}" "$@" ;} monitor=${1:-0} diff -Nru herbstluftwm-0.6.2/share/restartpanels.sh herbstluftwm-0.7.0/share/restartpanels.sh --- herbstluftwm-0.6.2/share/restartpanels.sh 2013-12-14 17:08:13.000000000 +0000 +++ herbstluftwm-0.7.0/share/restartpanels.sh 2015-06-03 21:27:47.000000000 +0000 @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash installdir=/ diff -Nru herbstluftwm-0.6.2/src/clientlist.c herbstluftwm-0.7.0/src/clientlist.c --- herbstluftwm-0.6.2/src/clientlist.c 2014-03-27 01:38:55.000000000 +0000 +++ herbstluftwm-0.7.0/src/clientlist.c 1970-01-01 00:00:00.000000000 +0000 @@ -1,1049 +0,0 @@ -/** Copyright 2011-2013 Thorsten Wißmann. All rights reserved. - * - * This software is licensed under the "Simplified BSD License". - * See LICENSE for details */ - -#include "clientlist.h" -#include "settings.h" -#include "globals.h" -#include "layout.h" -#include "stack.h" -#include "utils.h" -#include "hook.h" -#include "mouse.h" -#include "ewmh.h" -#include "rules.h" -#include "ipc-protocol.h" -#include "object.h" -#include "decoration.h" -#include "key.h" -// system -#include "glib-backports.h" -#include -#include -#include -#include -#include -#include -#include -// gui -#include -#include -#include -#include - -int g_monitor_float_treshold = 24; - -int* g_raise_on_focus; -int* g_snap_gap; - -static GHashTable* g_clients; // container of all clients -static HSObject* g_client_object; - -// atoms from dwm.c -// default atoms -enum { WMProtocols, WMDelete, WMState, WMTakeFocus, WMLast }; -static Atom g_wmatom[WMLast]; - -static HSClient* lastfocus = NULL; -static void client_set_urgent_force(HSClient* client, bool state); -static HSDecorationScheme client_scheme_from_triple(HSClient* client, int tripidx); -static int client_get_scheme_triple_idx(HSClient* client); - -static bool g_startup = true; // whether hlwm is starting up and is not in the - // main event loop yet -static HSClient* create_client() { - HSClient* hc = g_new0(HSClient, 1); - hsobject_init(&hc->object); - hc->window_str = NULL; - hc->float_size.width = 100; - hc->float_size.height = 100; - hc->title = g_string_new(""); - hc->urgent = false; - hc->fullscreen = false; - hc->ewmhfullscreen = false; - hc->pseudotile = false; - hc->ewmhrequests = true; - hc->ewmhnotify = true; - hc->sizehints_floating = true; - hc->sizehints_tiling = false; - hc->visible = false; - return hc; -} - -static void fetch_colors() { - g_window_gap = &(settings_find("window_gap")->value.i); - g_snap_gap = &(settings_find("snap_gap")->value.i); - g_raise_on_focus = &(settings_find("raise_on_focus")->value.i); -} - -void clientlist_init() { - // init regex simple.. - fetch_colors(); - g_wmatom[WMProtocols] = XInternAtom(g_display, "WM_PROTOCOLS", False); - g_wmatom[WMDelete] = XInternAtom(g_display, "WM_DELETE_WINDOW", False); - g_wmatom[WMState] = XInternAtom(g_display, "WM_STATE", False); - g_wmatom[WMTakeFocus] = XInternAtom(g_display, "WM_TAKE_FOCUS", False); - // init actual client list - g_client_object = hsobject_create_and_link(hsobject_root(), "clients"); - g_clients = g_hash_table_new_full(g_int_hash, g_int_equal, - NULL, (GDestroyNotify)client_destroy); -} - -void clientlist_end_startup() { - g_startup = false; -} - -bool clientlist_ignore_unmapnotify(Window win) { - HSClient* c = get_client_from_window(win); - if (c && c->ignore_unmaps > 0) { - c->ignore_unmaps--; - return true; - } else { - return false; - } -} - -void reset_client_colors() { - fetch_colors(); - all_monitors_apply_layout(); -} - -static void client_move_to_floatpos(void* key, void* client_void, void* data) { - (void)key; - (void)data; - HSClient* client = client_void; - if (client) { - int x = client->float_size.x; - int y = client->float_size.y; - unsigned int w = client->float_size.width; - unsigned int h = client->float_size.height; - XMoveResizeWindow(g_display, client->window, x, y, w, h); - XReparentWindow(g_display, client->window, g_root, x, y); - ewmh_update_frame_extents(client->window, 0,0,0,0); - } -} - -static void client_show_window(void* key, void* client_void, void* data) { - (void)key; - (void)data; - HSClient* client = client_void; - window_set_visible(client->window, true); -} - -void clientlist_destroy() { - // move all clients to their original floating position - g_hash_table_foreach(g_clients, client_move_to_floatpos, NULL); - g_hash_table_foreach(g_clients, client_show_window, NULL); - - g_hash_table_destroy(g_clients); - hsobject_unlink_and_destroy(hsobject_root(), g_client_object); -} - - -void clientlist_foreach(GHFunc func, gpointer data) { - g_hash_table_foreach(g_clients, func, data); -} - -HSClient* get_client_from_window(Window window) { - return (HSClient*) g_hash_table_lookup(g_clients, &window); -} - -#define CLIENT_UPDATE_ATTR(FUNC,MEMBER) do { \ - HSClient* client = container_of(attr->value.b, HSClient, MEMBER); \ - FUNC(client, client->MEMBER); \ - return NULL; \ - } \ - while (0); - -static void client_attr_tag(void* data, GString* output) { - HSClient* client = (HSClient*) data; - g_string_append(output, client->tag->display_name->str); -} - -static void client_attr_class(void* data, GString* output) { - HSClient* client = (HSClient*) data; - GString* ret = window_class_to_g_string(g_display, client->window); - g_string_append(output, ret->str); - g_string_free(ret, true); -} - -static void client_attr_instance(void* data, GString* output) { - HSClient* client = (HSClient*) data; - GString* ret = window_instance_to_g_string(g_display, client->window); - g_string_append(output, ret->str); - g_string_free(ret, true); -} - -static GString* client_attr_fullscreen(HSAttribute* attr) { - CLIENT_UPDATE_ATTR(client_set_fullscreen, fullscreen); -} - -static GString* client_attr_pseudotile(HSAttribute* attr) { - CLIENT_UPDATE_ATTR(client_set_pseudotile, pseudotile); -} - -static GString* client_attr_urgent(HSAttribute* attr) { - CLIENT_UPDATE_ATTR(client_set_urgent_force, urgent); -} - -static GString* client_attr_sh_tiling(HSAttribute* attr) { - HSClient* client = container_of(attr->value.b, HSClient, sizehints_tiling); - if (!is_client_floated(client) && !client->pseudotile) { - HSMonitor* mon = find_monitor_with_tag(client->tag); - if (mon) { - monitor_apply_layout(mon); - } - } - return NULL; -} - -static GString* client_attr_sh_floating(HSAttribute* attr) { - HSClient* client = container_of(attr->value.b, HSClient, sizehints_floating); - if (!is_client_floated(client) || client->pseudotile) { - HSMonitor* mon = find_monitor_with_tag(client->tag); - if (mon) { - monitor_apply_layout(mon); - } - } - return NULL; -} - -HSClient* manage_client(Window win) { - if (is_herbstluft_window(g_display, win)) { - // ignore our own window - return NULL; - } - if (get_client_from_window(win)) { - return NULL; - } - // init client - HSClient* client = create_client(); - client->pid = window_pid(g_display, win); - HSMonitor* m = get_current_monitor(); - // set to window properties - client->window = win; - client_update_title(client); - - unsigned int border, depth; - Window root_win; - int x, y; - unsigned int w, h; - XGetGeometry(g_display, win, &root_win, &x, &y, &w, &h, &border, &depth); - // treat wanted coordinates as floating coords - client->float_size.x = x; - client->float_size.y = y; - client->float_size.width = w; - client->float_size.height = h; - client->last_size = client->float_size; - - // apply rules - HSClientChanges changes; - client_changes_init(&changes, client); - rules_apply(client, &changes); - if (changes.tag_name) { - client->tag = find_tag(changes.tag_name->str); - } - if (changes.monitor_name) { - HSMonitor *monitor = string_to_monitor(changes.monitor_name->str); - if (monitor) { - // a valid tag was not already found, use the target monitor's tag - if (!client->tag) { client->tag = monitor->tag; } - // a tag was already found, display it on the target monitor, but - // only if switchtag is set - else if (changes.switchtag) { - monitor_set_tag(monitor, client->tag); - } - } - } - - // Reuse the keymask string - client->keymask = changes.keymask; - - if (!changes.manage) { - client_changes_free_members(&changes); - client_destroy(client); - // map it... just to be sure - XMapWindow(g_display, win); - return NULL; - } - - // actually manage it - decoration_setup_frame(client); - client_fuzzy_fix_initial_position(client); - g_hash_table_insert(g_clients, &(client->window), client); - client->window_str = g_string_sized_new(10); - g_string_printf(client->window_str, "0x%lx", win); - hsobject_link(g_client_object, &client->object, client->window_str->str); - // insert to layout - if (!client->tag) { - client->tag = m->tag; - } - // insert window to the stack - client->slice = slice_create_client(client); - stack_insert_slice(client->tag->stack, client->slice); - // insert window to the tag - frame_insert_client(lookup_frame(client->tag->frame, changes.tree_index->str), client); - client_update_wm_hints(client); - updatesizehints(client); - if (changes.focus) { - // give focus to window if wanted - // TODO: make this faster! - // WARNING: this solution needs O(C + exp(D)) time where W is the count - // of clients on this tag and D is the depth of the binary layout tree - frame_focus_client(client->tag->frame, client); - } - - client->object.data = client; - - HSAttribute attributes[] = { - ATTRIBUTE_STRING( "winid", client->window_str, ATTR_READ_ONLY), - ATTRIBUTE_STRING( "title", client->title, ATTR_READ_ONLY), - ATTRIBUTE_STRING( "keymask", client->keymask, ATTR_READ_ONLY), - ATTRIBUTE_CUSTOM( "tag", client_attr_tag, ATTR_READ_ONLY), - ATTRIBUTE_INT( "pid", client->pid, ATTR_READ_ONLY), - ATTRIBUTE_CUSTOM( "class", client_attr_class, ATTR_READ_ONLY), - ATTRIBUTE_CUSTOM( "instance", client_attr_instance, ATTR_READ_ONLY), - ATTRIBUTE_BOOL( "fullscreen", client->fullscreen, client_attr_fullscreen), - ATTRIBUTE_BOOL( "pseudotile", client->pseudotile, client_attr_pseudotile), - ATTRIBUTE_BOOL( "ewmhrequests", client->ewmhrequests, ATTR_ACCEPT_ALL), - ATTRIBUTE_BOOL( "ewmhnotify", client->ewmhnotify, ATTR_ACCEPT_ALL), - ATTRIBUTE_BOOL( "sizehints_tiling", client->sizehints_tiling, client_attr_sh_tiling), - ATTRIBUTE_BOOL( "sizehints_floating", client->sizehints_floating, client_attr_sh_floating), - ATTRIBUTE_BOOL( "urgent", client->urgent, client_attr_urgent), - ATTRIBUTE_LAST, - }; - hsobject_set_attributes(&client->object, attributes); - - ewmh_window_update_tag(client->window, client->tag); - tag_set_flags_dirty(); - client_set_fullscreen(client, changes.fullscreen); - ewmh_update_window_state(client); - // add client after setting the correct tag for the new client - // this ensures a panel can read the tag property correctly at this point - ewmh_add_client(client->window); - - XSetWindowBorderWidth(g_display, client->window,0); - // specify that the client window survives if hlwm dies, i.e. it will be - // reparented back to root - XChangeSaveSet(g_display, client->window, SetModeInsert); - XReparentWindow(g_display, client->window, client->dec.decwin, 40, 40); - if (g_startup) client->ignore_unmaps++; - // get events from window - XSelectInput(g_display, client->dec.decwin, (EnterWindowMask | LeaveWindowMask | - ButtonPressMask | ButtonReleaseMask | - ExposureMask | - SubstructureRedirectMask | FocusChangeMask)); - XSelectInput(g_display, win, CLIENT_EVENT_MASK); - - HSMonitor* monitor = find_monitor_with_tag(client->tag); - if (monitor) { - if (monitor != get_current_monitor() - && changes.focus && changes.switchtag) { - monitor_set_tag(get_current_monitor(), client->tag); - } - // TODO: monitor_apply_layout() maybe is called twice here if it - // already is called by monitor_set_tag() - monitor_apply_layout(monitor); - client_set_visible(client, true); - } else { - if (changes.focus && changes.switchtag) { - monitor_set_tag(get_current_monitor(), client->tag); - client_set_visible(client, true); - } - } - client_send_configure(client); - - client_changes_free_members(&changes); - grab_client_buttons(client, false); - - return client; -} - -void unmanage_client(Window win) { - HSClient* client = get_client_from_window(win); - if (!client) { - return; - } - if (client->dragged) { - mouse_stop_drag(); - } - // remove from tag - frame_remove_client(client->tag->frame, client); - // ignore events from it - XSelectInput(g_display, win, 0); - //XUngrabButton(g_display, AnyButton, AnyModifier, win); - // permanently remove it - XUnmapWindow(g_display, client->dec.decwin); - XReparentWindow(g_display, win, g_root, 0, 0); - // delete ewmh-properties and ICCCM-Properties such that the client knows - // that he has been unmanaged and now the client is allowed to be mapped - // again (e.g. if it is some dialog) - ewmh_clear_client_properties(client); - XDeleteProperty(g_display, client->window, g_wmatom[WMState]); - HSTag* tag = client->tag; - g_hash_table_remove(g_clients, &win); - client = NULL; - // and arrange monitor after the client has been removed from the stack - HSMonitor* m = find_monitor_with_tag(tag); - tag_update_focus_layer(tag); - if (m) monitor_apply_layout(m); - ewmh_remove_client(win); - tag_set_flags_dirty(); -} - -// destroys a special client -void client_destroy(HSClient* client) { - hsobject_unlink(g_client_object, &client->object); - decoration_free(&client->dec); - if (lastfocus == client) { - lastfocus = NULL; - } - if (client->tag && client->slice) { - stack_remove_slice(client->tag->stack, client->slice); - } - if (client->slice) { - slice_destroy(client->slice); - } - if (client->title) { - /* free window title */ - g_string_free(client->title, true); - } - if (client->window_str) { - g_string_free(client->window_str, true); - } - if (client->keymask) { - g_string_free(client->keymask, true); - } - hsobject_free(&client->object); - g_free(client); -} - -static int client_get_scheme_triple_idx(HSClient* client) { - if (client->fullscreen) return HSDecSchemeFullscreen; - else if (is_client_floated(client)) return HSDecSchemeFloating; - else if (client_needs_minimal_dec(client, NULL)) return HSDecSchemeMinimal; - else return HSDecSchemeTiling; -} - -bool client_needs_minimal_dec(HSClient* client, HSFrame* frame) { - if (!frame) { - frame = find_frame_with_client(client->tag->frame, client); - HSAssert(frame != NULL); - } - if (!smart_window_surroundings_active(frame)) return false; - if (client->pseudotile) return false; - if (is_client_floated(client)) return false; - return true; -} - -void client_window_unfocus(HSClient* client) { - if (!client) return; - grab_client_buttons(client, false); -} - -void client_window_unfocus_last() { - if (lastfocus) { - client_window_unfocus(lastfocus); - } - hsobject_unlink_by_name(g_client_object, "focus"); - // give focus to root window - XSetInputFocus(g_display, g_root, RevertToPointerRoot, CurrentTime); - if (lastfocus) { - /* only emit the hook if the focus *really* changes */ - hook_emit_list("focus_changed", "0x0", "", NULL); - ewmh_update_active_window(None); - tag_update_each_focus_layer(); - - // Enable all keys in the root window - key_set_keymask(get_current_monitor()->tag, 0); - } - lastfocus = 0; -} - -void client_window_focus(HSClient* client) { - assert(client != NULL); - // set keyboard focus - if (!client->neverfocus) { - XSetInputFocus(g_display, client->window, RevertToPointerRoot, CurrentTime); - } - else client_sendevent(client, g_wmatom[WMTakeFocus]); - - if (client != lastfocus) { - /* FIXME: this is a workaround because window_focus always is called - * twice. see BUGS for more information - * - * only emit the hook if the focus *really* changes */ - // unfocus last one - client_window_unfocus(lastfocus); - hsobject_link(g_client_object, &client->object, "focus"); - ewmh_update_active_window(client->window); - tag_update_each_focus_layer(); - char* title = client ? client->title->str : "?"; - char winid_str[STRING_BUF_SIZE]; - snprintf(winid_str, STRING_BUF_SIZE, "0x%x", (unsigned int)client->window); - hook_emit_list("focus_changed", winid_str, title, NULL); - } - - // change window-colors - //HSDebug("window_focus ACTIVE: 0x%lx\n", client->window); - //client_setup_border(client, true); - - lastfocus = client; - /* do some specials for the max layout */ - bool is_max_layout = frame_focused_client(g_cur_frame) == client - && g_cur_frame->content.clients.layout == LAYOUT_MAX - && get_current_monitor()->tag->floating == false; - if (*g_raise_on_focus || is_max_layout) { - client_raise(client); - } - tag_update_focus_layer(get_current_monitor()->tag); - grab_client_buttons(client, true); - key_set_keymask(client->tag, client); - client_set_urgent(client, false); -} - -void client_setup_border(HSClient* client, bool focused) { - if (focused) { - decoration_change_scheme(client, - g_decorations[client_get_scheme_triple_idx(client)].active); - } else if (client->urgent) { - decoration_change_scheme(client, - g_decorations[client_get_scheme_triple_idx(client)].urgent); - } else { - decoration_change_scheme(client, - g_decorations[client_get_scheme_triple_idx(client)].normal); - } -} - -static void client_resize_fullscreen(HSClient* client, HSMonitor* m) { - if (!client || !m) { - HSDebug("client_resize_fullscreen() got invalid parameters\n"); - return; - } - decoration_resize_outline(client, m->rect, - client_scheme_from_triple(client, HSDecSchemeFullscreen)); -} - -void client_raise(HSClient* client) { - assert(client); - stack_raise_slide(client->tag->stack, client->slice); -} - -static HSDecorationScheme client_scheme_from_triple(HSClient* client, int tripidx) { - if (get_current_client() == client) { - return g_decorations[tripidx].active; - } else if (client->urgent) { - return g_decorations[tripidx].urgent; - } else { - return g_decorations[tripidx].normal; - } -} - -void client_resize_tiling(HSClient* client, Rectangle rect, HSFrame* frame) { - HSMonitor* m; - if (client->fullscreen && (m = find_monitor_with_tag(client->tag))) { - client_resize_fullscreen(client, m); - return; - } - // apply border width - if (!client->pseudotile && !smart_window_surroundings_active(frame)) { - // apply window gap - rect.width -= *g_window_gap; - rect.height -= *g_window_gap; - } - HSDecorationScheme scheme = client_scheme_from_triple(client, HSDecSchemeTiling); - if (client->pseudotile) { - Rectangle inner = client->float_size; - applysizehints(client, &inner.width, &inner.height); - Rectangle outline = inner_rect_to_outline(inner, scheme); - rect.x += MAX(0, (rect.width - outline.width)/2); - rect.y += MAX(0, (rect.height - outline.height)/2); - rect.width = MIN(outline.width, rect.width); - rect.height = MIN(outline.height, rect.height); - scheme.tight_decoration = true; - } - if (client_needs_minimal_dec(client, frame)) { - scheme = client_scheme_from_triple(client, HSDecSchemeMinimal); - } - decoration_resize_outline(client, rect, scheme); -} - -// from dwm.c -bool applysizehints(HSClient *c, int *w, int *h) { - bool baseismin; - - /* set minimum possible */ - *w = MAX(1, *w); - *h = MAX(1, *h); - if(*h < WINDOW_MIN_HEIGHT) - *h = WINDOW_MIN_HEIGHT; - if(*w < WINDOW_MIN_WIDTH) - *w = WINDOW_MIN_WIDTH; - bool sizehints = (is_client_floated(c) || c->pseudotile) - ? c->sizehints_floating - : c->sizehints_tiling; - if(sizehints) { - /* see last two sentences in ICCCM 4.1.2.3 */ - baseismin = c->basew == c->minw && c->baseh == c->minh; - if(!baseismin) { /* temporarily remove base dimensions */ - *w -= c->basew; - *h -= c->baseh; - } - /* adjust for aspect limits */ - if(c->mina > 0 && c->maxa > 0) { - if(c->maxa < (float)*w / *h) - *w = *h * c->maxa + 0.5; - else if(c->mina < (float)*h / *w) - *h = *w * c->mina + 0.5; - } - if(baseismin) { /* increment calculation requires this */ - *w -= c->basew; - *h -= c->baseh; - } - /* adjust for increment value */ - if(c->incw) - *w -= *w % c->incw; - if(c->inch) - *h -= *h % c->inch; - /* restore base dimensions */ - *w += c->basew; - *h += c->baseh; - *w = MAX(*w, c->minw); - *h = MAX(*h, c->minh); - if(c->maxw) - *w = MIN(*w, c->maxw); - if(c->maxh) - *h = MIN(*h, c->maxh); - } - return *w != c->last_size.width || *h != c->last_size.height; -} - -bool applysizehints_xy(HSClient *c, int *x, int *y, int *w, int *h) { - return applysizehints(c,w,h) || *x != c->last_size.x - || *y != c->last_size.y; -} - -// from dwm.c -void updatesizehints(HSClient *c) { - long msize; - XSizeHints size; - - if(!XGetWMNormalHints(g_display, c->window, &size, &msize)) - /* size is uninitialized, ensure that size.flags aren't used */ - size.flags = PSize; - if(size.flags & PBaseSize) { - c->basew = size.base_width; - c->baseh = size.base_height; - } - else if(size.flags & PMinSize) { - c->basew = size.min_width; - c->baseh = size.min_height; - } else { - c->basew = c->baseh = 0; - } - if(size.flags & PResizeInc) { - c->incw = size.width_inc; - c->inch = size.height_inc; - } - else - c->incw = c->inch = 0; - if(size.flags & PMaxSize) { - c->maxw = size.max_width; - c->maxh = size.max_height; - } else { - c->maxw = c->maxh = 0; - } - if(size.flags & PMinSize) { - c->minw = size.min_width; - c->minh = size.min_height; - } - else if(size.flags & PBaseSize) { - c->minw = size.base_width; - c->minh = size.base_height; - } else { - c->minw = c->minh = 0; - } - if(size.flags & PAspect) { - c->mina = (float)size.min_aspect.y / size.min_aspect.x; - c->maxa = (float)size.max_aspect.x / size.max_aspect.y; - } else { - c->maxa = c->mina = 0.0; - } - //c->isfixed = (c->maxw && c->minw && c->maxh && c->minh - // && c->maxw == c->minw && c->maxh == c->minh); -} - - - - -void client_send_configure(HSClient *c) { - XConfigureEvent ce = { - .type = ConfigureNotify, - .display = g_display, - .event = c->window, - .window = c->window, - .x = c->dec.last_inner_rect.x, - .y = c->dec.last_inner_rect.y, - .width = MAX(c->dec.last_inner_rect.width, WINDOW_MIN_WIDTH), - .height = MAX(c->dec.last_inner_rect.height, WINDOW_MIN_HEIGHT), - .border_width = 0, - .above = None, - .override_redirect = False, - }; - XSendEvent(g_display, c->window, False, StructureNotifyMask, (XEvent *)&ce); -} - -void client_resize_floating(HSClient* client, HSMonitor* m) { - if (!client || !m) return; - if (client->fullscreen) { - client_resize_fullscreen(client, m); - return; - } - Rectangle rect = client->float_size; - rect.x += m->rect.x; - rect.x += m->rect.y; - rect.x += m->pad_left; - rect.y += m->pad_up; - // ensure position is on monitor - int space = g_monitor_float_treshold; - rect.x = - CLAMP(rect.x, - m->rect.x + m->pad_left - rect.width + space, - m->rect.x + m->rect.width - m->pad_left - m->pad_right - space); - rect.y = - CLAMP(rect.y, - m->rect.y + m->pad_up - rect.height + space, - m->rect.y + m->rect.height - m->pad_up - m->pad_down - space); - decoration_resize_inner(client, rect, - client_scheme_from_triple(client, HSDecSchemeFloating)); -} - -Rectangle client_outer_floating_rect(HSClient* client) { - return inner_rect_to_outline(client->float_size, client->dec.last_scheme); -} - -int close_command(int argc, char** argv, GString* output) { - Window win; - HSClient* client = NULL; - win = string_to_client((argc > 1) ? argv[1] : "", &client); - if (win) window_close(win); - else return HERBST_INVALID_ARGUMENT; - return 0; -} - -bool is_client_floated(HSClient* client) { - return client->tag->floating; -} - -void window_close(Window window) { - XEvent ev; - ev.type = ClientMessage; - ev.xclient.window = window; - ev.xclient.message_type = g_wmatom[WMProtocols]; - ev.xclient.format = 32; - ev.xclient.data.l[0] = g_wmatom[WMDelete]; - ev.xclient.data.l[1] = CurrentTime; - XSendEvent(g_display, window, False, NoEventMask, &ev); -} - -void window_set_visible(Window win, bool visible) { - static int (*action[])(Display*,Window) = { - XUnmapWindow, - XMapWindow, - }; - unsigned long event_mask = CLIENT_EVENT_MASK; - XGrabServer(g_display); - XSelectInput(g_display, win, event_mask & ~StructureNotifyMask); - XSelectInput(g_display, g_root, ROOT_EVENT_MASK & ~SubstructureNotifyMask); - action[visible](g_display, win); - XSelectInput(g_display, win, event_mask); - XSelectInput(g_display, g_root, ROOT_EVENT_MASK); - XUngrabServer(g_display); -} - -void client_set_visible(HSClient* client, bool visible) { - if (visible == client->visible) return; - if (visible) { - /* Grab the server to make sure that the frame window is mapped before - the client gets its MapNotify, i.e. to make sure the client is - _visible_ when it gets MapNotify. */ - XGrabServer(g_display); - window_update_wm_state(client->window, WmStateNormalState); - XMapWindow(g_display, client->window); - XMapWindow(g_display, client->dec.decwin); - XUngrabServer(g_display); - } else { - /* we unmap the client itself so that we can get MapRequest - events, and because the ICCCM tells us to! */ - XUnmapWindow(g_display, client->dec.decwin); - XUnmapWindow(g_display, client->window); - window_update_wm_state(client->window, WmStateWithdrawnState); - client->ignore_unmaps++; - } - client->visible = visible; -} - -// heavily inspired by dwm.c -void client_set_urgent(HSClient* client, bool state) { - if (client->urgent == state) { - // nothing to do - return; - } - client_set_urgent_force(client, state); -} - -static void client_set_urgent_force(HSClient* client, bool state) { - char winid_str[STRING_BUF_SIZE]; - snprintf(winid_str, STRING_BUF_SIZE, "0x%lx", client->window); - hook_emit_list("urgent", state ? "on" : "off", winid_str, NULL); - - client->urgent = state; - - client_setup_border(client, client == frame_focused_client(g_cur_frame)); - - XWMHints *wmh; - if(!(wmh = XGetWMHints(g_display, client->window))) - return; - - if (state) { - wmh->flags |= XUrgencyHint; - } else { - wmh->flags &= ~XUrgencyHint; - } - - XSetWMHints(g_display, client->window, wmh); - XFree(wmh); - // report changes to tags - tag_set_flags_dirty(); -} - -// heavily inspired by dwm.c -void client_update_wm_hints(HSClient* client) { - XWMHints* wmh = XGetWMHints(g_display, client->window); - if (!wmh) { - return; - } - - HSClient* focused_client = frame_focused_client(g_cur_frame); - if ((focused_client == client) - && wmh->flags & XUrgencyHint) { - // remove urgency hint if window is focused - wmh->flags &= ~XUrgencyHint; - XSetWMHints(g_display, client->window, wmh); - } else { - bool newval = (wmh->flags & XUrgencyHint) ? true : false; - if (newval != client->urgent) { - client->urgent = newval; - char winid_str[STRING_BUF_SIZE]; - snprintf(winid_str, STRING_BUF_SIZE, "0x%lx", client->window); - client_setup_border(client, focused_client == client); - hook_emit_list("urgent", client->urgent ? "on":"off", winid_str, NULL); - tag_set_flags_dirty(); - } - } - if (wmh->flags & InputHint) { - client->neverfocus = !wmh->input; - } else { - client->neverfocus = false; - } - XFree(wmh); -} - -void client_update_title(HSClient* client) { - GString* new_name = window_property_to_g_string(g_display, - client->window, g_netatom[NetWmName]); - if (!new_name) { - char* ch_new_name = NULL; - /* if ewmh name isn't set, then fall back to WM_NAME */ - if (0 != XFetchName(g_display, client->window, &ch_new_name)) { - new_name = g_string_new(ch_new_name); - XFree(ch_new_name); - } else { - new_name = g_string_new(""); - HSDebug("no title for window %lx found, using \"\"\n", - client->window); - } - } - bool changed = (0 != strcmp(client->title->str, new_name->str)); - g_string_free(client->title, true); - client->title = new_name; - if (changed && get_current_client() == client) { - char buf[STRING_BUF_SIZE]; - snprintf(buf, STRING_BUF_SIZE, "0x%lx", client->window); - hook_emit_list("window_title_changed", buf, client->title->str, NULL); - } -} - -HSClient* get_current_client() { - return frame_focused_client(g_cur_frame); -} - -void client_set_fullscreen(HSClient* client, bool state) { - if (client->fullscreen == state) return; - client->fullscreen = state; - if (client->ewmhnotify) { - client->ewmhfullscreen = state; - } - HSStack* stack = client->tag->stack; - if (state) { - stack_slice_add_layer(stack, client->slice, LAYER_FULLSCREEN); - } else { - stack_slice_remove_layer(stack, client->slice, LAYER_FULLSCREEN); - } - tag_update_focus_layer(client->tag); - monitor_apply_layout(find_monitor_with_tag(client->tag)); - - char buf[STRING_BUF_SIZE]; - snprintf(buf, STRING_BUF_SIZE, "0x%lx", client->window); - ewmh_update_window_state(client); - hook_emit_list("fullscreen", state ? "on" : "off", buf, NULL); -} - -void client_set_pseudotile(HSClient* client, bool state) { - client->pseudotile = state; - monitor_apply_layout(find_monitor_with_tag(client->tag)); -} - -int client_set_property_command(int argc, char** argv) { - char* action = (argc > 1) ? argv[1] : "toggle"; - - HSClient* client = get_current_client(); - if (!client) { - // nothing to do - return 0; - } - - struct { - char* name; - void (*func)(HSClient*, bool); - bool* value; - } properties[] = { - { "fullscreen", client_set_fullscreen, &client->fullscreen }, - { "pseudotile", client_set_pseudotile, &client->pseudotile }, - }; - - // find the property - int i; - for (i = 0; i < LENGTH(properties); i++) { - if (!strcmp(properties[i].name, argv[0])) { - break; - } - } - if (i >= LENGTH(properties)) { - return HERBST_INVALID_ARGUMENT; - } - - // if found, then change it - bool old_value = *(properties[i].value); - bool state = string_to_bool(action, *(properties[i].value)); - if (state != old_value) { - properties[i].func(client, state); - } - return 0; -} - -static bool is_client_urgent(void* key, HSClient* client, void* data) { - (void) key; - (void) data; - return client->urgent; -} - -HSClient* get_urgent_client() { - return g_hash_table_find(g_clients, (GHRFunc)is_client_urgent, NULL); -} - -/** - * \brief Resolve a window description to a client or a window id - * - * \param str Describes the window: "" means the focused one, "urgent" - * resolves to a arbitrary urgent window, "0x..." just - * resolves to the given window given its hexadecimal window id, - * a decimal number its decimal window id. - * \param ret_client The client pointer is stored there if ret_client is - * given and the specified window is managed. - * \return The resolved window id is stored there if the according - * window has been found - */ -Window string_to_client(char* str, HSClient** ret_client) { - Window win = 0; - if (!strcmp(str, "")) { - HSClient* client = get_current_client(); - win = client ? client->window : 0; - if (ret_client) { - *ret_client = client; - } - } else if (!strcmp(str, "urgent")) { - HSClient* client = get_urgent_client(); - if (client) { - win = client->window; - } - if (ret_client) { - *ret_client = client; - } - } else if (1 == sscanf(str, "0x%lx", (long unsigned int*)&win)) { - if (ret_client) { - *ret_client = get_client_from_window(win); - } - } else if (1 == sscanf(str, "%lu", (long unsigned int*)&win)) { - if (ret_client) { - *ret_client = get_client_from_window(win); - } - } - return win; -} - -// mainly from dwm.c -bool client_sendevent(HSClient *client, Atom proto) { - int n; - Atom *protocols; - bool exists = false; - XEvent ev; - - if (XGetWMProtocols(g_display, client->window, &protocols, &n)) { - while (!exists && n--) - exists = protocols[n] == proto; - XFree(protocols); - } - if (exists) { - ev.type = ClientMessage; - ev.xclient.window = client->window; - ev.xclient.message_type = g_wmatom[WMProtocols]; - ev.xclient.format = 32; - ev.xclient.data.l[0] = proto; - ev.xclient.data.l[1] = CurrentTime; - XSendEvent(g_display, client->window, False, NoEventMask, &ev); - } - return exists; -} - -void client_set_dragged(HSClient* client, bool drag_state) { - if (drag_state == client->dragged) return; - client->dragged = drag_state; - if (drag_state == true) { - hsobject_link(g_client_object, &client->object, "dragged"); - } else { - hsobject_unlink_by_name(g_client_object, "dragged"); - } -} - -void client_fuzzy_fix_initial_position(HSClient* client) { - // find out the top-left-most position of the decoration, - // considering the current settings of possible floating decorations - int extreme_x = client->float_size.x; - int extreme_y = client->float_size.y; - HSDecTriple* t = &g_decorations[HSDecSchemeFloating]; - Rectangle r = inner_rect_to_outline(client->float_size, t->active); - extreme_x = MIN(extreme_x, r.x); - extreme_y = MIN(extreme_y, r.y); - r = inner_rect_to_outline(client->float_size, t->normal); - extreme_x = MIN(extreme_x, r.x); - extreme_y = MIN(extreme_y, r.y); - r = inner_rect_to_outline(client->float_size, t->urgent); - extreme_x = MIN(extreme_x, r.x); - extreme_y = MIN(extreme_y, r.y); - // if top left corner might be outside of the monitor, move it accordingly - if (extreme_x < 0) { client->float_size.x += abs(extreme_x); } - if (extreme_y < 0) { client->float_size.y += abs(extreme_y); } -} - diff -Nru herbstluftwm-0.6.2/src/clientlist.cpp herbstluftwm-0.7.0/src/clientlist.cpp --- herbstluftwm-0.6.2/src/clientlist.cpp 1970-01-01 00:00:00.000000000 +0000 +++ herbstluftwm-0.7.0/src/clientlist.cpp 2015-10-14 13:27:40.000000000 +0000 @@ -0,0 +1,1063 @@ +/** Copyright 2011-2013 Thorsten Wißmann. All rights reserved. + * + * This software is licensed under the "Simplified BSD License". + * See LICENSE for details */ + +#include "clientlist.h" +#include "settings.h" +#include "globals.h" +#include "layout.h" +#include "stack.h" +#include "utils.h" +#include "hook.h" +#include "mouse.h" +#include "ewmh.h" +#include "rules.h" +#include "ipc-protocol.h" +#include "object.h" +#include "decoration.h" +#include "key.h" +#include "desktopwindow.h" +// system +#include "glib-backports.h" +#include +#include +#include +#include +#include +#include +#include +// gui +#include +#include +#include +#include + +static int g_monitor_float_treshold = 24; + +static int* g_raise_on_focus; +static int* g_snap_gap; + +static GHashTable* g_clients; // container of all clients +static HSObject* g_client_object; + +// atoms from dwm.c +// default atoms +enum { WMProtocols, WMDelete, WMState, WMTakeFocus, WMLast }; +static Atom g_wmatom[WMLast]; + +static HSClient* lastfocus = NULL; +static void client_set_urgent_force(HSClient* client, bool state); +static HSDecorationScheme client_scheme_from_triple(HSClient* client, int tripidx); +static int client_get_scheme_triple_idx(HSClient* client); + +static bool g_startup = true; // whether hlwm is starting up and is not in the + // main event loop yet +static HSClient* create_client() { + HSClient* hc = g_new0(HSClient, 1); + hsobject_init(&hc->object); + hc->window_str = NULL; + hc->float_size.width = 100; + hc->float_size.height = 100; + hc->title = g_string_new(""); + hc->urgent = false; + hc->fullscreen = false; + hc->ewmhfullscreen = false; + hc->pseudotile = false; + hc->ewmhrequests = true; + hc->ewmhnotify = true; + hc->sizehints_floating = true; + hc->sizehints_tiling = false; + hc->visible = false; + return hc; +} + +static void fetch_colors() { + g_window_gap = &(settings_find("window_gap")->value.i); + g_snap_gap = &(settings_find("snap_gap")->value.i); + g_raise_on_focus = &(settings_find("raise_on_focus")->value.i); +} + +void clientlist_init() { + // init regex simple.. + fetch_colors(); + g_wmatom[WMProtocols] = XInternAtom(g_display, "WM_PROTOCOLS", False); + g_wmatom[WMDelete] = XInternAtom(g_display, "WM_DELETE_WINDOW", False); + g_wmatom[WMState] = XInternAtom(g_display, "WM_STATE", False); + g_wmatom[WMTakeFocus] = XInternAtom(g_display, "WM_TAKE_FOCUS", False); + // init actual client list + g_client_object = hsobject_create_and_link(hsobject_root(), "clients"); + g_clients = g_hash_table_new_full(g_int_hash, g_int_equal, + NULL, (GDestroyNotify)client_destroy); +} + +void clientlist_end_startup() { + g_startup = false; +} + +bool clientlist_ignore_unmapnotify(Window win) { + HSClient* c = get_client_from_window(win); + if (c && c->ignore_unmaps > 0) { + c->ignore_unmaps--; + return true; + } else { + return false; + } +} + +void reset_client_colors() { + fetch_colors(); + all_monitors_apply_layout(); +} + +static void client_move_to_floatpos(void* key, void* client_void, void* data) { + (void)key; + (void)data; + HSClient* client = (HSClient*)client_void; + if (client) { + int x = client->float_size.x; + int y = client->float_size.y; + unsigned int w = client->float_size.width; + unsigned int h = client->float_size.height; + XMoveResizeWindow(g_display, client->window, x, y, w, h); + XReparentWindow(g_display, client->window, g_root, x, y); + ewmh_update_frame_extents(client->window, 0,0,0,0); + } +} + +static void client_show_window(void* key, void* client_void, void* data) { + (void)key; + (void)data; + HSClient* client = (HSClient*)client_void; + window_set_visible(client->window, true); +} + +void clientlist_destroy() { + // move all clients to their original floating position + g_hash_table_foreach(g_clients, client_move_to_floatpos, NULL); + g_hash_table_foreach(g_clients, client_show_window, NULL); + + g_hash_table_destroy(g_clients); + hsobject_unlink_and_destroy(hsobject_root(), g_client_object); +} + + +void clientlist_foreach(GHFunc func, gpointer data) { + g_hash_table_foreach(g_clients, func, data); +} + +HSClient* get_client_from_window(Window window) { + return (HSClient*) g_hash_table_lookup(g_clients, &window); +} + +#define CLIENT_UPDATE_ATTR(FUNC,MEMBER) do { \ + HSClient* client = container_of(attr->value.b, HSClient, MEMBER); \ + bool val = client->MEMBER; \ + client->MEMBER = ! client->MEMBER ; /* enforce update of MEMBER */ \ + FUNC(client, val); \ + return NULL; \ + } \ + while (0); + +static void client_attr_tag(void* data, GString* output) { + HSClient* client = (HSClient*) data; + g_string_append(output, client->tag->display_name->str); +} + +static void client_attr_class(void* data, GString* output) { + HSClient* client = (HSClient*) data; + GString* ret = window_class_to_g_string(g_display, client->window); + g_string_append(output, ret->str); + g_string_free(ret, true); +} + +static void client_attr_instance(void* data, GString* output) { + HSClient* client = (HSClient*) data; + GString* ret = window_instance_to_g_string(g_display, client->window); + g_string_append(output, ret->str); + g_string_free(ret, true); +} + +static GString* client_attr_fullscreen(HSAttribute* attr) { + CLIENT_UPDATE_ATTR(client_set_fullscreen, fullscreen); +} + +static GString* client_attr_pseudotile(HSAttribute* attr) { + CLIENT_UPDATE_ATTR(client_set_pseudotile, pseudotile); +} + +static GString* client_attr_urgent(HSAttribute* attr) { + CLIENT_UPDATE_ATTR(client_set_urgent_force, urgent); +} + +static GString* client_attr_sh_tiling(HSAttribute* attr) { + HSClient* client = container_of(attr->value.b, HSClient, sizehints_tiling); + if (!is_client_floated(client) && !client->pseudotile) { + HSMonitor* mon = find_monitor_with_tag(client->tag); + if (mon) { + monitor_apply_layout(mon); + } + } + return NULL; +} + +static GString* client_attr_sh_floating(HSAttribute* attr) { + HSClient* client = container_of(attr->value.b, HSClient, sizehints_floating); + if (!is_client_floated(client) || client->pseudotile) { + HSMonitor* mon = find_monitor_with_tag(client->tag); + if (mon) { + monitor_apply_layout(mon); + } + } + return NULL; +} + +HSClient* manage_client(Window win) { + if (is_herbstluft_window(g_display, win)) { + // ignore our own window + return NULL; + } + if (get_client_from_window(win)) { + return NULL; + } + if (ewmh_is_desktop_window(win)) { + DesktopWindow::registerDesktop(win); + monitor_restack(get_current_monitor()); + XMapWindow(g_display, win); + return NULL; + } + // init client + HSClient* client = create_client(); + client->pid = window_pid(g_display, win); + HSMonitor* m = get_current_monitor(); + // set to window properties + client->window = win; + client_update_title(client); + + unsigned int border, depth; + Window root_win; + int x, y; + unsigned int w, h; + XGetGeometry(g_display, win, &root_win, &x, &y, &w, &h, &border, &depth); + // treat wanted coordinates as floating coords + client->float_size.x = x; + client->float_size.y = y; + client->float_size.width = w; + client->float_size.height = h; + client->last_size = client->float_size; + + // apply rules + HSClientChanges changes; + client_changes_init(&changes, client); + rules_apply(client, &changes); + if (changes.tag_name) { + client->tag = find_tag(changes.tag_name->str); + } + if (changes.monitor_name) { + HSMonitor *monitor = string_to_monitor(changes.monitor_name->str); + if (monitor) { + // a valid tag was not already found, use the target monitor's tag + if (!client->tag) { client->tag = monitor->tag; } + // a tag was already found, display it on the target monitor, but + // only if switchtag is set + else if (changes.switchtag) { + monitor_set_tag(monitor, client->tag); + } + } + } + + // Reuse the keymask string + client->keymask = changes.keymask; + + if (!changes.manage) { + client_changes_free_members(&changes); + client_destroy(client); + // map it... just to be sure + XMapWindow(g_display, win); + return NULL; + } + + // actually manage it + decoration_setup_frame(client); + client_fuzzy_fix_initial_position(client); + g_hash_table_insert(g_clients, &(client->window), client); + client->window_str = g_string_sized_new(10); + g_string_printf(client->window_str, "0x%lx", win); + hsobject_link(g_client_object, &client->object, client->window_str->str); + // insert to layout + if (!client->tag) { + client->tag = m->tag; + } + // insert window to the stack + client->slice = slice_create_client(client); + stack_insert_slice(client->tag->stack, client->slice); + // insert window to the tag + frame_insert_client(lookup_frame(client->tag->frame, changes.tree_index->str), client); + client_update_wm_hints(client); + updatesizehints(client); + if (changes.focus) { + // give focus to window if wanted + // TODO: make this faster! + // WARNING: this solution needs O(C + exp(D)) time where W is the count + // of clients on this tag and D is the depth of the binary layout tree + frame_focus_client(client->tag->frame, client); + } + + client->object.data = client; + + HSAttribute attributes[] = { + ATTRIBUTE_STRING( "winid", client->window_str, ATTR_READ_ONLY), + ATTRIBUTE_STRING( "title", client->title, ATTR_READ_ONLY), + ATTRIBUTE_STRING( "keymask", client->keymask, ATTR_READ_ONLY), + ATTRIBUTE_CUSTOM( "tag", client_attr_tag, ATTR_READ_ONLY), + ATTRIBUTE_INT( "pid", client->pid, ATTR_READ_ONLY), + ATTRIBUTE_CUSTOM( "class", client_attr_class, ATTR_READ_ONLY), + ATTRIBUTE_CUSTOM( "instance", client_attr_instance, ATTR_READ_ONLY), + ATTRIBUTE_BOOL( "fullscreen", client->fullscreen, client_attr_fullscreen), + ATTRIBUTE_BOOL( "pseudotile", client->pseudotile, client_attr_pseudotile), + ATTRIBUTE_BOOL( "ewmhrequests", client->ewmhrequests, ATTR_ACCEPT_ALL), + ATTRIBUTE_BOOL( "ewmhnotify", client->ewmhnotify, ATTR_ACCEPT_ALL), + ATTRIBUTE_BOOL( "sizehints_tiling", client->sizehints_tiling, client_attr_sh_tiling), + ATTRIBUTE_BOOL( "sizehints_floating", client->sizehints_floating, client_attr_sh_floating), + ATTRIBUTE_BOOL( "urgent", client->urgent, client_attr_urgent), + ATTRIBUTE_LAST, + }; + hsobject_set_attributes(&client->object, attributes); + + ewmh_window_update_tag(client->window, client->tag); + tag_set_flags_dirty(); + client_set_fullscreen(client, changes.fullscreen); + ewmh_update_window_state(client); + // add client after setting the correct tag for the new client + // this ensures a panel can read the tag property correctly at this point + ewmh_add_client(client->window); + + XSetWindowBorderWidth(g_display, client->window,0); + // specify that the client window survives if hlwm dies, i.e. it will be + // reparented back to root + XChangeSaveSet(g_display, client->window, SetModeInsert); + XReparentWindow(g_display, client->window, client->dec.decwin, 40, 40); + if (g_startup) client->ignore_unmaps++; + // get events from window + XSelectInput(g_display, client->dec.decwin, (EnterWindowMask | LeaveWindowMask | + ButtonPressMask | ButtonReleaseMask | + ExposureMask | + SubstructureRedirectMask | FocusChangeMask)); + XSelectInput(g_display, win, CLIENT_EVENT_MASK); + + HSMonitor* monitor = find_monitor_with_tag(client->tag); + if (monitor) { + if (monitor != get_current_monitor() + && changes.focus && changes.switchtag) { + monitor_set_tag(get_current_monitor(), client->tag); + } + // TODO: monitor_apply_layout() maybe is called twice here if it + // already is called by monitor_set_tag() + monitor_apply_layout(monitor); + client_set_visible(client, true); + } else { + if (changes.focus && changes.switchtag) { + monitor_set_tag(get_current_monitor(), client->tag); + client_set_visible(client, true); + } + } + client_send_configure(client); + + client_changes_free_members(&changes); + grab_client_buttons(client, false); + + return client; +} + +void unmanage_client(Window win) { + HSClient* client = get_client_from_window(win); + if (!client) { + return; + } + if (client->dragged) { + mouse_stop_drag(); + } + // remove from tag + frame_remove_client(client->tag->frame, client); + // ignore events from it + XSelectInput(g_display, win, 0); + //XUngrabButton(g_display, AnyButton, AnyModifier, win); + // permanently remove it + XUnmapWindow(g_display, client->dec.decwin); + XReparentWindow(g_display, win, g_root, 0, 0); + // delete ewmh-properties and ICCCM-Properties such that the client knows + // that he has been unmanaged and now the client is allowed to be mapped + // again (e.g. if it is some dialog) + ewmh_clear_client_properties(client); + XDeleteProperty(g_display, client->window, g_wmatom[WMState]); + HSTag* tag = client->tag; + g_hash_table_remove(g_clients, &win); + client = NULL; + // and arrange monitor after the client has been removed from the stack + HSMonitor* m = find_monitor_with_tag(tag); + tag_update_focus_layer(tag); + if (m) monitor_apply_layout(m); + ewmh_remove_client(win); + tag_set_flags_dirty(); + + // Get the current client and update the windows focus. + client = frame_focused_client(tag->frame); + if (!client) { + hook_emit_list("window_title_changed", NULL); + } +} + +// destroys a special client +void client_destroy(HSClient* client) { + hsobject_unlink(g_client_object, &client->object); + decoration_free(&client->dec); + if (lastfocus == client) { + lastfocus = NULL; + } + if (client->tag && client->slice) { + stack_remove_slice(client->tag->stack, client->slice); + } + if (client->slice) { + slice_destroy(client->slice); + } + if (client->title) { + /* free window title */ + g_string_free(client->title, true); + } + if (client->window_str) { + g_string_free(client->window_str, true); + } + if (client->keymask) { + g_string_free(client->keymask, true); + } + hsobject_free(&client->object); + g_free(client); +} + +static int client_get_scheme_triple_idx(HSClient* client) { + if (client->fullscreen) return HSDecSchemeFullscreen; + else if (is_client_floated(client)) return HSDecSchemeFloating; + else if (client_needs_minimal_dec(client, NULL)) return HSDecSchemeMinimal; + else return HSDecSchemeTiling; +} + +bool client_needs_minimal_dec(HSClient* client, HSFrame* frame) { + if (!frame) { + frame = find_frame_with_client(client->tag->frame, client); + HSAssert(frame != NULL); + } + if (!smart_window_surroundings_active(frame)) return false; + if (client->pseudotile) return false; + if (is_client_floated(client)) return false; + return true; +} + +void client_window_unfocus(HSClient* client) { + if (!client) return; + grab_client_buttons(client, false); +} + +void client_window_unfocus_last() { + if (lastfocus) { + client_window_unfocus(lastfocus); + } + hsobject_unlink_by_name(g_client_object, "focus"); + // give focus to root window + XSetInputFocus(g_display, g_root, RevertToPointerRoot, CurrentTime); + if (lastfocus) { + /* only emit the hook if the focus *really* changes */ + hook_emit_list("focus_changed", "0x0", "", NULL); + ewmh_update_active_window(None); + tag_update_each_focus_layer(); + + // Enable all keys in the root window + key_set_keymask(get_current_monitor()->tag, 0); + } + lastfocus = 0; +} + +void client_window_focus(HSClient* client) { + assert(client != NULL); + // set keyboard focus + if (!client->neverfocus) { + XSetInputFocus(g_display, client->window, RevertToPointerRoot, CurrentTime); + } + else client_sendevent(client, g_wmatom[WMTakeFocus]); + + if (client != lastfocus) { + /* FIXME: this is a workaround because window_focus always is called + * twice. see BUGS for more information + * + * only emit the hook if the focus *really* changes */ + // unfocus last one + client_window_unfocus(lastfocus); + hsobject_link(g_client_object, &client->object, "focus"); + ewmh_update_active_window(client->window); + tag_update_each_focus_layer(); + const char* title = client ? client->title->str : "?"; + char winid_str[STRING_BUF_SIZE]; + snprintf(winid_str, STRING_BUF_SIZE, "0x%x", (unsigned int)client->window); + hook_emit_list("focus_changed", winid_str, title, NULL); + } + + // change window-colors + //HSDebug("window_focus ACTIVE: 0x%lx\n", client->window); + //client_setup_border(client, true); + + lastfocus = client; + /* do some specials for the max layout */ + bool is_max_layout = frame_focused_client(g_cur_frame) == client + && g_cur_frame->content.clients.layout == LAYOUT_MAX + && get_current_monitor()->tag->floating == false; + if (*g_raise_on_focus || is_max_layout) { + client_raise(client); + } + tag_update_focus_layer(get_current_monitor()->tag); + grab_client_buttons(client, true); + key_set_keymask(client->tag, client); + client_set_urgent(client, false); +} + +void client_setup_border(HSClient* client, bool focused) { + if (focused) { + decoration_change_scheme(client, + g_decorations[client_get_scheme_triple_idx(client)].active); + } else if (client->urgent) { + decoration_change_scheme(client, + g_decorations[client_get_scheme_triple_idx(client)].urgent); + } else { + decoration_change_scheme(client, + g_decorations[client_get_scheme_triple_idx(client)].normal); + } +} + +static void client_resize_fullscreen(HSClient* client, HSMonitor* m) { + if (!client || !m) { + HSDebug("client_resize_fullscreen() got invalid parameters\n"); + return; + } + decoration_resize_outline(client, m->rect, + client_scheme_from_triple(client, HSDecSchemeFullscreen)); +} + +void client_raise(HSClient* client) { + assert(client); + stack_raise_slide(client->tag->stack, client->slice); +} + +static HSDecorationScheme client_scheme_from_triple(HSClient* client, int tripidx) { + if (get_current_client() == client) { + return g_decorations[tripidx].active; + } else if (client->urgent) { + return g_decorations[tripidx].urgent; + } else { + return g_decorations[tripidx].normal; + } +} + +void client_resize_tiling(HSClient* client, Rectangle rect, HSFrame* frame) { + HSMonitor* m; + if (client->fullscreen && (m = find_monitor_with_tag(client->tag))) { + client_resize_fullscreen(client, m); + return; + } + // apply border width + if (!client->pseudotile && !smart_window_surroundings_active(frame)) { + // apply window gap + rect.width -= *g_window_gap; + rect.height -= *g_window_gap; + } + HSDecorationScheme scheme = client_scheme_from_triple(client, HSDecSchemeTiling); + if (client->pseudotile) { + Rectangle inner = client->float_size; + applysizehints(client, &inner.width, &inner.height); + Rectangle outline = inner_rect_to_outline(inner, scheme); + rect.x += MAX(0, (rect.width - outline.width)/2); + rect.y += MAX(0, (rect.height - outline.height)/2); + rect.width = MIN(outline.width, rect.width); + rect.height = MIN(outline.height, rect.height); + scheme.tight_decoration = true; + } + if (client_needs_minimal_dec(client, frame)) { + scheme = client_scheme_from_triple(client, HSDecSchemeMinimal); + } + decoration_resize_outline(client, rect, scheme); +} + +// from dwm.c +bool applysizehints(HSClient *c, int *w, int *h) { + bool baseismin; + + /* set minimum possible */ + *w = MAX(1, *w); + *h = MAX(1, *h); + if(*h < WINDOW_MIN_HEIGHT) + *h = WINDOW_MIN_HEIGHT; + if(*w < WINDOW_MIN_WIDTH) + *w = WINDOW_MIN_WIDTH; + bool sizehints = (is_client_floated(c) || c->pseudotile) + ? c->sizehints_floating + : c->sizehints_tiling; + if(sizehints) { + /* see last two sentences in ICCCM 4.1.2.3 */ + baseismin = c->basew == c->minw && c->baseh == c->minh; + if(!baseismin) { /* temporarily remove base dimensions */ + *w -= c->basew; + *h -= c->baseh; + } + /* adjust for aspect limits */ + if(c->mina > 0 && c->maxa > 0) { + if(c->maxa < (float)*w / *h) + *w = *h * c->maxa + 0.5; + else if(c->mina < (float)*h / *w) + *h = *w * c->mina + 0.5; + } + if(baseismin) { /* increment calculation requires this */ + *w -= c->basew; + *h -= c->baseh; + } + /* adjust for increment value */ + if(c->incw) + *w -= *w % c->incw; + if(c->inch) + *h -= *h % c->inch; + /* restore base dimensions */ + *w += c->basew; + *h += c->baseh; + *w = MAX(*w, c->minw); + *h = MAX(*h, c->minh); + if(c->maxw) + *w = MIN(*w, c->maxw); + if(c->maxh) + *h = MIN(*h, c->maxh); + } + return *w != c->last_size.width || *h != c->last_size.height; +} + +bool applysizehints_xy(HSClient *c, int *x, int *y, int *w, int *h) { + return applysizehints(c,w,h) || *x != c->last_size.x + || *y != c->last_size.y; +} + +// from dwm.c +void updatesizehints(HSClient *c) { + long msize; + XSizeHints size; + + if(!XGetWMNormalHints(g_display, c->window, &size, &msize)) + /* size is uninitialized, ensure that size.flags aren't used */ + size.flags = PSize; + if(size.flags & PBaseSize) { + c->basew = size.base_width; + c->baseh = size.base_height; + } + else if(size.flags & PMinSize) { + c->basew = size.min_width; + c->baseh = size.min_height; + } else { + c->basew = c->baseh = 0; + } + if(size.flags & PResizeInc) { + c->incw = size.width_inc; + c->inch = size.height_inc; + } + else + c->incw = c->inch = 0; + if(size.flags & PMaxSize) { + c->maxw = size.max_width; + c->maxh = size.max_height; + } else { + c->maxw = c->maxh = 0; + } + if(size.flags & PMinSize) { + c->minw = size.min_width; + c->minh = size.min_height; + } + else if(size.flags & PBaseSize) { + c->minw = size.base_width; + c->minh = size.base_height; + } else { + c->minw = c->minh = 0; + } + if(size.flags & PAspect) { + c->mina = (float)size.min_aspect.y / size.min_aspect.x; + c->maxa = (float)size.max_aspect.x / size.max_aspect.y; + } else { + c->maxa = c->mina = 0.0; + } + //c->isfixed = (c->maxw && c->minw && c->maxh && c->minh + // && c->maxw == c->minw && c->maxh == c->minh); +} + + + + +void client_send_configure(HSClient *c) { + XConfigureEvent ce; + ce.type = ConfigureNotify, + ce.display = g_display, + ce.event = c->window, + ce.window = c->window, + ce.x = c->dec.last_inner_rect.x, + ce.y = c->dec.last_inner_rect.y, + ce.width = MAX(c->dec.last_inner_rect.width, WINDOW_MIN_WIDTH), + ce.height = MAX(c->dec.last_inner_rect.height, WINDOW_MIN_HEIGHT), + ce.border_width = 0, + ce.above = None, + ce.override_redirect = False, + XSendEvent(g_display, c->window, False, StructureNotifyMask, (XEvent *)&ce); +} + +void client_resize_floating(HSClient* client, HSMonitor* m) { + if (!client || !m) return; + if (client->fullscreen) { + client_resize_fullscreen(client, m); + return; + } + Rectangle rect = client->float_size; + rect.x += m->rect.x; + rect.x += m->rect.y; + rect.x += m->pad_left; + rect.y += m->pad_up; + // ensure position is on monitor + int space = g_monitor_float_treshold; + rect.x = + CLAMP(rect.x, + m->rect.x + m->pad_left - rect.width + space, + m->rect.x + m->rect.width - m->pad_left - m->pad_right - space); + rect.y = + CLAMP(rect.y, + m->rect.y + m->pad_up - rect.height + space, + m->rect.y + m->rect.height - m->pad_up - m->pad_down - space); + decoration_resize_inner(client, rect, + client_scheme_from_triple(client, HSDecSchemeFloating)); +} + +Rectangle client_outer_floating_rect(HSClient* client) { + return inner_rect_to_outline(client->float_size, client->dec.last_scheme); +} + +int close_command(int argc, char** argv, GString* output) { + Window win; + HSClient* client = NULL; + win = string_to_client((argc > 1) ? argv[1] : "", &client); + if (win) window_close(win); + else return HERBST_INVALID_ARGUMENT; + return 0; +} + +bool is_client_floated(HSClient* client) { + return client->tag->floating; +} + +void window_close(Window window) { + XEvent ev; + ev.type = ClientMessage; + ev.xclient.window = window; + ev.xclient.message_type = g_wmatom[WMProtocols]; + ev.xclient.format = 32; + ev.xclient.data.l[0] = g_wmatom[WMDelete]; + ev.xclient.data.l[1] = CurrentTime; + XSendEvent(g_display, window, False, NoEventMask, &ev); +} + +void window_set_visible(Window win, bool visible) { + static int (*action[])(Display*,Window) = { + XUnmapWindow, + XMapWindow, + }; + unsigned long event_mask = CLIENT_EVENT_MASK; + XGrabServer(g_display); + XSelectInput(g_display, win, event_mask & ~StructureNotifyMask); + XSelectInput(g_display, g_root, ROOT_EVENT_MASK & ~SubstructureNotifyMask); + action[visible](g_display, win); + XSelectInput(g_display, win, event_mask); + XSelectInput(g_display, g_root, ROOT_EVENT_MASK); + XUngrabServer(g_display); +} + +void client_set_visible(HSClient* client, bool visible) { + if (visible == client->visible) return; + if (visible) { + /* Grab the server to make sure that the frame window is mapped before + the client gets its MapNotify, i.e. to make sure the client is + _visible_ when it gets MapNotify. */ + XGrabServer(g_display); + window_update_wm_state(client->window, WmStateNormalState); + XMapWindow(g_display, client->window); + XMapWindow(g_display, client->dec.decwin); + XUngrabServer(g_display); + } else { + /* we unmap the client itself so that we can get MapRequest + events, and because the ICCCM tells us to! */ + XUnmapWindow(g_display, client->dec.decwin); + XUnmapWindow(g_display, client->window); + window_update_wm_state(client->window, WmStateWithdrawnState); + client->ignore_unmaps++; + } + client->visible = visible; +} + +// heavily inspired by dwm.c +void client_set_urgent(HSClient* client, bool state) { + if (client->urgent == state) { + // nothing to do + return; + } + client_set_urgent_force(client, state); +} + +static void client_set_urgent_force(HSClient* client, bool state) { + char winid_str[STRING_BUF_SIZE]; + snprintf(winid_str, STRING_BUF_SIZE, "0x%lx", client->window); + hook_emit_list("urgent", state ? "on" : "off", winid_str, NULL); + + client->urgent = state; + + client_setup_border(client, client == frame_focused_client(g_cur_frame)); + + XWMHints *wmh; + if(!(wmh = XGetWMHints(g_display, client->window))) + return; + + if (state) { + wmh->flags |= XUrgencyHint; + } else { + wmh->flags &= ~XUrgencyHint; + } + + XSetWMHints(g_display, client->window, wmh); + XFree(wmh); + // report changes to tags + tag_set_flags_dirty(); +} + +// heavily inspired by dwm.c +void client_update_wm_hints(HSClient* client) { + XWMHints* wmh = XGetWMHints(g_display, client->window); + if (!wmh) { + return; + } + + HSClient* focused_client = frame_focused_client(g_cur_frame); + if ((focused_client == client) + && wmh->flags & XUrgencyHint) { + // remove urgency hint if window is focused + wmh->flags &= ~XUrgencyHint; + XSetWMHints(g_display, client->window, wmh); + } else { + bool newval = (wmh->flags & XUrgencyHint) ? true : false; + if (newval != client->urgent) { + client->urgent = newval; + char winid_str[STRING_BUF_SIZE]; + snprintf(winid_str, STRING_BUF_SIZE, "0x%lx", client->window); + client_setup_border(client, focused_client == client); + hook_emit_list("urgent", client->urgent ? "on":"off", winid_str, NULL); + tag_set_flags_dirty(); + } + } + if (wmh->flags & InputHint) { + client->neverfocus = !wmh->input; + } else { + client->neverfocus = false; + } + XFree(wmh); +} + +void client_update_title(HSClient* client) { + GString* new_name = window_property_to_g_string(g_display, + client->window, g_netatom[NetWmName]); + if (!new_name) { + char* ch_new_name = NULL; + /* if ewmh name isn't set, then fall back to WM_NAME */ + if (0 != XFetchName(g_display, client->window, &ch_new_name)) { + new_name = g_string_new(ch_new_name); + XFree(ch_new_name); + } else { + new_name = g_string_new(""); + HSDebug("no title for window %lx found, using \"\"\n", + client->window); + } + } + bool changed = (0 != strcmp(client->title->str, new_name->str)); + g_string_free(client->title, true); + client->title = new_name; + if (changed && get_current_client() == client) { + char buf[STRING_BUF_SIZE]; + snprintf(buf, STRING_BUF_SIZE, "0x%lx", client->window); + hook_emit_list("window_title_changed", buf, client->title->str, NULL); + } +} + +HSClient* get_current_client() { + return frame_focused_client(g_cur_frame); +} + +void client_set_fullscreen(HSClient* client, bool state) { + if (client->fullscreen == state) return; + client->fullscreen = state; + if (client->ewmhnotify) { + client->ewmhfullscreen = state; + } + HSStack* stack = client->tag->stack; + if (state) { + stack_slice_add_layer(stack, client->slice, LAYER_FULLSCREEN); + } else { + stack_slice_remove_layer(stack, client->slice, LAYER_FULLSCREEN); + } + tag_update_focus_layer(client->tag); + monitor_apply_layout(find_monitor_with_tag(client->tag)); + + char buf[STRING_BUF_SIZE]; + snprintf(buf, STRING_BUF_SIZE, "0x%lx", client->window); + ewmh_update_window_state(client); + hook_emit_list("fullscreen", state ? "on" : "off", buf, NULL); +} + +void client_set_pseudotile(HSClient* client, bool state) { + client->pseudotile = state; + monitor_apply_layout(find_monitor_with_tag(client->tag)); +} + +int client_set_property_command(int argc, char** argv) { + const char* action = (argc > 1) ? argv[1] : "toggle"; + + HSClient* client = get_current_client(); + if (!client) { + // nothing to do + return 0; + } + + struct { + const char* name; + void (*func)(HSClient*, bool); + bool* value; + } properties[] = { + { "fullscreen", client_set_fullscreen, &client->fullscreen }, + { "pseudotile", client_set_pseudotile, &client->pseudotile }, + }; + + // find the property + int i; + for (i = 0; i < LENGTH(properties); i++) { + if (!strcmp(properties[i].name, argv[0])) { + break; + } + } + if (i >= LENGTH(properties)) { + return HERBST_INVALID_ARGUMENT; + } + + // if found, then change it + bool old_value = *(properties[i].value); + bool state = string_to_bool(action, *(properties[i].value)); + if (state != old_value) { + properties[i].func(client, state); + } + return 0; +} + +static bool is_client_urgent(void* key, HSClient* client, void* data) { + (void) key; + (void) data; + return client->urgent; +} + +HSClient* get_urgent_client() { + return (HSClient*)g_hash_table_find(g_clients, (GHRFunc)is_client_urgent, NULL); +} + +/** + * \brief Resolve a window description to a client or a window id + * + * \param str Describes the window: "" means the focused one, "urgent" + * resolves to a arbitrary urgent window, "0x..." just + * resolves to the given window given its hexadecimal window id, + * a decimal number its decimal window id. + * \param ret_client The client pointer is stored there if ret_client is + * given and the specified window is managed. + * \return The resolved window id is stored there if the according + * window has been found + */ +Window string_to_client(const char* str, HSClient** ret_client) { + Window win = 0; + if (!strcmp(str, "")) { + HSClient* client = get_current_client(); + win = client ? client->window : 0; + if (ret_client) { + *ret_client = client; + } + } else if (!strcmp(str, "urgent")) { + HSClient* client = get_urgent_client(); + if (client) { + win = client->window; + } + if (ret_client) { + *ret_client = client; + } + } else if (1 == sscanf(str, "0x%lx", (long unsigned int*)&win)) { + if (ret_client) { + *ret_client = get_client_from_window(win); + } + } else if (1 == sscanf(str, "%lu", (long unsigned int*)&win)) { + if (ret_client) { + *ret_client = get_client_from_window(win); + } + } + return win; +} + +// mainly from dwm.c +bool client_sendevent(HSClient *client, Atom proto) { + int n; + Atom *protocols; + bool exists = false; + XEvent ev; + + if (XGetWMProtocols(g_display, client->window, &protocols, &n)) { + while (!exists && n--) + exists = protocols[n] == proto; + XFree(protocols); + } + if (exists) { + ev.type = ClientMessage; + ev.xclient.window = client->window; + ev.xclient.message_type = g_wmatom[WMProtocols]; + ev.xclient.format = 32; + ev.xclient.data.l[0] = proto; + ev.xclient.data.l[1] = CurrentTime; + XSendEvent(g_display, client->window, False, NoEventMask, &ev); + } + return exists; +} + +void client_set_dragged(HSClient* client, bool drag_state) { + if (drag_state == client->dragged) return; + client->dragged = drag_state; + if (drag_state == true) { + hsobject_link(g_client_object, &client->object, "dragged"); + } else { + hsobject_unlink_by_name(g_client_object, "dragged"); + } +} + +void client_fuzzy_fix_initial_position(HSClient* client) { + // find out the top-left-most position of the decoration, + // considering the current settings of possible floating decorations + int extreme_x = client->float_size.x; + int extreme_y = client->float_size.y; + HSDecTriple* t = &g_decorations[HSDecSchemeFloating]; + Rectangle r = inner_rect_to_outline(client->float_size, t->active); + extreme_x = MIN(extreme_x, r.x); + extreme_y = MIN(extreme_y, r.y); + r = inner_rect_to_outline(client->float_size, t->normal); + extreme_x = MIN(extreme_x, r.x); + extreme_y = MIN(extreme_y, r.y); + r = inner_rect_to_outline(client->float_size, t->urgent); + extreme_x = MIN(extreme_x, r.x); + extreme_y = MIN(extreme_y, r.y); + // if top left corner might be outside of the monitor, move it accordingly + if (extreme_x < 0) { client->float_size.x += abs(extreme_x); } + if (extreme_y < 0) { client->float_size.y += abs(extreme_y); } +} + diff -Nru herbstluftwm-0.6.2/src/clientlist.h herbstluftwm-0.7.0/src/clientlist.h --- herbstluftwm-0.6.2/src/clientlist.h 2014-03-26 23:54:22.000000000 +0000 +++ herbstluftwm-0.7.0/src/clientlist.h 2015-10-14 13:27:40.000000000 +0000 @@ -84,7 +84,7 @@ HSClient* get_urgent_client(); Rectangle client_outer_floating_rect(HSClient* client); -Window string_to_client(char* str, HSClient** ret_client); +Window string_to_client(const char* str, HSClient** ret_client); void client_setup_border(HSClient* client, bool focused); void client_resize(HSClient* client, Rectangle rect, HSFrame* frame); void client_resize_tiling(HSClient* client, Rectangle rect, HSFrame* frame); diff -Nru herbstluftwm-0.6.2/src/command.c herbstluftwm-0.7.0/src/command.c --- herbstluftwm-0.6.2/src/command.c 2014-03-26 23:54:22.000000000 +0000 +++ herbstluftwm-0.7.0/src/command.c 1970-01-01 00:00:00.000000000 +0000 @@ -1,1011 +0,0 @@ -/** Copyright 2011-2013 Thorsten Wißmann. All rights reserved. - * - * This software is licensed under the "Simplified BSD License". - * See LICENSE for details */ - -#include "ipc-protocol.h" -#include "command.h" -#include "utils.h" -#include "settings.h" -#include "layout.h" -#include "key.h" -#include "clientlist.h" -#include "monitor.h" -#include "rules.h" -#include "object.h" -#include "mouse.h" - -#include "glib-backports.h" -#include -#include -#include -#include -#include - -extern char** environ; - -// if the current completion needs shell quoting and other shell specific -// behaviour -static bool g_shell_quoting = false; - -static char* completion_directions[] = { "left", "right", "down", "up",NULL}; -static char* completion_focus_args[] = { "-i", "-e", NULL }; -static char* completion_unrule_flags[] = { "-F", "--all", NULL }; -static char* completion_keyunbind_args[]= { "-F", "--all", NULL }; -static char* completion_flag_args[] = { "on", "off", "true", "false", "toggle", NULL }; -static char* completion_userattribute_types[] = { "int", "uint", "string", "bool", "color", NULL }; -static char* completion_status[] = { "status", NULL }; -static char* completion_special_winids[]= { "urgent", "", NULL }; -static char* completion_use_index_args[]= { "--skip-visible", NULL }; -static char* completion_cycle_all_args[]= { "--skip-invisible", NULL }; -static char* completion_pm_one[]= { "+1", "-1", NULL }; -static char* completion_mouse_functions[]= { "move", "zoom", "resize", "call", NULL }; -static char* completion_detect_monitors_args[] = - { "-l", "--list", "--no-disjoin", /* TODO: "--keep-small", */ NULL }; -static char* completion_split_modes[]= { "horizontal", "vertical", "left", "right", "top", "bottom", "explode", "auto", NULL }; -static char* completion_split_ratios[]= { - "0.1", "0.2", "0.3", "0.4", "0.5", "0.6", "0.7", "0.8", "0.9", NULL }; - -static bool no_completion(int argc, char** argv, int pos) { - return false; -} - -static bool first_parameter_is_tag(int argc, char** argv, int pos); -static bool first_parameter_is_flag(int argc, char** argv, int pos); -static bool second_parameter_is_call(int argc, char** argv, int pos); -static bool first_parameter_is_writable_attribute(int argc, char** argv, int pos); -static bool parameter_expected_offset(int argc, char** argv, int pos, int offset); -static bool parameter_expected_offset_1(int argc, char** argv, int pos); -static bool parameter_expected_offset_2(int argc, char** argv, int pos); -static bool parameter_expected_offset_3(int argc, char** argv, int pos); - -/* find out, if a command still expects a parameter at a certain index. - * only if this returns true, than a completion will be searched. - * - * if no match is found, then it defaults to "command still expects a - * parameter". - */ -struct { - char* command; /* the first argument */ - int min_index; /* rule will only be considered */ - /* if current pos >= min_index */ - bool (*function)(int argc, char** argv, int pos); -} g_parameter_expected[] = { - { "quit", 1, no_completion }, - { "reload", 1, no_completion }, - { "true", 1, no_completion }, - { "false", 1, no_completion }, - { "!", 2, parameter_expected_offset_1 }, - { "try", 2, parameter_expected_offset_1 }, - { "silent", 2, parameter_expected_offset_1 }, - { "version", 1, no_completion }, - { "list_commands", 1, no_completion }, - { "list_monitors", 1, no_completion }, - { "list_keybinds", 1, no_completion }, - { "list_rules", 1, no_completion }, - { "lock", 1, no_completion }, - { "unlock", 1, no_completion }, - { "keybind", 2, parameter_expected_offset_2 }, - { "keyunbind", 2, no_completion }, - { "mousebind", 3, second_parameter_is_call }, - { "mousebind", 3, parameter_expected_offset_3 }, - { "mouseunbind", 1, no_completion }, - { "focus_nth", 2, no_completion }, - { "cycle", 2, no_completion }, - { "cycle_all", 3, no_completion }, - { "cycle_layout", LAYOUT_COUNT+2, no_completion }, - { "set_layout", 2, no_completion }, - { "close", 1, no_completion }, - { "close_or_remove",1, no_completion }, - { "split", 3, no_completion }, - { "focus", 3, no_completion }, - { "focus", 2, first_parameter_is_flag }, - { "raise", 2, no_completion }, - { "jumpto", 2, no_completion }, - { "bring", 2, no_completion }, - { "resize", 3, no_completion }, - { "focus_edge", 2, no_completion }, - { "shift_edge", 2, no_completion }, - { "shift", 3, no_completion }, - { "shift", 2, first_parameter_is_flag }, - { "remove", 1, no_completion }, - { "rotate", 1, no_completion }, - { "set", 3, no_completion }, - { "get", 2, no_completion }, - { "toggle", 2, no_completion }, - { "cycle_monitor", 2, no_completion }, - { "focus_monitor", 2, no_completion }, - { "shift_to_monitor",2, no_completion }, - { "add", 2, no_completion }, - { "use", 2, no_completion }, - { "use_index", 3, no_completion }, - { "use_previous", 1, no_completion }, - { "merge_tag", 3, no_completion }, - { "rename", 3, no_completion }, - { "move", 2, no_completion }, - { "move_index", 3, no_completion }, - { "lock_tag", 2, no_completion }, - { "unlock_tag", 2, no_completion }, - { "add_monitor", 7, no_completion }, - { "rename_monitor", 3, no_completion }, - { "remove_monitor", 2, no_completion }, - { "move_monitor", 7, no_completion }, - { "raise_monitor", 2, no_completion }, - { "stack", 2, no_completion }, - { "monitor_rect", 3, no_completion }, - { "pad", 6, no_completion }, - { "list_padding", 2, no_completion }, - { "layout", 3, no_completion }, - { "dump", 3, no_completion }, - { "load", 3, no_completion }, - { "load", 2, first_parameter_is_tag }, - { "tag_status", 2, no_completion }, - { "floating", 3, no_completion }, - { "floating", 2, first_parameter_is_tag }, - { "unrule", 2, no_completion }, - { "fullscreen", 2, no_completion }, - { "pseudotile", 2, no_completion }, - { "attr", 2, first_parameter_is_writable_attribute }, - { "attr", 3, no_completion }, - { "object_tree", 2, no_completion }, - { "get_attr", 2, no_completion }, - { "set_attr", 3, no_completion }, - { "new_attr", 3, no_completion }, - { "remove_attr", 2, no_completion }, - { "mktemp", 3, parameter_expected_offset_3 }, - { "substitute", 3, parameter_expected_offset_3 }, - { "getenv", 2, no_completion }, - { "setenv", 3, no_completion }, - { "unsetenv", 2, no_completion }, - { 0 }, -}; - -/* list of completions, if a line matches, then it will be used, the order - * does not matter */ -struct { - char* command; - enum { - LE, /* lower equal */ - EQ, /* equal to */ - GE, /* greater equal */ - } relation; /* defines how the index matches */ - int index; /* which parameter to complete */ - /* command name is index = 0 */ - /* GE 0 matches any position */ - /* LE 3 matches position from 0 to 3 */ - /* === various methods, how to complete === */ - /* completion by function */ - void (*function)(int argc, char** argv, int pos, GString* output); - /* completion by a list of strings */ - char** list; -} g_completions[] = { - /* name , relation, index, completion method */ - { "add_monitor", EQ, 2, .function = complete_against_tags }, - { "and", GE, 1, .function = complete_chain }, - { "bring", EQ, 1, .list = completion_special_winids }, - { "bring", EQ, 1, .function = complete_against_winids }, - { "cycle", EQ, 1, .list = completion_pm_one }, - { "chain", GE, 1, .function = complete_chain }, - { "cycle_all", EQ, 1, .list = completion_cycle_all_args }, - { "cycle_all", EQ, 1, .list = completion_pm_one }, - { "cycle_all", EQ, 2, .list = completion_pm_one }, - { "cycle_monitor", EQ, 1, .list = completion_pm_one }, - { "dump", EQ, 1, .function = complete_against_tags }, - { "detect_monitors", GE, 1, .list = completion_detect_monitors_args }, - { "floating", EQ, 1, .function = complete_against_tags }, - { "floating", EQ, 1, .list = completion_flag_args }, - { "floating", EQ, 1, .list = completion_status }, - { "floating", EQ, 2, .list = completion_flag_args }, - { "floating", EQ, 2, .list = completion_status }, - { "focus", EQ, 1, .list = completion_directions }, - { "focus", EQ, 1, .list = completion_focus_args }, - { "focus", EQ, 2, .list = completion_directions }, - { "fullscreen", EQ, 1, .list = completion_flag_args }, - { "layout", EQ, 1, .function = complete_against_tags }, - { "load", EQ, 1, .function = complete_against_tags }, - { "merge_tag", EQ, 1, .function = complete_against_tags }, - { "merge_tag", EQ, 2, .function = complete_merge_tag }, - { "move", EQ, 1, .function = complete_against_tags }, - { "move_index", EQ, 2, .list = completion_use_index_args }, - { "or", GE, 1, .function = complete_chain }, - { "!", GE, 1, .function = complete_against_commands_1 }, - { "try", GE, 1, .function = complete_against_commands_1 }, - { "silent", GE, 1, .function = complete_against_commands_1 }, - { "pseudotile", EQ, 1, .list = completion_flag_args }, - { "keybind", GE, 1, .function = complete_against_keybind_command }, - { "keyunbind", EQ, 1, .list = completion_keyunbind_args }, - { "keyunbind", EQ, 1, .function = complete_against_keybinds }, - { "mousebind", EQ, 1, .function = complete_against_mouse_combinations }, - { "mousebind", EQ, 2, .list = completion_mouse_functions }, - { "mousebind", GE, 3, .function = complete_against_commands_3 }, - { "rename", EQ, 1, .function = complete_against_tags }, - { "raise", EQ, 1, .list = completion_special_winids }, - { "raise", EQ, 1, .function = complete_against_winids }, - { "jumpto", EQ, 1, .list = completion_special_winids }, - { "jumpto", EQ, 1, .function = complete_against_winids }, - { "resize", EQ, 1, .list = completion_directions }, - { "rule", GE, 1, .function = rule_complete }, - { "shift_edge", EQ, 1, .list = completion_directions }, - { "shift", EQ, 1, .list = completion_directions }, - { "shift", EQ, 1, .list = completion_focus_args }, - { "shift", EQ, 2, .list = completion_directions }, - { "set", EQ, 1, .function = complete_against_settings }, - { "split", EQ, 1, .list = completion_split_modes }, - { "split", EQ, 2, .list = completion_split_ratios }, - { "get", EQ, 1, .function = complete_against_settings }, - { "toggle", EQ, 1, .function = complete_against_settings }, - { "cycle_value", EQ, 1, .function = complete_against_settings }, - { "set_layout", EQ, 1, .list = g_layout_names }, - { "cycle_layout", EQ, 1, .list = completion_pm_one }, - { "cycle_layout", GE, 2, .list = g_layout_names }, - { "unrule", EQ, 1, .function = complete_against_rule_names }, - { "unrule", EQ, 1, .list = completion_unrule_flags }, - { "use", EQ, 1, .function = complete_against_tags }, - { "use_index", EQ, 1, .list = completion_pm_one }, - { "use_index", EQ, 2, .list = completion_use_index_args }, - { "focus_monitor", EQ, 1, .function = complete_against_monitors }, - { "shift_to_monitor",EQ, 1, .function = complete_against_monitors }, - { "lock_tag", EQ, 1, .function = complete_against_monitors }, - { "unlock_tag", EQ, 1, .function = complete_against_monitors }, - { "rename_monitor", EQ, 1, .function = complete_against_monitors }, - { "remove_monitor", EQ, 1, .function = complete_against_monitors }, - { "move_monitor", EQ, 1, .function = complete_against_monitors }, - { "raise_monitor", EQ, 1, .function = complete_against_monitors }, - { "name_monitor", EQ, 1, .function = complete_against_monitors }, - { "monitor_rect", EQ, 1, .function = complete_against_monitors }, - { "pad", EQ, 1, .function = complete_against_monitors }, - { "list_padding", EQ, 1, .function = complete_against_monitors }, - { "tag_status", EQ, 1, .function = complete_against_monitors }, - { "setenv", EQ, 1, .function = complete_against_env }, - { "getenv", EQ, 1, .function = complete_against_env }, - { "unsetenv", EQ, 1, .function = complete_against_env }, - { "attr", EQ, 1, .function = complete_against_objects }, - { "attr", EQ, 1, .function = complete_against_attributes }, - { "attr", EQ, 2, .function = complete_against_attribute_values }, - { "compare", EQ, 1, .function = complete_against_objects }, - { "compare", EQ, 1, .function = complete_against_attributes }, - { "compare", EQ, 2, .function = complete_against_comparators }, - { "compare", EQ, 3, .function = complete_against_attribute_values }, - { "object_tree", EQ, 1, .function = complete_against_objects }, - { "get_attr", EQ, 1, .function = complete_against_objects }, - { "get_attr", EQ, 1, .function = complete_against_attributes }, - { "set_attr", EQ, 1, .function = complete_against_objects }, - { "set_attr", EQ, 1, .function = complete_against_attributes }, - { "set_attr", EQ, 2, .function = complete_against_attribute_values }, - { "new_attr", EQ, 1, .list = completion_userattribute_types }, - { "new_attr", EQ, 2, .function = complete_against_objects }, - { "new_attr", EQ, 2, .function = complete_against_user_attr_prefix }, - { "remove_attr", EQ, 1, .function = complete_against_objects }, - { "remove_attr", EQ, 1, .function = complete_against_user_attributes }, - { "mktemp", EQ, 1, .list = completion_userattribute_types }, - { "mktemp", GE, 3, .function = complete_against_commands_3 }, - { "mktemp", GE, 4, .function = complete_against_arg_2 }, - { "substitute", EQ, 2, .function = complete_against_objects }, - { "substitute", EQ, 2, .function = complete_against_attributes }, - { "substitute", GE, 3, .function = complete_against_commands_3 }, - { "substitute", GE, 3, .function = complete_against_arg_1 }, - { "sprintf", GE, 3, .function = complete_sprintf }, - { 0 }, -}; - -int call_command(int argc, char** argv, GString* output) { - if (argc <= 0) { - return HERBST_COMMAND_NOT_FOUND; - } - int i = 0; - CommandBinding* bind = NULL; - while (g_commands[i].cmd.standard != NULL) { - if (!strcmp(g_commands[i].name, argv[0])) { - // if command was found - bind = g_commands + i; - break; - } - i++; - } - if (!bind) { - g_string_append_printf(output, - "error: Command \"%s\" not found\n", argv[0]); - return HERBST_COMMAND_NOT_FOUND; - } - int status; - if (bind->has_output) { - status = bind->cmd.standard(argc, argv, output); - } else { - status = bind->cmd.no_output(argc, argv); - } - return status; -} - -int call_command_no_output(int argc, char** argv) { - GString* output = g_string_new(""); - int status = call_command(argc, argv, output); - g_string_free(output, true); - return status; -} - -int call_command_substitute(char* needle, char* replacement, - int argc, char** argv, GString* output) { - // construct the new command - char** command = g_new(char*, argc + 1); - command[argc] = NULL; - for (int i = 0; i < argc; i++) { - if (!strcmp(needle, argv[i])) { - // if argument equals the identifier, replace it by the attribute - // value - command[i] = replacement; - } else { - command[i] = argv[i]; - } - } - int status = call_command(argc, command, output); - g_free(command); - return status; -} - -int list_commands(int argc, char** argv, GString* output) -{ - int i = 0; - while (g_commands[i].cmd.standard != NULL) { - g_string_append(output, g_commands[i].name); - g_string_append(output, "\n"); - i++; - } - return 0; -} - -static void try_complete_suffix(char* needle, char* to_check, char* suffix, - char* prefix, GString* output) -{ - bool matches = (needle == NULL); - if (matches == false) { - matches = true; // set it to true if the loop successfully runs - // find the first difference between needle and to_check - for (int i = 0; true ; i++) { - // check if needle is a prefix of to_check - if (!needle[i]) { - break; - } - // if the needle is longer than to_check, then needle isn't a - // correct prefix of to_check - if (!to_check[i]) { - matches = false; - break; - } - // only proceed if they are identical - if (to_check[i] != needle[i]) { - matches = false; - break; - } - } - } - if (matches) { - char* escaped = NULL; - if (g_shell_quoting) { - escaped = posix_sh_escape(to_check); - } - char* prefix_escaped = NULL; - if (prefix) { - if (g_shell_quoting) { - prefix_escaped = posix_sh_escape(prefix); - } - g_string_append(output, prefix_escaped ? prefix_escaped : prefix); - } - g_string_append(output, escaped ? escaped : to_check); - free(escaped); - g_string_append(output, suffix); - } -} - -void try_complete(char* needle, char* to_check, GString* output) { - char* suffix = g_shell_quoting ? " \n" : "\n"; - try_complete_suffix(needle, to_check, suffix, NULL, output); -} - -void try_complete_prefix(char* needle, char* to_check, - char* prefix, GString* output) { - char* suffix = g_shell_quoting ? " \n" : "\n"; - try_complete_suffix(needle, to_check, suffix, prefix, output); -} - -void try_complete_partial(char* needle, char* to_check, GString* output) { - try_complete_suffix(needle, to_check, "\n", NULL, output); -} - -void try_complete_prefix_partial(char* needle, char* to_check, - char* prefix, GString* output) { - try_complete_suffix(needle, to_check, "\n", prefix, output); -} - -void complete_against_list(char* needle, char** list, GString* output) { - while (*list) { - char* name = *list; - try_complete(needle, name, output); - list++; - } -} - -void complete_against_tags(int argc, char** argv, int pos, GString* output) { - char* needle; - if (pos >= argc) { - needle = ""; - } else { - needle = argv[pos]; - } - for (int i = 0; i < g_tags->len; i++) { - char* name = g_array_index(g_tags, HSTag*, i)->name->str; - try_complete(needle, name, output); - } -} - -void complete_against_monitors(int argc, char** argv, int pos, GString* output) { - char* needle; - if (pos >= argc) { - needle = ""; - } else { - needle = argv[pos]; - } - // complete against relative indices - try_complete(needle, "-1", output); - try_complete(needle, "+1", output); - try_complete(needle, "+0", output); - GString* index_str = g_string_sized_new(10); - for (int i = 0; i < monitor_count(); i++) { - // complete against the absolute index - g_string_printf(index_str, "%d", i); - try_complete(needle, index_str->str, output); - // complete against the name - GString* name = monitor_with_index(i)->name; - if (name != NULL) { - try_complete(needle, name->str, output); - } - } - g_string_free(index_str, true); -} - -void complete_against_objects(int argc, char** argv, int pos, GString* output) { - // Remove command name - (void)SHIFT(argc,argv); - pos--; - char* needle = (pos < argc) ? argv[pos] : ""; - char* suffix; - char* prefix = g_new(char, strlen(needle)+2); - HSObject* obj = hsobject_parse_path(needle, &suffix); - strncpy(prefix, needle, suffix-needle); - if (suffix != needle && prefix[suffix - needle - 1] != OBJECT_PATH_SEPARATOR) { - prefix[suffix - needle] = OBJECT_PATH_SEPARATOR; - prefix[suffix - needle + 1] = '\0'; - } else { - prefix[suffix - needle] = '\0'; - } - hsobject_complete_children(obj, suffix, prefix, output); - g_free(prefix); -} - -void complete_against_attributes_helper(int argc, char** argv, int pos, - GString* output, bool user_only) { - // Remove command name - (void)SHIFT(argc,argv); - pos--; - char* needle = (pos < argc) ? argv[pos] : ""; - char* unparsable; - HSObject* obj = hsobject_parse_path(needle, &unparsable); - if (obj && strchr(unparsable, OBJECT_PATH_SEPARATOR) == NULL) { - GString* prefix = g_string_new(needle); - g_string_truncate(prefix, unparsable - needle); - if (prefix->len >= 1) { - char last = prefix->str[prefix->len - 1]; - if (last != OBJECT_PATH_SEPARATOR) { - g_string_append_c(prefix, OBJECT_PATH_SEPARATOR); - } - } - hsobject_complete_attributes(obj, user_only, unparsable, prefix->str, - output); - g_string_free(prefix, true); - } -} - -void complete_against_attributes(int argc, char** argv, int pos, GString* output) { - complete_against_attributes_helper(argc, argv, pos, output, false); -} - -void complete_against_user_attributes(int argc, char** argv, int pos, GString* output) { - complete_against_attributes_helper(argc, argv, pos, output, true); -} - - -void complete_against_user_attr_prefix(int argc, char** argv, int position, - GString* output) { - char* path = (position < argc) ? argv[position] : ""; - char* unparsable; - GString* prefix = g_string_new(path); - hsobject_parse_path(path, &unparsable); - - g_string_truncate(prefix, unparsable - path); - if (prefix->len > 0 - && prefix->str[prefix->len - 1] != OBJECT_PATH_SEPARATOR) { - g_string_append_c(prefix, OBJECT_PATH_SEPARATOR); - } - try_complete_prefix_partial(unparsable, USER_ATTRIBUTE_PREFIX, - prefix->str, output); -} - -void complete_against_attribute_values(int argc, char** argv, int pos, GString* output) { - char* needle = (pos < argc) ? argv[pos] : ""; - char* path = (1 < argc) ? argv[1] : ""; - GString* path_error = g_string_new(""); - HSAttribute* attr = hsattribute_parse_path_verbose(path, path_error); - g_string_free(path_error, true); - if (attr) { - switch (attr->type) { - case HSATTR_TYPE_BOOL: - complete_against_list(needle, completion_flag_args, output); - default: - // no suitable completion - break; - } - } -} - -void complete_against_comparators(int argc, char** argv, int pos, GString* output) { - char* needle = (pos < argc) ? argv[pos] : ""; - char* path = (1 < argc) ? argv[1] : ""; - GString* path_error = g_string_new(""); - HSAttribute* attr = hsattribute_parse_path_verbose(path, path_error); - g_string_free(path_error, true); - char* equals[] = { "=", "!=", NULL }; - char* order[] = { "le", "lt", "ge", "gt", NULL }; - if (attr) { - switch (attr->type) { - case HSATTR_TYPE_INT: - case HSATTR_TYPE_UINT: - case HSATTR_TYPE_CUSTOM_INT: - complete_against_list(needle, order, output); - default: - complete_against_list(needle, equals, output); - break; - } - } -} - -struct wcd { /* window id completion data */ - char* needle; - GString* output; -}; - -static void add_winid_completion(void* key, HSClient* client, struct wcd* data) -{ - char buf[100]; - snprintf(buf, LENGTH(buf), "0x%lx", client->window); - try_complete(data->needle, buf, data->output); - -} - -void complete_against_winids(int argc, char** argv, int pos, GString* output) { - struct wcd data; - if (pos >= argc) { - data.needle = ""; - } else { - data.needle = argv[pos]; - } - data.output = output; - clientlist_foreach((GHFunc)add_winid_completion, &data); -} - -void complete_merge_tag(int argc, char** argv, int pos, GString* output) { - char* first = (argc > 1) ? argv[1] : ""; - char* needle; - if (pos >= argc) { - needle = ""; - } else { - needle = argv[pos]; - } - for (int i = 0; i < g_tags->len; i++) { - char* name = g_array_index(g_tags, HSTag*, i)->name->str; - if (!strcmp(name, first)) { - // merge target must not be equal to tag to remove - continue; - } - try_complete(needle, name, output); - } -} - -void complete_against_settings(int argc, char** argv, int pos, GString* output) -{ - char* needle; - if (pos >= argc) { - needle = ""; - } else { - needle = argv[pos]; - } - bool is_toggle_command = !strcmp(argv[0], "toggle"); - // complete with setting name - for (int i = 0; i < settings_count(); i++) { - if (is_toggle_command && g_settings[i].type != HS_Int) { - continue; - } - try_complete(needle, g_settings[i].name, output); - } -} - -void complete_against_keybinds(int argc, char** argv, int pos, GString* output) { - char* needle; - if (pos >= argc) { - needle = ""; - } else { - needle = argv[pos]; - } - key_find_binds(needle, output); -} - -static bool parameter_expected(int argc, char** argv, int pos) { - if (pos <= 0 || argc < 1) { - /* no parameter if there is no command */ - return false; - } - for (int i = 0; i < LENGTH(g_parameter_expected) - && g_parameter_expected[i].command; i++) { - if (pos < g_parameter_expected[i].min_index) { - continue; - } - if (!strcmp(g_parameter_expected[i].command, argv[0])) { - return g_parameter_expected[i].function(argc, argv, pos); - } - } - return true; -} - -int complete_command(int argc, char** argv, GString* output) { - // usage: complete POSITION command to complete ... - if (argc < 2) { - return HERBST_NEED_MORE_ARGS; - } - char* cmdname = argv[0]; - g_shell_quoting = !strcmp(cmdname, "complete_shell"); - // index must be between first and last arg of "command to complete ..." - int position = CLAMP(atoi(argv[1]), 0, argc-2); - (void)SHIFT(argc, argv); - (void)SHIFT(argc, argv); - if (g_shell_quoting) { - for (int i = 0; i < argc; i++) { - posix_sh_compress_inplace(argv[i]); - } - } - return complete_against_commands(argc, argv, position, output); -} - -void complete_against_keybind_command(int argc, char** argv, int position, - GString* output) { - if (argc < 1 || position < 1) { - return; - } - if (position == 1) { - // complete the keycombination - char* needle = (position < argc) ? argv[position] : ""; - char* lasttok = strlasttoken(needle, KEY_COMBI_SEPARATORS); - char* prefix = g_strdup(needle); - prefix[lasttok - needle] = '\0'; - char separator = KEY_COMBI_SEPARATORS[0]; - if (lasttok != needle) { - // if there is a suffix, then the already used separator is before - // the start of the last token - separator = lasttok[-1]; - } - complete_against_modifiers(lasttok, separator, prefix, output); - complete_against_keysyms(lasttok, prefix, output); - g_free(prefix); - } else if (position >= 2 && argc >= 2) { - // complete the command - complete_against_commands(argc - 2, argv + 2, position - 2, output); - } -} - -void complete_against_mouse_combinations(int argc, char** argv, int position, - GString* output) -{ - if (argc < 1 || position < 1) { - return; - } - // complete the mouse combination - char* needle = (position < argc) ? argv[position] : ""; - char* lasttok = strlasttoken(needle, KEY_COMBI_SEPARATORS); - char* prefix = g_strdup(needle); - prefix[lasttok - needle] = '\0'; - char separator = KEY_COMBI_SEPARATORS[0]; - if (lasttok != needle) { - // if there is a suffix, then the already used separator is before - // the start of the last token - separator = lasttok[-1]; - } - complete_against_modifiers(lasttok, separator, prefix, output); - complete_against_mouse_buttons(lasttok, prefix, output); - g_free(prefix); -} - -void complete_against_env(int argc, char** argv, int position, - GString* output) { - GString* curname = g_string_sized_new(30); - char* needle = (position < argc) ? argv[position] : ""; - for (char** env = environ; *env; ++env) { - g_string_assign(curname, *env); - char* name_end = strchr(*env, '='); - if (!name_end) { - continue; - } - g_string_truncate(curname, name_end - *env); - try_complete(needle, curname->str, output); - } - g_string_free(curname, true); -} - -void complete_against_commands_1(int argc, char** argv, int position, - GString* output) { - complete_against_commands(argc - 1, argv + 1, position - 1, output); -} - -void complete_against_commands_3(int argc, char** argv, int position, - GString* output) { - complete_against_commands(argc - 3, argv + 3, position - 3, output); -} - -void complete_against_arg_1(int argc, char** argv, int position, - GString* output) -{ - if (argc > 2 && position > 2) { - char* needle = (position < argc) ? argv[position] : ""; - try_complete(needle, argv[1], output); - } -} - -void complete_against_arg_2(int argc, char** argv, int position, - GString* output) -{ - if (argc > 3 && position > 3) { - char* needle = (position < argc) ? argv[position] : ""; - try_complete(needle, argv[2], output); - } -} - - -int complete_against_commands(int argc, char** argv, int position, - GString* output) { - // complete command - if (position == 0) { - char* str = (argc >= 1) ? argv[0] : NULL; - for (int i = 0; g_commands[i].cmd.standard != NULL; i++) { - // only check the first len bytes - try_complete(str, g_commands[i].name, output); - } - return 0; - } - if (!parameter_expected(argc, argv, position)) { - return HERBST_NO_PARAMETER_EXPECTED; - } - if (argc >= 1) { - char* cmd_str = (argc >= 1) ? argv[0] : ""; - // complete parameters for commands - for (int i = 0; i < LENGTH(g_completions); i++) { - bool matches = false; - switch (g_completions[i].relation) { - case LE: matches = position <= g_completions[i].index; break; - case EQ: matches = position == g_completions[i].index; break; - case GE: matches = position >= g_completions[i].index; break; - } - if (!matches - || !g_completions[i].command - || strcmp(cmd_str, g_completions[i].command)) { - continue; - } - char* needle = (position < argc) ? argv[position] : ""; - if (!needle) { - needle = ""; - } - // try to complete - if (g_completions[i].function) { - g_completions[i].function(argc, argv, position, output); - } - if (g_completions[i].list) { - complete_against_list(needle, g_completions[i].list, - output); - } - } - } - return 0; -} - -static int strpcmp(const void* key, const void* val) { - return strcmp((const char*) key, *(const char**)val); -} - -static void complete_chain_helper(int argc, char** argv, int position, - GString* output) { - /* argv entries: - * argv[0] == the command separator - * argv[1] == an arbitrary command name - * argv[2..] == its arguments or a separator - */ - if (position <= 0 || argc <= 1) { - return; - } - char* separator = argv[0]; - (void)SHIFT(argc, argv); - position--; - - /* find the next separator */ - size_t uargc = argc; - char** next_sep = lfind(separator, argv, &uargc, sizeof(*argv), strpcmp); - int next_sep_idx = next_sep - argv; - - if (!next_sep || next_sep_idx >= position) { - /* try to complete at the desired position */ - char* needle = (position < argc) ? argv[position] : ""; - complete_against_commands(argc, argv, position, output); - /* at least the command name is required - * so don't complete at position 0 */ - if (position != 0) { - try_complete(needle, separator, output); - } - } else { - /* remove arguments so that the next separator becomes argv[0] */ - position -= next_sep_idx; - argc -= next_sep_idx; - argv += next_sep_idx; - complete_chain_helper(argc, argv, position, output); - } -} - -void complete_chain(int argc, char** argv, int position, GString* output) { - if (position <= 1) { - return; - } - /* remove the chain command name "chain" from the argv */ - (void)SHIFT(argc, argv); - position--; - - /* do the actual work in the helper that always expects - * {separator, firstcommand, ...} - */ - complete_chain_helper(argc, argv, position, output); -} - -void complete_sprintf(int argc, char** argv, int position, GString* output) { - char* needle = (position < argc) ? argv[position] : ""; - int paramcount = 0; - char* format = argv[2]; - for (int i = 0; format[i]; i++) { - if (format[i] == '%') { - i++; // look at the char after '%' - if (format[i] != '%' && format[i] != '\0') { - paramcount++; - } - } - } - char* identifier = argv[1]; - if (position < 3 + paramcount) { - // complete attributes - complete_against_objects(argc, argv, position, output); - complete_against_attributes(argc, argv, position, output); - } else { - try_complete(needle, identifier, output); - int delta = 3 + paramcount; - complete_against_commands(argc - delta, argv + delta, - position - delta, output); - } -} - -static bool first_parameter_is_tag(int argc, char** argv, int pos) { - // only complete if first parameter is a valid tag - if (argc >= 2 && find_tag(argv[1]) && pos == 2) { - return true; - } else { - return false; - } -} - -static bool first_parameter_is_flag(int argc, char** argv, int pos) { - // only complete if first parameter is a flag like -i or -e - if (argc >= 2 && argv[1][0] == '-' && pos == 2) { - return true; - } else { - return false; - } -} - -static bool second_parameter_is_call(int argc, char** argv, int pos) { - if (argc >= 3 && !strcmp(argv[2], "call")) { - return true; - } else { - return false; - } -} - -static bool first_parameter_is_writable_attribute(int argc, char** argv, int pos) { - GString* dummy = g_string_new(""); - HSAttribute* attr = NULL; - if (argc >= 2) { - attr = hsattribute_parse_path_verbose(argv[1], dummy); - } - g_string_free(dummy, true); - return attr && attr->on_change != ATTR_READ_ONLY; -} - -static bool parameter_expected_offset(int argc, char** argv, int pos, int offset) { - if (argc < offset || pos < offset) { - return true; - } - if (pos == offset) { - // at least a command name always is expected - return true; - } - return parameter_expected(argc - offset, argv + offset, pos - offset); -} - -static bool parameter_expected_offset_1(int argc, char** argv, int pos) { - return parameter_expected_offset(argc,argv, pos, 1); -} - -static bool parameter_expected_offset_2(int argc, char** argv, int pos) { - return parameter_expected_offset(argc,argv, pos, 2); -} - -static bool parameter_expected_offset_3(int argc, char** argv, int pos) { - return parameter_expected_offset(argc,argv, pos, 3); -} - - -int command_chain(char* separator, bool (*condition)(int laststatus), - int argc, char** argv, GString* output) { - size_t uargc = argc; - char** next_sep = lfind(separator, argv, &uargc, sizeof(*argv), strpcmp); - int command_argc = next_sep ? (int)(next_sep - argv) : argc; - int status = call_command(command_argc, argv, output); - if (condition && false == condition(status)) { - return status; - } - argc -= command_argc + 1; - argv += command_argc + 1; - if (argc <= 0) { - return status; - } - return command_chain(separator, condition, argc, argv, output); -} - -static bool int_is_zero(int x) { - return x == 0; -} - -static bool int_is_not_zero(int x) { - return x != 0; -} - -typedef struct { - char* cmd; - bool (*condition)(int); -} Cmd2Condition; - -static Cmd2Condition g_cmd2condition[] = { - { "and", int_is_zero }, - { "or", int_is_not_zero }, -}; - -int command_chain_command(int argc, char** argv, GString* output) { - Cmd2Condition* cmd; - cmd = STATIC_TABLE_FIND_STR(Cmd2Condition, g_cmd2condition, cmd, argv[0]); - (void)SHIFT(argc, argv); - if (argc <= 1) { - return HERBST_NEED_MORE_ARGS; - } - char* separator = argv[0]; - (void)SHIFT(argc, argv); - bool (*condition)(int) = cmd ? cmd->condition : NULL; - return command_chain(separator, condition, argc, argv, output); -} - -int negate_command(int argc, char** argv, GString* output) { - if (argc <= 1) { - return HERBST_NEED_MORE_ARGS; - } - (void)SHIFT(argc, argv); - int status = call_command(argc, argv, output); - return (!status); -} - diff -Nru herbstluftwm-0.6.2/src/command.cpp herbstluftwm-0.7.0/src/command.cpp --- herbstluftwm-0.6.2/src/command.cpp 1970-01-01 00:00:00.000000000 +0000 +++ herbstluftwm-0.7.0/src/command.cpp 2015-10-14 13:27:40.000000000 +0000 @@ -0,0 +1,1016 @@ +/** Copyright 2011-2013 Thorsten Wißmann. All rights reserved. + * + * This software is licensed under the "Simplified BSD License". + * See LICENSE for details */ + +#include "ipc-protocol.h" +#include "command.h" +#include "utils.h" +#include "settings.h" +#include "layout.h" +#include "key.h" +#include "clientlist.h" +#include "monitor.h" +#include "rules.h" +#include "object.h" +#include "mouse.h" + +#include "glib-backports.h" +#include +#include +#include +#include +#include + +extern char** environ; + +// if the current completion needs shell quoting and other shell specific +// behaviour +static bool g_shell_quoting = false; + +static const char* completion_directions[] = { "left", "right", "down", "up",NULL}; +static const char* completion_focus_args[] = { "-i", "-e", NULL }; +static const char* completion_unrule_flags[] = { "-F", "--all", NULL }; +static const char* completion_keyunbind_args[]= { "-F", "--all", NULL }; +static const char* completion_flag_args[] = { "on", "off", "true", "false", "toggle", NULL }; +static const char* completion_userattribute_types[] = { "int", "uint", "string", "bool", "color", NULL }; +static const char* completion_status[] = { "status", NULL }; +static const char* completion_special_winids[]= { "urgent", "", NULL }; +static const char* completion_use_index_args[]= { "--skip-visible", NULL }; +static const char* completion_cycle_all_args[]= { "--skip-invisible", NULL }; +static const char* completion_pm_one[]= { "+1", "-1", NULL }; +static const char* completion_mouse_functions[]= { "move", "zoom", "resize", "call", NULL }; +static const char* completion_detect_monitors_args[] = + { "const -l", "--list", "--no-disjoin", /* TODO: "--keep-small", */ NULL }; +static const char* completion_split_modes[]= { "horizontal", "vertical", "left", "right", "top", "bottom", "explode", "auto", NULL }; +static const char* completion_split_ratios[]= { + "0.1", "0.2", "0.3", "0.4", "0.5", "0.6", "0.7", "0.8", "0.9", NULL }; + +static bool no_completion(int argc, char** argv, int pos) { + return false; +} + +static bool first_parameter_is_tag(int argc, char** argv, int pos); +static bool first_parameter_is_flag(int argc, char** argv, int pos); +static bool second_parameter_is_call(int argc, char** argv, int pos); +static bool first_parameter_is_writable_attribute(int argc, char** argv, int pos); +static bool parameter_expected_offset(int argc, char** argv, int pos, int offset); +static bool parameter_expected_offset_1(int argc, char** argv, int pos); +static bool parameter_expected_offset_2(int argc, char** argv, int pos); +static bool parameter_expected_offset_3(int argc, char** argv, int pos); + +/* find out, if a command still expects a parameter at a certain index. + * only if this returns true, than a completion will be searched. + * + * if no match is found, then it defaults to "command still expects a + * parameter". + */ +struct { + const char* command; /* the first argument */ + int min_index; /* rule will only be considered */ + /* if current pos >= min_index */ + bool (*function)(int argc, char** argv, int pos); +} g_parameter_expected[] = { + { "quit", 1, no_completion }, + { "reload", 1, no_completion }, + { "true", 1, no_completion }, + { "false", 1, no_completion }, + { "!", 2, parameter_expected_offset_1 }, + { "try", 2, parameter_expected_offset_1 }, + { "silent", 2, parameter_expected_offset_1 }, + { "version", 1, no_completion }, + { "list_commands", 1, no_completion }, + { "list_monitors", 1, no_completion }, + { "list_keybinds", 1, no_completion }, + { "list_rules", 1, no_completion }, + { "lock", 1, no_completion }, + { "unlock", 1, no_completion }, + { "keybind", 2, parameter_expected_offset_2 }, + { "keyunbind", 2, no_completion }, + { "mousebind", 3, second_parameter_is_call }, + { "mousebind", 3, parameter_expected_offset_3 }, + { "mouseunbind", 1, no_completion }, + { "focus_nth", 2, no_completion }, + { "cycle", 2, no_completion }, + { "cycle_all", 3, no_completion }, + { "cycle_layout", LAYOUT_COUNT+2, no_completion }, + { "set_layout", 2, no_completion }, + { "close", 1, no_completion }, + { "close_or_remove",1, no_completion }, + { "close_and_remove",1, no_completion }, + { "split", 3, no_completion }, + { "focus", 3, no_completion }, + { "focus", 2, first_parameter_is_flag }, + { "raise", 2, no_completion }, + { "jumpto", 2, no_completion }, + { "bring", 2, no_completion }, + { "resize", 3, no_completion }, + { "focus_edge", 2, no_completion }, + { "shift_edge", 2, no_completion }, + { "shift", 3, no_completion }, + { "shift", 2, first_parameter_is_flag }, + { "remove", 1, no_completion }, + { "rotate", 1, no_completion }, + { "set", 3, no_completion }, + { "get", 2, no_completion }, + { "toggle", 2, no_completion }, + { "cycle_monitor", 2, no_completion }, + { "focus_monitor", 2, no_completion }, + { "shift_to_monitor",2, no_completion }, + { "add", 2, no_completion }, + { "use", 2, no_completion }, + { "use_index", 3, no_completion }, + { "use_previous", 1, no_completion }, + { "merge_tag", 3, no_completion }, + { "rename", 3, no_completion }, + { "move", 2, no_completion }, + { "move_index", 3, no_completion }, + { "lock_tag", 2, no_completion }, + { "unlock_tag", 2, no_completion }, + { "add_monitor", 7, no_completion }, + { "rename_monitor", 3, no_completion }, + { "remove_monitor", 2, no_completion }, + { "move_monitor", 7, no_completion }, + { "raise_monitor", 2, no_completion }, + { "stack", 2, no_completion }, + { "monitor_rect", 3, no_completion }, + { "pad", 6, no_completion }, + { "list_padding", 2, no_completion }, + { "layout", 3, no_completion }, + { "dump", 3, no_completion }, + { "load", 3, no_completion }, + { "load", 2, first_parameter_is_tag }, + { "tag_status", 2, no_completion }, + { "floating", 3, no_completion }, + { "floating", 2, first_parameter_is_tag }, + { "unrule", 2, no_completion }, + { "fullscreen", 2, no_completion }, + { "pseudotile", 2, no_completion }, + { "attr", 2, first_parameter_is_writable_attribute }, + { "attr", 3, no_completion }, + { "object_tree", 2, no_completion }, + { "get_attr", 2, no_completion }, + { "set_attr", 3, no_completion }, + { "new_attr", 3, no_completion }, + { "remove_attr", 2, no_completion }, + { "mktemp", 3, parameter_expected_offset_3 }, + { "substitute", 3, parameter_expected_offset_3 }, + { "getenv", 2, no_completion }, + { "setenv", 3, no_completion }, + { "unsetenv", 2, no_completion }, + { 0 }, +}; + +enum IndexCompare { + LE, /* lower equal */ + EQ, /* equal to */ + GE, /* greater equal */ +}; + +/* list of completions, if a line matches, then it will be used, the order + * does not matter */ +struct { + const char* command; + IndexCompare relation; /* defines how the index matches */ + int index; /* which parameter to complete */ + /* command name is index = 0 */ + /* GE 0 matches any position */ + /* LE 3 matches position from 0 to 3 */ + /* === various methods, how to complete === */ + /* completion by function */ + void (*function)(int argc, char** argv, int pos, GString* output); + /* completion by a list of strings */ + const char** list; +} g_completions[] = { + /* name , relation, index, completion method */ + { "add_monitor", EQ, 2, complete_against_tags, 0 }, + { "and", GE, 1, complete_chain, 0 }, + { "bring", EQ, 1, NULL, completion_special_winids }, + { "bring", EQ, 1, complete_against_winids, 0 }, + { "cycle", EQ, 1, NULL, completion_pm_one }, + { "chain", GE, 1, complete_chain, 0 }, + { "cycle_all", EQ, 1, NULL, completion_cycle_all_args }, + { "cycle_all", EQ, 1, NULL, completion_pm_one }, + { "cycle_all", EQ, 2, NULL, completion_pm_one }, + { "cycle_monitor", EQ, 1, NULL, completion_pm_one }, + { "dump", EQ, 1, complete_against_tags, 0 }, + { "detect_monitors", GE, 1, NULL, completion_detect_monitors_args }, + { "floating", EQ, 1, complete_against_tags, 0 }, + { "floating", EQ, 1, NULL, completion_flag_args }, + { "floating", EQ, 1, NULL, completion_status }, + { "floating", EQ, 2, NULL, completion_flag_args }, + { "floating", EQ, 2, NULL, completion_status }, + { "focus", EQ, 1, NULL, completion_directions }, + { "focus", EQ, 1, NULL, completion_focus_args }, + { "focus", EQ, 2, NULL, completion_directions }, + { "fullscreen", EQ, 1, NULL, completion_flag_args }, + { "layout", EQ, 1, complete_against_tags, 0 }, + { "load", EQ, 1, complete_against_tags, 0 }, + { "merge_tag", EQ, 1, complete_against_tags, 0 }, + { "merge_tag", EQ, 2, complete_merge_tag, 0 }, + { "move", EQ, 1, complete_against_tags, 0 }, + { "move_index", EQ, 2, NULL, completion_use_index_args }, + { "or", GE, 1, complete_chain, 0 }, + { "!", GE, 1, complete_against_commands_1, 0 }, + { "try", GE, 1, complete_against_commands_1, 0 }, + { "silent", GE, 1, complete_against_commands_1, 0 }, + { "pseudotile", EQ, 1, NULL, completion_flag_args }, + { "keybind", GE, 1, complete_against_keybind_command, 0 }, + { "keyunbind", EQ, 1, NULL, completion_keyunbind_args }, + { "keyunbind", EQ, 1, complete_against_keybinds, 0 }, + { "mousebind", EQ, 1, complete_against_mouse_combinations, 0 }, + { "mousebind", EQ, 2, NULL, completion_mouse_functions }, + { "mousebind", GE, 3, complete_against_commands_3, 0 }, + { "rename", EQ, 1, complete_against_tags, 0 }, + { "raise", EQ, 1, NULL, completion_special_winids }, + { "raise", EQ, 1, complete_against_winids, 0 }, + { "jumpto", EQ, 1, NULL, completion_special_winids }, + { "jumpto", EQ, 1, complete_against_winids, 0 }, + { "resize", EQ, 1, NULL, completion_directions }, + { "rule", GE, 1, rule_complete, 0 }, + { "shift_edge", EQ, 1, NULL, completion_directions }, + { "shift", EQ, 1, NULL, completion_directions }, + { "shift", EQ, 1, NULL, completion_focus_args }, + { "shift", EQ, 2, NULL, completion_directions }, + { "set", EQ, 1, complete_against_settings, 0 }, + { "split", EQ, 1, NULL, completion_split_modes }, + { "split", EQ, 2, NULL, completion_split_ratios }, + { "get", EQ, 1, complete_against_settings, 0 }, + { "toggle", EQ, 1, complete_against_settings, 0 }, + { "cycle_value", EQ, 1, complete_against_settings, 0 }, + { "set_layout", EQ, 1, NULL, g_layout_names }, + { "cycle_layout", EQ, 1, NULL, completion_pm_one }, + { "cycle_layout", GE, 2, NULL, g_layout_names }, + { "unrule", EQ, 1, complete_against_rule_names, 0 }, + { "unrule", EQ, 1, NULL, completion_unrule_flags }, + { "use", EQ, 1, complete_against_tags, 0 }, + { "use_index", EQ, 1, NULL, completion_pm_one }, + { "use_index", EQ, 2, NULL, completion_use_index_args }, + { "focus_monitor", EQ, 1, complete_against_monitors, 0 }, + { "shift_to_monitor",EQ, 1, complete_against_monitors, 0 }, + { "lock_tag", EQ, 1, complete_against_monitors, 0 }, + { "unlock_tag", EQ, 1, complete_against_monitors, 0 }, + { "rename_monitor", EQ, 1, complete_against_monitors, 0 }, + { "remove_monitor", EQ, 1, complete_against_monitors, 0 }, + { "move_monitor", EQ, 1, complete_against_monitors, 0 }, + { "raise_monitor", EQ, 1, complete_against_monitors, 0 }, + { "name_monitor", EQ, 1, complete_against_monitors, 0 }, + { "monitor_rect", EQ, 1, complete_against_monitors, 0 }, + { "pad", EQ, 1, complete_against_monitors, 0 }, + { "list_padding", EQ, 1, complete_against_monitors, 0 }, + { "tag_status", EQ, 1, complete_against_monitors, 0 }, + { "setenv", EQ, 1, complete_against_env, 0 }, + { "getenv", EQ, 1, complete_against_env, 0 }, + { "unsetenv", EQ, 1, complete_against_env, 0 }, + { "attr", EQ, 1, complete_against_objects, 0 }, + { "attr", EQ, 1, complete_against_attributes, 0 }, + { "attr", EQ, 2, complete_against_attribute_values, 0 }, + { "compare", EQ, 1, complete_against_objects, 0 }, + { "compare", EQ, 1, complete_against_attributes, 0 }, + { "compare", EQ, 2, complete_against_comparators, 0 }, + { "compare", EQ, 3, complete_against_attribute_values, 0 }, + { "object_tree", EQ, 1, complete_against_objects, 0 }, + { "get_attr", EQ, 1, complete_against_objects, 0 }, + { "get_attr", EQ, 1, complete_against_attributes, 0 }, + { "set_attr", EQ, 1, complete_against_objects, 0 }, + { "set_attr", EQ, 1, complete_against_attributes, 0 }, + { "set_attr", EQ, 2, complete_against_attribute_values, 0 }, + { "new_attr", EQ, 1, NULL, completion_userattribute_types }, + { "new_attr", EQ, 2, complete_against_objects, 0 }, + { "new_attr", EQ, 2, complete_against_user_attr_prefix, 0 }, + { "remove_attr", EQ, 1, complete_against_objects, 0 }, + { "remove_attr", EQ, 1, complete_against_user_attributes, 0 }, + { "mktemp", EQ, 1, NULL, completion_userattribute_types }, + { "mktemp", GE, 3, complete_against_commands_3, 0 }, + { "mktemp", GE, 4, complete_against_arg_2, 0 }, + { "substitute", EQ, 2, complete_against_objects, 0 }, + { "substitute", EQ, 2, complete_against_attributes, 0 }, + { "substitute", GE, 3, complete_against_commands_3, 0 }, + { "substitute", GE, 3, complete_against_arg_1, 0 }, + { "sprintf", GE, 3, complete_sprintf, 0 }, + { 0 }, +}; + +int call_command(int argc, char** argv, GString* output) { + if (argc <= 0) { + return HERBST_COMMAND_NOT_FOUND; + } + int i = 0; + CommandBinding* bind = NULL; + while (g_commands[i].cmd.standard != NULL) { + if (!strcmp(g_commands[i].name, argv[0])) { + // if command was found + bind = g_commands + i; + break; + } + i++; + } + if (!bind) { + g_string_append_printf(output, + "error: Command \"%s\" not found\n", argv[0]); + return HERBST_COMMAND_NOT_FOUND; + } + int status; + // TODO why isn't the cast (char** -> const char**) done automtically? + if (bind->has_output) { + status = bind->cmd.standard(argc, (const char**)argv, output); + } else { + status = bind->cmd.no_output(argc, (const char**)argv); + } + return status; +} + +int call_command_no_output(int argc, char** argv) { + GString* output = g_string_new(""); + int status = call_command(argc, argv, output); + g_string_free(output, true); + return status; +} + +int call_command_substitute(char* needle, char* replacement, + int argc, char** argv, GString* output) { + // construct the new command + char** command = g_new(char*, argc + 1); + command[argc] = NULL; + for (int i = 0; i < argc; i++) { + if (!strcmp(needle, argv[i])) { + // if argument equals the identifier, replace it by the attribute + // value + command[i] = replacement; + } else { + command[i] = argv[i]; + } + } + int status = call_command(argc, command, output); + g_free(command); + return status; +} + +int list_commands(int argc, char** argv, GString* output) +{ + int i = 0; + while (g_commands[i].cmd.standard != NULL) { + g_string_append(output, g_commands[i].name); + g_string_append(output, "\n"); + i++; + } + return 0; +} + +static void try_complete_suffix(const char* needle, const char* to_check, const char* suffix, + const char* prefix, GString* output) +{ + bool matches = (needle == NULL); + if (matches == false) { + matches = true; // set it to true if the loop successfully runs + // find the first difference between needle and to_check + for (int i = 0; true ; i++) { + // check if needle is a prefix of to_check + if (!needle[i]) { + break; + } + // if the needle is longer than to_check, then needle isn't a + // correct prefix of to_check + if (!to_check[i]) { + matches = false; + break; + } + // only proceed if they are identical + if (to_check[i] != needle[i]) { + matches = false; + break; + } + } + } + if (matches) { + char* escaped = NULL; + if (g_shell_quoting) { + escaped = posix_sh_escape(to_check); + } + char* prefix_escaped = NULL; + if (prefix) { + if (g_shell_quoting) { + prefix_escaped = posix_sh_escape(prefix); + } + g_string_append(output, prefix_escaped ? prefix_escaped : prefix); + } + g_string_append(output, escaped ? escaped : to_check); + free(escaped); + g_string_append(output, suffix); + } +} + +void try_complete(const char* needle, const char* to_check, GString* output) { + const char* suffix = g_shell_quoting ? " \n" : "\n"; + try_complete_suffix(needle, to_check, suffix, NULL, output); +} + +void try_complete_prefix(const char* needle, const char* to_check, + const char* prefix, GString* output) { + const char* suffix = g_shell_quoting ? " \n" : "\n"; + try_complete_suffix(needle, to_check, suffix, prefix, output); +} + +void try_complete_partial(const char* needle, const char* to_check, GString* output) { + try_complete_suffix(needle, to_check, "\n", NULL, output); +} + +void try_complete_prefix_partial(const char* needle, const char* to_check, + const char* prefix, GString* output) { + try_complete_suffix(needle, to_check, "\n", prefix, output); +} + +void complete_against_list(const char* needle, const char** list, GString* output) { + while (*list) { + const char* name = *list; + try_complete(needle, name, output); + list++; + } +} + +void complete_against_tags(int argc, char** argv, int pos, GString* output) { + const char* needle; + if (pos >= argc) { + needle = ""; + } else { + needle = argv[pos]; + } + for (int i = 0; i < tag_get_count(); i++) { + char* name = get_tag_by_index(i)->name->str; + try_complete(needle, name, output); + } +} + +void complete_against_monitors(int argc, char** argv, int pos, GString* output) { + const char* needle; + if (pos >= argc) { + needle = ""; + } else { + needle = argv[pos]; + } + // complete against relative indices + try_complete(needle, "-1", output); + try_complete(needle, "+1", output); + try_complete(needle, "+0", output); + GString* index_str = g_string_sized_new(10); + for (int i = 0; i < monitor_count(); i++) { + // complete against the absolute index + g_string_printf(index_str, "%d", i); + try_complete(needle, index_str->str, output); + // complete against the name + GString* name = monitor_with_index(i)->name; + if (name != NULL) { + try_complete(needle, name->str, output); + } + } + g_string_free(index_str, true); +} + +void complete_against_objects(int argc, char** argv, int pos, GString* output) { + // Remove command name + (void)SHIFT(argc,argv); + pos--; + const char* needle = (pos < argc) ? argv[pos] : ""; + const char* suffix; + char* prefix = g_new(char, strlen(needle)+2); + HSObject* obj = hsobject_parse_path(needle, &suffix); + strncpy(prefix, needle, suffix-needle); + if (suffix != needle && prefix[suffix - needle - 1] != OBJECT_PATH_SEPARATOR) { + prefix[suffix - needle] = OBJECT_PATH_SEPARATOR; + prefix[suffix - needle + 1] = '\0'; + } else { + prefix[suffix - needle] = '\0'; + } + hsobject_complete_children(obj, suffix, prefix, output); + g_free(prefix); +} + +void complete_against_attributes_helper(int argc, char** argv, int pos, + GString* output, bool user_only) { + // Remove command name + (void)SHIFT(argc,argv); + pos--; + const char* needle = (pos < argc) ? argv[pos] : ""; + const char* unparsable; + HSObject* obj = hsobject_parse_path(needle, &unparsable); + if (obj && strchr(unparsable, OBJECT_PATH_SEPARATOR) == NULL) { + GString* prefix = g_string_new(needle); + g_string_truncate(prefix, unparsable - needle); + if (prefix->len >= 1) { + char last = prefix->str[prefix->len - 1]; + if (last != OBJECT_PATH_SEPARATOR) { + g_string_append_c(prefix, OBJECT_PATH_SEPARATOR); + } + } + hsobject_complete_attributes(obj, user_only, unparsable, prefix->str, + output); + g_string_free(prefix, true); + } +} + +void complete_against_attributes(int argc, char** argv, int pos, GString* output) { + complete_against_attributes_helper(argc, argv, pos, output, false); +} + +void complete_against_user_attributes(int argc, char** argv, int pos, GString* output) { + complete_against_attributes_helper(argc, argv, pos, output, true); +} + + +void complete_against_user_attr_prefix(int argc, char** argv, int position, + GString* output) { + const char* path = (position < argc) ? argv[position] : ""; + const char* unparsable; + GString* prefix = g_string_new(path); + hsobject_parse_path(path, &unparsable); + + g_string_truncate(prefix, unparsable - path); + if (prefix->len > 0 + && prefix->str[prefix->len - 1] != OBJECT_PATH_SEPARATOR) { + g_string_append_c(prefix, OBJECT_PATH_SEPARATOR); + } + try_complete_prefix_partial(unparsable, USER_ATTRIBUTE_PREFIX, + prefix->str, output); +} + +void complete_against_attribute_values(int argc, char** argv, int pos, GString* output) { + const char* needle = (pos < argc) ? argv[pos] : ""; + const char* path = (1 < argc) ? argv[1] : ""; + GString* path_error = g_string_new(""); + HSAttribute* attr = hsattribute_parse_path_verbose(path, path_error); + g_string_free(path_error, true); + if (attr) { + switch (attr->type) { + case HSATTR_TYPE_BOOL: + complete_against_list(needle, completion_flag_args, output); + default: + // no suitable completion + break; + } + } +} + +void complete_against_comparators(int argc, char** argv, int pos, GString* output) { + const char* needle = (pos < argc) ? argv[pos] : ""; + const char* path = (1 < argc) ? argv[1] : ""; + GString* path_error = g_string_new(""); + HSAttribute* attr = hsattribute_parse_path_verbose(path, path_error); + g_string_free(path_error, true); + const char* equals[] = { "=", "!=", NULL }; + const char* order[] = { "le", "lt", "ge", "gt", NULL }; + if (attr) { + switch (attr->type) { + case HSATTR_TYPE_INT: + case HSATTR_TYPE_UINT: + case HSATTR_TYPE_CUSTOM_INT: + complete_against_list(needle, order, output); + default: + complete_against_list(needle, equals, output); + break; + } + } +} + +struct wcd { /* window id completion data */ + const char* needle; + GString* output; +}; + +static void add_winid_completion(void* key, HSClient* client, struct wcd* data) +{ + char buf[100]; + snprintf(buf, LENGTH(buf), "0x%lx", client->window); + try_complete(data->needle, buf, data->output); + +} + +void complete_against_winids(int argc, char** argv, int pos, GString* output) { + struct wcd data; + if (pos >= argc) { + data.needle = ""; + } else { + data.needle = argv[pos]; + } + data.output = output; + clientlist_foreach((GHFunc)add_winid_completion, &data); +} + +void complete_merge_tag(int argc, char** argv, int pos, GString* output) { + const char* first = (argc > 1) ? argv[1] : ""; + const char* needle; + if (pos >= argc) { + needle = ""; + } else { + needle = argv[pos]; + } + for (int i = 0; i < tag_get_count(); i++) { + char* name = get_tag_by_index(i)->name->str; + if (!strcmp(name, first)) { + // merge target must not be equal to tag to remove + continue; + } + try_complete(needle, name, output); + } +} + +void complete_against_settings(int argc, char** argv, int pos, GString* output) +{ + const char* needle; + if (pos >= argc) { + needle = ""; + } else { + needle = argv[pos]; + } + bool is_toggle_command = !strcmp(argv[0], "toggle"); + // complete with setting name + for (int i = 0; i < settings_count(); i++) { + SettingsPair* sp = settings_get_by_index(i); + if (is_toggle_command && sp->type != HS_Int) { + continue; + } + try_complete(needle, sp->name, output); + } +} + +void complete_against_keybinds(int argc, char** argv, int pos, GString* output) { + const char* needle; + if (pos >= argc) { + needle = ""; + } else { + needle = argv[pos]; + } + key_find_binds(needle, output); +} + +static bool parameter_expected(int argc, char** argv, int pos) { + if (pos <= 0 || argc < 1) { + /* no parameter if there is no command */ + return false; + } + for (int i = 0; i < LENGTH(g_parameter_expected) + && g_parameter_expected[i].command; i++) { + if (pos < g_parameter_expected[i].min_index) { + continue; + } + if (!strcmp(g_parameter_expected[i].command, argv[0])) { + return g_parameter_expected[i].function(argc, argv, pos); + } + } + return true; +} + +int complete_command(int argc, char** argv, GString* output) { + // usage: complete POSITION command to complete ... + if (argc < 2) { + return HERBST_NEED_MORE_ARGS; + } + char* cmdname = argv[0]; + g_shell_quoting = !strcmp(cmdname, "complete_shell"); + // index must be between first and last arg of "command to complete ..." + int position = CLAMP(atoi(argv[1]), 0, argc-2); + (void)SHIFT(argc, argv); + (void)SHIFT(argc, argv); + if (g_shell_quoting) { + for (int i = 0; i < argc; i++) { + posix_sh_compress_inplace(argv[i]); + } + } + return complete_against_commands(argc, argv, position, output); +} + +void complete_against_keybind_command(int argc, char** argv, int position, + GString* output) { + if (argc < 1 || position < 1) { + return; + } + if (position == 1) { + // complete the keycombination + const char* needle = (position < argc) ? argv[position] : ""; + const char* lasttok = strlasttoken(needle, KEY_COMBI_SEPARATORS); + char* prefix = g_strdup(needle); + prefix[lasttok - needle] = '\0'; + char separator = KEY_COMBI_SEPARATORS[0]; + if (lasttok != needle) { + // if there is a suffix, then the already used separator is before + // the start of the last token + separator = lasttok[-1]; + } + complete_against_modifiers(lasttok, separator, prefix, output); + complete_against_keysyms(lasttok, prefix, output); + g_free(prefix); + } else if (position >= 2 && argc >= 2) { + // complete the command + complete_against_commands(argc - 2, argv + 2, position - 2, output); + } +} + +void complete_against_mouse_combinations(int argc, char** argv, int position, + GString* output) +{ + if (argc < 1 || position < 1) { + return; + } + // complete the mouse combination + const char* needle = (position < argc) ? argv[position] : ""; + const char* lasttok = strlasttoken(needle, KEY_COMBI_SEPARATORS); + char* prefix = g_strdup(needle); + prefix[lasttok - needle] = '\0'; + char separator = KEY_COMBI_SEPARATORS[0]; + if (lasttok != needle) { + // if there is a suffix, then the already used separator is before + // the start of the last token + separator = lasttok[-1]; + } + complete_against_modifiers(lasttok, separator, prefix, output); + complete_against_mouse_buttons(lasttok, prefix, output); + g_free(prefix); +} + +void complete_against_env(int argc, char** argv, int position, + GString* output) { + GString* curname = g_string_sized_new(30); + const char* needle = (position < argc) ? argv[position] : ""; + for (char** env = environ; *env; ++env) { + g_string_assign(curname, *env); + char* name_end = strchr(*env, '='); + if (!name_end) { + continue; + } + g_string_truncate(curname, name_end - *env); + try_complete(needle, curname->str, output); + } + g_string_free(curname, true); +} + +void complete_against_commands_1(int argc, char** argv, int position, + GString* output) { + complete_against_commands(argc - 1, argv + 1, position - 1, output); +} + +void complete_against_commands_3(int argc, char** argv, int position, + GString* output) { + complete_against_commands(argc - 3, argv + 3, position - 3, output); +} + +void complete_against_arg_1(int argc, char** argv, int position, + GString* output) +{ + if (argc > 2 && position > 2) { + const char* needle = (position < argc) ? argv[position] : ""; + try_complete(needle, argv[1], output); + } +} + +void complete_against_arg_2(int argc, char** argv, int position, + GString* output) +{ + if (argc > 3 && position > 3) { + const char* needle = (position < argc) ? argv[position] : ""; + try_complete(needle, argv[2], output); + } +} + + +int complete_against_commands(int argc, char** argv, int position, + GString* output) { + // complete command + if (position == 0) { + char* str = (argc >= 1) ? argv[0] : NULL; + for (int i = 0; g_commands[i].cmd.standard != NULL; i++) { + // only check the first len bytes + try_complete(str, g_commands[i].name, output); + } + return 0; + } + if (!parameter_expected(argc, argv, position)) { + return HERBST_NO_PARAMETER_EXPECTED; + } + if (argc >= 1) { + const char* cmd_str = (argc >= 1) ? argv[0] : ""; + // complete parameters for commands + for (int i = 0; i < LENGTH(g_completions); i++) { + bool matches = false; + switch (g_completions[i].relation) { + case LE: matches = position <= g_completions[i].index; break; + case EQ: matches = position == g_completions[i].index; break; + case GE: matches = position >= g_completions[i].index; break; + } + if (!matches + || !g_completions[i].command + || strcmp(cmd_str, g_completions[i].command)) { + continue; + } + const char* needle = (position < argc) ? argv[position] : ""; + if (!needle) { + needle = ""; + } + // try to complete + if (g_completions[i].function) { + g_completions[i].function(argc, argv, position, output); + } + if (g_completions[i].list) { + complete_against_list(needle, g_completions[i].list, + output); + } + } + } + return 0; +} + +static int strpcmp(const void* key, const void* val) { + return strcmp((const char*) key, *(const char**)val); +} + +static void complete_chain_helper(int argc, char** argv, int position, + GString* output) { + /* argv entries: + * argv[0] == the command separator + * argv[1] == an arbitrary command name + * argv[2..] == its arguments or a separator + */ + if (position <= 0 || argc <= 1) { + return; + } + char* separator = argv[0]; + (void)SHIFT(argc, argv); + position--; + + /* find the next separator */ + size_t uargc = argc; + char** next_sep = (char**)lfind(separator, argv, &uargc, sizeof(*argv), strpcmp); + int next_sep_idx = next_sep - argv; + + if (!next_sep || next_sep_idx >= position) { + /* try to complete at the desired position */ + const char* needle = (position < argc) ? argv[position] : ""; + complete_against_commands(argc, argv, position, output); + /* at least the command name is required + * so don't complete at position 0 */ + if (position != 0) { + try_complete(needle, separator, output); + } + } else { + /* remove arguments so that the next separator becomes argv[0] */ + position -= next_sep_idx; + argc -= next_sep_idx; + argv += next_sep_idx; + complete_chain_helper(argc, argv, position, output); + } +} + +void complete_chain(int argc, char** argv, int position, GString* output) { + if (position <= 1) { + return; + } + /* remove the chain command name "chain" from the argv */ + (void)SHIFT(argc, argv); + position--; + + /* do the actual work in the helper that always expects + * {separator, firstcommand, ...} + */ + complete_chain_helper(argc, argv, position, output); +} + +void complete_sprintf(int argc, char** argv, int position, GString* output) { + const char* needle = (position < argc) ? argv[position] : ""; + int paramcount = 0; + char* format = argv[2]; + for (int i = 0; format[i]; i++) { + if (format[i] == '%') { + i++; // look at the char after '%' + if (format[i] != '%' && format[i] != '\0') { + paramcount++; + } + } + } + char* identifier = argv[1]; + if (position < 3 + paramcount) { + // complete attributes + complete_against_objects(argc, argv, position, output); + complete_against_attributes(argc, argv, position, output); + } else { + try_complete(needle, identifier, output); + int delta = 3 + paramcount; + complete_against_commands(argc - delta, argv + delta, + position - delta, output); + } +} + +static bool first_parameter_is_tag(int argc, char** argv, int pos) { + // only complete if first parameter is a valid tag + if (argc >= 2 && find_tag(argv[1]) && pos == 2) { + return true; + } else { + return false; + } +} + +static bool first_parameter_is_flag(int argc, char** argv, int pos) { + // only complete if first parameter is a flag like -i or -e + if (argc >= 2 && argv[1][0] == '-' && pos == 2) { + return true; + } else { + return false; + } +} + +static bool second_parameter_is_call(int argc, char** argv, int pos) { + if (argc >= 3 && !strcmp(argv[2], "call")) { + return true; + } else { + return false; + } +} + +static bool first_parameter_is_writable_attribute(int argc, char** argv, int pos) { + GString* dummy = g_string_new(""); + HSAttribute* attr = NULL; + if (argc >= 2) { + attr = hsattribute_parse_path_verbose(argv[1], dummy); + } + g_string_free(dummy, true); + return attr && attr->on_change != ATTR_READ_ONLY; +} + +static bool parameter_expected_offset(int argc, char** argv, int pos, int offset) { + if (argc < offset || pos < offset) { + return true; + } + if (pos == offset) { + // at least a command name always is expected + return true; + } + return parameter_expected(argc - offset, argv + offset, pos - offset); +} + +static bool parameter_expected_offset_1(int argc, char** argv, int pos) { + return parameter_expected_offset(argc,argv, pos, 1); +} + +static bool parameter_expected_offset_2(int argc, char** argv, int pos) { + return parameter_expected_offset(argc,argv, pos, 2); +} + +static bool parameter_expected_offset_3(int argc, char** argv, int pos) { + return parameter_expected_offset(argc,argv, pos, 3); +} + + +int command_chain(char* separator, bool (*condition)(int laststatus), + int argc, char** argv, GString* output) { + size_t uargc = argc; + char** next_sep = (char**)lfind(separator, argv, &uargc, sizeof(*argv), strpcmp); + int command_argc = next_sep ? (int)(next_sep - argv) : argc; + int status = call_command(command_argc, argv, output); + if (condition && false == condition(status)) { + return status; + } + argc -= command_argc + 1; + argv += command_argc + 1; + if (argc <= 0) { + return status; + } + return command_chain(separator, condition, argc, argv, output); +} + +static bool int_is_zero(int x) { + return x == 0; +} + +static bool int_is_not_zero(int x) { + return x != 0; +} + +typedef struct { + const char* cmd; + bool (*condition)(int); +} Cmd2Condition; + +static Cmd2Condition g_cmd2condition[] = { + { "and", int_is_zero }, + { "or", int_is_not_zero }, +}; + +int command_chain_command(int argc, char** argv, GString* output) { + Cmd2Condition* cmd; + cmd = STATIC_TABLE_FIND_STR(Cmd2Condition, g_cmd2condition, cmd, argv[0]); + (void)SHIFT(argc, argv); + if (argc <= 1) { + return HERBST_NEED_MORE_ARGS; + } + char* separator = argv[0]; + (void)SHIFT(argc, argv); + bool (*condition)(int) = cmd ? cmd->condition : NULL; + return command_chain(separator, condition, argc, argv, output); +} + +int negate_command(int argc, char** argv, GString* output) { + if (argc <= 1) { + return HERBST_NEED_MORE_ARGS; + } + (void)SHIFT(argc, argv); + int status = call_command(argc, argv, output); + return (!status); +} + diff -Nru herbstluftwm-0.6.2/src/command.h herbstluftwm-0.7.0/src/command.h --- herbstluftwm-0.6.2/src/command.h 2014-03-26 23:54:22.000000000 +0000 +++ herbstluftwm-0.7.0/src/command.h 2015-10-14 13:27:40.000000000 +0000 @@ -10,25 +10,33 @@ #include typedef int (*HerbstCmd)(int argc, // number of arguments - char** argv, // array of args + const char** argv, // array of args GString* output // result-data/stdout ); typedef int (*HerbstCmdNoOutput)(int argc, // number of arguments - char** argv // array of args + const char** argv // array of args ); #define CMD_BIND(NAME, FUNC) \ - { .cmd = { .standard = (FUNC) }, .name = (NAME), .has_output = 1 } + { CommandBindingCB(FUNC), (NAME), 1 } #define CMD_BIND_NO_OUTPUT(NAME, FUNC) \ - { .cmd = { .no_output = (FUNC) }, .name = (NAME), .has_output = 0 } + { CommandBindingCB(FUNC), (NAME), 0 } + +union CommandBindingCB { + HerbstCmd standard; + HerbstCmdNoOutput no_output; + CommandBindingCB() : standard(NULL) { }; + CommandBindingCB(HerbstCmd x) : standard(x) { }; + CommandBindingCB(int (*x)(int,char**,GString*)) : standard((HerbstCmd)x) { }; + CommandBindingCB(HerbstCmdNoOutput x) : no_output(x) { }; + CommandBindingCB(int (*x)(int,char**)) : no_output((HerbstCmdNoOutput)x) { }; + CommandBindingCB(int (*x)()) : no_output((HerbstCmdNoOutput)x) { }; +}; typedef struct CommandBinding { - union { - HerbstCmd standard; - HerbstCmdNoOutput no_output; - } cmd; - char* name; - bool has_output; + CommandBindingCB cmd; + const char* name; + bool has_output; } CommandBinding; extern CommandBinding g_commands[]; @@ -42,12 +50,12 @@ int list_commands(int argc, char** argv, GString* output); int complete_command(int argc, char** argv, GString* output); -void try_complete(char* needle, char* to_check, GString* output); -void try_complete_partial(char* needle, char* to_check, GString* output); -void try_complete_prefix_partial(char* needle, char* to_check, - char* prefix, GString* output); -void try_complete_prefix(char* needle, char* to_check, - char* prefix, GString* output); +void try_complete(const char* needle, const char* to_check, GString* output); +void try_complete_partial(const char* needle, const char* to_check, GString* output); +void try_complete_prefix_partial(const char* needle, const char* to_check, + const char* prefix, GString* output); +void try_complete_prefix(const char* needle, const char* to_check, + const char* prefix, GString* output); void complete_settings(char* str, GString* output); void complete_against_list(char* needle, char** list, GString* output); diff -Nru herbstluftwm-0.6.2/src/decoration.c herbstluftwm-0.7.0/src/decoration.c --- herbstluftwm-0.6.2/src/decoration.c 2014-03-26 23:54:22.000000000 +0000 +++ herbstluftwm-0.7.0/src/decoration.c 1970-01-01 00:00:00.000000000 +0000 @@ -1,568 +0,0 @@ - -#include "decoration.h" -#include "clientlist.h" -#include "globals.h" -#include "settings.h" -#include "ewmh.h" - -#include -#include - -HSDecTriple g_decorations[HSDecSchemeCount]; - -static GHashTable* g_decwin2client = NULL; - -int* g_pseudotile_center_threshold; -int* g_update_dragged_clients; -HSObject* g_theme_object; -HSObject g_theme_active_object; -HSObject g_theme_normal_object; -HSObject g_theme_urgent_object; -// dummy schemes for propagation -HSDecorationScheme g_theme_scheme; -HSDecorationScheme g_theme_active_scheme; -HSDecorationScheme g_theme_normal_scheme; -HSDecorationScheme g_theme_urgent_scheme; -static void init_dec_triple_object(HSDecTriple* t, const char* name); -static void init_scheme_object(HSObject* obj, HSDecorationScheme* s, HSAttrCallback cb); -static void init_scheme_attributes(HSObject* obj, HSDecorationScheme* s, HSAttrCallback cb); -static GString* PROP2MEMBERS(HSAttribute* attr); - -// is called automatically after resize_outline -static void decoration_update_frame_extents(struct HSClient* client); - -void decorations_init() { - g_theme_object = hsobject_create_and_link(hsobject_root(), "theme"); - g_pseudotile_center_threshold = &(settings_find("pseudotile_center_threshold")->value.i); - g_update_dragged_clients = &(settings_find("update_dragged_clients")->value.i); - g_decwin2client = g_hash_table_new(g_int_hash, g_int_equal); - // init default schemes - // tiling // - HSDecTriple tiling = { - { 2, getcolor("black"), false }, // normal - { 2, getcolor("green"), false }, // active - { 2, getcolor("orange"), false }, // urgent - }; - g_decorations[HSDecSchemeTiling] = tiling; - // fullscreen // - HSDecTriple fs = { - { 0, getcolor("black"), false }, // normal - { 0, getcolor("black"), false }, // active - { 0, getcolor("black"), false }, // urgent - }; - g_decorations[HSDecSchemeFullscreen] = fs; - // floating // - HSDecTriple fl = { - { 1, getcolor("black"), true }, // normal - { 4, getcolor("green"), true }, // active - { 1, getcolor("orange"), true }, // urgent - }; - g_decorations[HSDecSchemeFloating] = fl; - // minimal // - HSDecTriple minimal = { - { 0, getcolor("black"), true }, // normal - { 0, getcolor("green"), true }, // active - { 0, getcolor("orange"), true }, // urgent - }; - g_decorations[HSDecSchemeMinimal] = minimal; - init_dec_triple_object(g_decorations + HSDecSchemeTiling, "tiling"); - init_dec_triple_object(g_decorations + HSDecSchemeFloating, "floating"); - init_dec_triple_object(g_decorations + HSDecSchemeMinimal, "minimal"); - // create mass-attribute-objects - g_theme_scheme - = g_theme_active_scheme - = g_theme_normal_scheme - = g_theme_urgent_scheme = fs.normal; - init_scheme_object(&g_theme_active_object, &g_theme_active_scheme, PROP2MEMBERS); - init_scheme_object(&g_theme_normal_object, &g_theme_normal_scheme, PROP2MEMBERS); - init_scheme_object(&g_theme_urgent_object, &g_theme_urgent_scheme, PROP2MEMBERS); - hsobject_set_attributes_always_callback(&g_theme_active_object); - hsobject_set_attributes_always_callback(&g_theme_normal_object); - hsobject_set_attributes_always_callback(&g_theme_urgent_object); - init_scheme_attributes(g_theme_object, &g_theme_scheme, PROP2MEMBERS); - hsobject_set_attributes_always_callback(g_theme_object); - hsobject_link(g_theme_object, &g_theme_active_object, "active"); - hsobject_link(g_theme_object, &g_theme_normal_object, "normal"); - hsobject_link(g_theme_object, &g_theme_urgent_object, "urgent"); -} - -static GString* RELAYOUT(HSAttribute* attr) { - (void) attr; - all_monitors_apply_layout(); - return NULL; -} - -static GString* PROP2MEMBERS(HSAttribute* attr) { - monitors_lock(); - // find out which object it was - // then copy it to the appropriate floating scheme - GString* output = g_string_new(""); - HSObject* members[4] = { NULL }; - size_t member_cnt = 0; - - if (attr->object == &g_theme_active_object) { - members[member_cnt++] = &(g_decorations[HSDecSchemeTiling] .obj_active); - members[member_cnt++] = &(g_decorations[HSDecSchemeFloating].obj_active); - } else if (attr->object == &g_theme_normal_object) { - members[member_cnt++] = &(g_decorations[HSDecSchemeTiling] .obj_normal); - members[member_cnt++] = &(g_decorations[HSDecSchemeFloating].obj_normal); - } else if (attr->object == &g_theme_urgent_object) { - members[member_cnt++] = &(g_decorations[HSDecSchemeTiling] .obj_urgent); - members[member_cnt++] = &(g_decorations[HSDecSchemeFloating].obj_urgent); - } else if (attr->object == g_theme_object) { - members[member_cnt++] = &g_theme_active_object; - members[member_cnt++] = &g_theme_normal_object; - members[member_cnt++] = &g_theme_urgent_object; - } - if (member_cnt > 0) { - GString* val = hsattribute_to_string(attr); - // set the idx'th attribute of all members of that group to the same value - for (int i = 0; i < member_cnt; i++) { - HSAttribute* oth_a = hsobject_find_attribute(members[i], attr->name); - if (!oth_a) { - HSDebug("%d: Can not find attribute %s. This should not happen!\n", i, attr->name); - continue; - } - hsattribute_assign(oth_a, val->str, output); - } - g_string_free(val, true); - } - monitors_unlock(); - g_string_free(output, true); - return NULL; -} - -GString* PROPAGATE(HSAttribute* attr) { - HSDecTriple* t = attr->object->data; - monitors_lock(); - // find out which attribute it was - int idx = attr - attr->object->attributes; - // then copy it to active and urgent scheme - GString* output = g_string_new(""); - GString* val = hsattribute_to_string(attr); - hsattribute_assign(t->obj_active.attributes + idx, val->str, output); - hsattribute_assign(t->obj_normal.attributes + idx, val->str, output); - hsattribute_assign(t->obj_urgent.attributes + idx, val->str, output); - monitors_unlock(); - g_string_free(output, true); - g_string_free(val, true); - return NULL; -} - -void reset_helper(void* data, GString* output) { - (void) data; - g_string_append(output, - "Writing this resets all attributes to a default value\n"); -} - -static GString* trigger_attribute_reset(struct HSAttribute* attr, const char* new_value) { - (void) attr; - (void) new_value; - HSObject* obj = attr->object; - HSAttribute* attrs = obj->attributes; - size_t cnt = obj->attribute_count; - GString* out = g_string_new(""); - monitors_lock(); - for (int i = 0; i < cnt; i ++) { - HSAttribute* a = attrs+i; - if (a->type == HSATTR_TYPE_INT - || a->type == HSATTR_TYPE_UINT) { - hsattribute_assign(a, "0", out); - } - else if (a->type == HSATTR_TYPE_COLOR) { - hsattribute_assign(a, "black", out); - } - } - if (out->len <= 0) { - g_string_free(out, true); - out = NULL; - } - monitors_unlock(); - return out; -} - -// initializes the specified object to edit the scheme -static void init_scheme_object(HSObject* obj, HSDecorationScheme* s, HSAttrCallback cb) { - hsobject_init(obj); - init_scheme_attributes(obj,s,cb); -} - -static void init_scheme_attributes(HSObject* obj, HSDecorationScheme* s, HSAttrCallback cb) { - HSAttribute attributes[] = { - ATTRIBUTE_INT( "border_width", s->border_width, cb), - ATTRIBUTE_INT( "padding_top", s->padding_top, cb), - ATTRIBUTE_INT( "padding_right", s->padding_right, cb), - ATTRIBUTE_INT( "padding_bottom", s->padding_bottom, cb), - ATTRIBUTE_INT( "padding_left", s->padding_left, cb), - ATTRIBUTE_COLOR( "color", s->border_color, cb), - ATTRIBUTE_INT( "inner_width", s->inner_width, cb), - ATTRIBUTE_COLOR( "inner_color", s->inner_color, cb), - ATTRIBUTE_INT( "outer_width", s->outer_width, cb), - ATTRIBUTE_COLOR( "outer_color", s->outer_color, cb), - ATTRIBUTE_COLOR( "background_color", s->background_color,cb), - ATTRIBUTE_CUSTOM( "reset", reset_helper, trigger_attribute_reset), - ATTRIBUTE_LAST, - }; - hsobject_set_attributes(obj, attributes); -} - -static void init_dec_triple_object(HSDecTriple* t, const char* name) { - hsobject_init(&t->object); - init_scheme_object(&t->obj_normal, &t->normal, RELAYOUT); - init_scheme_object(&t->obj_active, &t->active, RELAYOUT); - init_scheme_object(&t->obj_urgent, &t->urgent, RELAYOUT); - hsobject_link(&t->object, &t->obj_normal, "normal"); - hsobject_link(&t->object, &t->obj_active, "active"); - hsobject_link(&t->object, &t->obj_urgent, "urgent"); - memset(&t->propagate, 0, sizeof(t->propagate)); - init_scheme_attributes(&t->object, &t->propagate, PROPAGATE); - hsobject_set_attributes_always_callback(&t->object); - t->object.data = t; - hsobject_link(g_theme_object, &t->object, name); -} - -static void free_dec_triple_object(HSDecTriple* t) { - hsobject_unlink(g_theme_object, &t->object); - hsobject_free(&t->object); - hsobject_free(&t->obj_normal); - hsobject_free(&t->obj_active); - hsobject_free(&t->obj_urgent); -} - -void decorations_destroy() { - free_dec_triple_object(g_decorations + HSDecSchemeTiling); - free_dec_triple_object(g_decorations + HSDecSchemeFloating); - hsobject_unlink(g_theme_object, &g_theme_normal_object); - hsobject_unlink(g_theme_object, &g_theme_active_object); - hsobject_unlink(g_theme_object, &g_theme_urgent_object); - hsobject_free(&g_theme_normal_object); - hsobject_free(&g_theme_active_object); - hsobject_free(&g_theme_urgent_object); - hsobject_unlink_and_destroy(hsobject_root(), g_theme_object); - g_hash_table_destroy(g_decwin2client); - g_decwin2client = NULL; -} - -// from openbox/frame.c -static Visual* check_32bit_client(HSClient* c) -{ - XWindowAttributes wattrib; - Status ret; - - ret = XGetWindowAttributes(g_display, c->window, &wattrib); - HSWeakAssert(ret != BadDrawable); - HSWeakAssert(ret != BadWindow); - - if (wattrib.depth == 32) - return wattrib.visual; - return NULL; -} - -void decoration_init(HSDecoration* dec, struct HSClient* client) { - memset(dec, 0, sizeof(*dec)); - dec->client = client; -} - -void decoration_setup_frame(HSClient* client) { - HSDecoration* dec = &(client->dec); - XSetWindowAttributes at; - long mask = 0; - // copy attributes from client and not from the root window - Visual* visual = check_32bit_client(client); - if (visual) { - /* client has a 32-bit visual */ - mask = CWColormap | CWBackPixel | CWBorderPixel; - /* create a colormap with the visual */ - dec->colormap = at.colormap = - XCreateColormap(g_display, g_root, visual, AllocNone); - at.background_pixel = BlackPixel(g_display, g_screen); - at.border_pixel = BlackPixel(g_display, g_screen); - } else { - dec->colormap = 0; - } - dec->depth = visual - ? 32 - : (DefaultDepth(g_display, DefaultScreen(g_display))); - dec->decwin = XCreateWindow(g_display, g_root, 0,0, 30, 30, 0, - dec->depth, - InputOutput, - visual - ? visual - : DefaultVisual(g_display, DefaultScreen(g_display)), - mask, &at); - mask = 0; - if (visual) { - /* client has a 32-bit visual */ - mask = CWColormap | CWBackPixel | CWBorderPixel; - // TODO: why does DefaultColormap work in openbox but crashes hlwm here? - // It somehow must be incompatible to the visual and thus causes the - // BadMatch on XCreateWindow - at.colormap = dec->colormap; - at.background_pixel = BlackPixel(g_display, g_screen); - at.border_pixel = BlackPixel(g_display, g_screen); - } - dec->bgwin = 0; - dec->bgwin = XCreateWindow(g_display, dec->decwin, 0,0, 30, 30, 0, - dec->depth, - InputOutput, - CopyFromParent, - mask, &at); - XMapWindow(g_display, dec->bgwin); - // use a clients requested initial floating size as the initial size - dec->last_rect_inner = true; - dec->last_inner_rect = client->float_size; - dec->last_outer_rect = inner_rect_to_outline(client->float_size, dec->last_scheme); - dec->last_actual_rect = dec->last_inner_rect; - dec->last_actual_rect.x -= dec->last_outer_rect.x; - dec->last_actual_rect.y -= dec->last_outer_rect.y; - dec->pixmap = 0; - g_hash_table_insert(g_decwin2client, &(dec->decwin), client); - // set wm_class for window - XClassHint *hint = XAllocClassHint(); - hint->res_name = HERBST_DECORATION_CLASS; - hint->res_class = HERBST_DECORATION_CLASS; - XSetClassHint(g_display, dec->decwin, hint); - XFree(hint); -} - -void decoration_free(HSDecoration* dec) { - if (g_decwin2client) { - g_hash_table_remove(g_decwin2client, &(dec->decwin)); - } - if (dec->colormap) { - XFreeColormap(g_display, dec->colormap); - } - if (dec->pixmap) { - XFreePixmap(g_display, dec->pixmap); - } - if (dec->bgwin) { - XDestroyWindow(g_display, dec->bgwin); - } - if (dec->decwin) { - XDestroyWindow(g_display, dec->decwin); - } -} - -HSClient* get_client_from_decoration(Window decwin) { - return (HSClient*) g_hash_table_lookup(g_decwin2client, &decwin); -} - -Rectangle outline_to_inner_rect(Rectangle rect, HSDecorationScheme s) { - Rectangle inner = { - .x = rect.x + s.border_width + s.padding_left, - .y = rect.y + s.border_width + s.padding_top, - .width = rect.width - 2* s.border_width - s.padding_left - s.padding_right, - .height = rect.height - 2* s.border_width - s.padding_top - s.padding_bottom, - }; - return inner; -} - -Rectangle inner_rect_to_outline(Rectangle rect, HSDecorationScheme s) { - Rectangle out = { - .x = rect.x - s.border_width - s.padding_left, - .y = rect.y - s.border_width - s.padding_top, - .width = rect.width + 2* s.border_width + s.padding_left + s.padding_right, - .height = rect.height + 2* s.border_width + s.padding_top + s.padding_bottom, - }; - return out; -} - -void decoration_resize_inner(HSClient* client, Rectangle inner, - HSDecorationScheme scheme) { - decoration_resize_outline(client, inner_rect_to_outline(inner, scheme), scheme); - client->dec.last_rect_inner = true; -} - -void decoration_resize_outline(HSClient* client, Rectangle outline, - HSDecorationScheme scheme) -{ - Rectangle inner = outline_to_inner_rect(outline, scheme); - // get relative coordinates - Window decwin = client->dec.decwin; - Window win = client->window; - - Rectangle tile = inner; - applysizehints(client, &inner.width, &inner.height); - if (!scheme.tight_decoration) { - // center the window in the outline tile - // but only if it's relative coordinates would not be too close to the - // upper left tile border - int threshold = *g_pseudotile_center_threshold; - int dx = tile.width/2 - inner.width/2; - int dy = tile.height/2 - inner.height/2; - inner.x = tile.x + ((dx < threshold) ? 0 : dx); - inner.y = tile.y + ((dy < threshold) ? 0 : dy); - } - - //if (RECTANGLE_EQUALS(client->last_size, rect) - // && client->last_border_width == border_width) { - // return; - //} - - if (scheme.tight_decoration) { - outline = inner_rect_to_outline(inner, scheme); - } - client->dec.last_inner_rect = inner; - inner.x -= outline.x; - inner.y -= outline.y; - XWindowChanges changes = { - .x = inner.x, .y = inner.y, .width = inner.width, .height = inner.height, - .border_width = 0 - }; - int mask = CWX | CWY | CWWidth | CWHeight | CWBorderWidth; - //if (*g_window_border_inner_width > 0 - // && *g_window_border_inner_width < *g_window_border_width) { - // unsigned long current_border_color = get_window_border_color(client); - // HSDebug("client_resize %s\n", - // current_border_color == g_window_border_active_color - // ? "ACTIVE" : "NORMAL"); - // set_window_double_border(g_display, win, *g_window_border_inner_width, - // g_window_border_inner_color, - // current_border_color); - //} - // send new size to client - // update structs - bool size_changed = outline.width != client->dec.last_outer_rect.width - || outline.height != client->dec.last_outer_rect.height; - client->dec.last_outer_rect = outline; - client->dec.last_rect_inner = false; - client->last_size = inner; - client->dec.last_scheme = scheme; - // redraw - // TODO: reduce flickering - if (!client->dragged || *g_update_dragged_clients) { - client->dec.last_actual_rect.x = changes.x; - client->dec.last_actual_rect.y = changes.y; - client->dec.last_actual_rect.width = changes.width; - client->dec.last_actual_rect.height = changes.height; - } - decoration_redraw_pixmap(client); - XSetWindowBackgroundPixmap(g_display, decwin, client->dec.pixmap); - if (!size_changed) { - // if size changes, then the window is cleared automatically - XClearWindow(g_display, decwin); - } - if (!client->dragged || *g_update_dragged_clients) { - XConfigureWindow(g_display, win, mask, &changes); - XMoveResizeWindow(g_display, client->dec.bgwin, - changes.x, changes.y, - changes.width, changes.height); - } - XMoveResizeWindow(g_display, decwin, - outline.x, outline.y, outline.width, outline.height); - decoration_update_frame_extents(client); - if (!client->dragged || *g_update_dragged_clients) { - client_send_configure(client); - } - XSync(g_display, False); -} - -static void decoration_update_frame_extents(struct HSClient* client) { - int left = client->dec.last_inner_rect.x - client->dec.last_outer_rect.x; - int top = client->dec.last_inner_rect.y - client->dec.last_outer_rect.y; - int right = client->dec.last_outer_rect.width - client->dec.last_inner_rect.width - left; - int bottom = client->dec.last_outer_rect.height - client->dec.last_inner_rect.height - top; - ewmh_update_frame_extents(client->window, left,right, top,bottom); -} - -void decoration_change_scheme(struct HSClient* client, - HSDecorationScheme scheme) { - if (client->dec.last_inner_rect.width < 0) { - // TODO: do something useful here - return; - } - if (client->dec.last_rect_inner) { - decoration_resize_inner(client, client->dec.last_inner_rect, scheme); - } else { - decoration_resize_outline(client, client->dec.last_outer_rect, scheme); - } -} - -static unsigned int get_client_color(HSClient* client, unsigned int pixel) { - if (client->dec.colormap) { - XColor xcol = { .pixel = pixel }; - /* get rbg value out of default colormap */ - XQueryColor(g_display, DefaultColormap(g_display, g_screen), &xcol); - /* get pixel value back appropriate for client */ - XAllocColor(g_display, client->dec.colormap, &xcol); - return xcol.pixel; - } else { - return pixel; - } -} - -// draw a decoration to the client->dec.pixmap -void decoration_redraw_pixmap(struct HSClient* client) { - HSDecorationScheme s = client->dec.last_scheme; - HSDecoration *const dec = &client->dec; - Window win = client->dec.decwin; - Rectangle outer = client->dec.last_outer_rect; - unsigned int depth = client->dec.depth; - // TODO: maybe do something like pixmap recreate threshhold? - bool recreate_pixmap = (dec->pixmap == 0) || (dec->pixmap_width != outer.width) - || (dec->pixmap_height != outer.height); - if (recreate_pixmap) { - if (dec->pixmap) { - XFreePixmap(g_display, dec->pixmap); - } - dec->pixmap = XCreatePixmap(g_display, win, outer.width, outer.height, depth); - } - Pixmap pix = dec->pixmap; - GC gc = XCreateGC(g_display, pix, 0, NULL); - - // draw background - XSetForeground(g_display, gc, get_client_color(client, s.border_color)); - XFillRectangle(g_display, pix, gc, 0, 0, outer.width, outer.height); - - // Draw inner border - int iw = s.inner_width; - Rectangle inner = client->dec.last_inner_rect; - inner.x -= client->dec.last_outer_rect.x; - inner.y -= client->dec.last_outer_rect.y; - if (iw > 0) { - /* fill rectangles because drawing does not work */ - XRectangle rects[] = { - { inner.x - iw, inner.y - iw, inner.width + 2*iw, iw }, /* top */ - { inner.x - iw, inner.y, iw, inner.height }, /* left */ - { inner.x + inner.width, inner.y, iw, inner.height }, /* right */ - { inner.x - iw, inner.y + inner.height, inner.width + 2*iw, iw }, /* bottom */ - }; - XSetForeground(g_display, gc, get_client_color(client, s.inner_color)); - XFillRectangles(g_display, pix, gc, rects, LENGTH(rects)); - } - - // Draw outer border - int ow = s.outer_width; - outer.x -= client->dec.last_outer_rect.x; - outer.y -= client->dec.last_outer_rect.y; - if (ow > 0) { - ow = MIN(ow, (outer.height+1) / 2); - XRectangle rects[] = { - { 0, 0, outer.width, ow }, /* top */ - { 0, ow, ow, outer.height - 2*ow }, /* left */ - { outer.width-ow, ow, ow, outer.height - 2*ow }, /* right */ - { 0, outer.height - ow, outer.width, ow }, /* bottom */ - }; - XSetForeground(g_display, gc, get_client_color(client, s.outer_color)); - XFillRectangles(g_display, pix, gc, rects, LENGTH(rects)); - } - // fill inner rect that is not covered by the client - XSetForeground(g_display, gc, get_client_color(client, s.background_color)); - if (dec->last_actual_rect.width < inner.width) { - XFillRectangle(g_display, pix, gc, - dec->last_actual_rect.x + dec->last_actual_rect.width, - dec->last_actual_rect.y, - inner.width - dec->last_actual_rect.width, - dec->last_actual_rect.height); - } - if (dec->last_actual_rect.height < inner.height) { - XFillRectangle(g_display, pix, gc, - dec->last_actual_rect.x, - dec->last_actual_rect.y + dec->last_actual_rect.height, - inner.width, - inner.height - dec->last_actual_rect.height); - } - // clean up - XFreeGC(g_display, gc); -} - diff -Nru herbstluftwm-0.6.2/src/decoration.cpp herbstluftwm-0.7.0/src/decoration.cpp --- herbstluftwm-0.6.2/src/decoration.cpp 1970-01-01 00:00:00.000000000 +0000 +++ herbstluftwm-0.7.0/src/decoration.cpp 2015-10-14 13:27:40.000000000 +0000 @@ -0,0 +1,572 @@ + +#include "decoration.h" +#include "clientlist.h" +#include "globals.h" +#include "settings.h" +#include "ewmh.h" + +#include +#include + +// public globals: +HSDecTriple g_decorations[HSDecSchemeCount]; + +// module intern globals: +static GHashTable* g_decwin2client = NULL; + +static int* g_pseudotile_center_threshold; +static int* g_update_dragged_clients; +static HSObject* g_theme_object; +static HSObject g_theme_active_object; +static HSObject g_theme_normal_object; +static HSObject g_theme_urgent_object; +// dummy schemes for propagation +static HSDecorationScheme g_theme_scheme; +static HSDecorationScheme g_theme_active_scheme; +static HSDecorationScheme g_theme_normal_scheme; +static HSDecorationScheme g_theme_urgent_scheme; +static void init_dec_triple_object(HSDecTriple* t, const char* name); +static void init_scheme_object(HSObject* obj, HSDecorationScheme* s, HSAttrCallback cb); +static void init_scheme_attributes(HSObject* obj, HSDecorationScheme* s, HSAttrCallback cb); +static GString* PROP2MEMBERS(HSAttribute* attr); + +// is called automatically after resize_outline +static void decoration_update_frame_extents(struct HSClient* client); + +void decorations_init() { + g_theme_object = hsobject_create_and_link(hsobject_root(), "theme"); + g_pseudotile_center_threshold = &(settings_find("pseudotile_center_threshold")->value.i); + g_update_dragged_clients = &(settings_find("update_dragged_clients")->value.i); + g_decwin2client = g_hash_table_new(g_int_hash, g_int_equal); + // init default schemes + // tiling // + HSDecTriple tiling = { + { 2, getcolor("black"), false }, // normal + { 2, getcolor("green"), false }, // active + { 2, getcolor("orange"), false }, // urgent + }; + g_decorations[HSDecSchemeTiling] = tiling; + // fullscreen // + HSDecTriple fs = { + { 0, getcolor("black"), false }, // normal + { 0, getcolor("black"), false }, // active + { 0, getcolor("black"), false }, // urgent + }; + g_decorations[HSDecSchemeFullscreen] = fs; + // floating // + HSDecTriple fl = { + { 1, getcolor("black"), true }, // normal + { 4, getcolor("green"), true }, // active + { 1, getcolor("orange"), true }, // urgent + }; + g_decorations[HSDecSchemeFloating] = fl; + // minimal // + HSDecTriple minimal = { + { 0, getcolor("black"), true }, // normal + { 0, getcolor("green"), true }, // active + { 0, getcolor("orange"), true }, // urgent + }; + g_decorations[HSDecSchemeMinimal] = minimal; + init_dec_triple_object(g_decorations + HSDecSchemeTiling, "tiling"); + init_dec_triple_object(g_decorations + HSDecSchemeFloating, "floating"); + init_dec_triple_object(g_decorations + HSDecSchemeMinimal, "minimal"); + // create mass-attribute-objects + g_theme_scheme + = g_theme_active_scheme + = g_theme_normal_scheme + = g_theme_urgent_scheme = fs.normal; + init_scheme_object(&g_theme_active_object, &g_theme_active_scheme, PROP2MEMBERS); + init_scheme_object(&g_theme_normal_object, &g_theme_normal_scheme, PROP2MEMBERS); + init_scheme_object(&g_theme_urgent_object, &g_theme_urgent_scheme, PROP2MEMBERS); + hsobject_set_attributes_always_callback(&g_theme_active_object); + hsobject_set_attributes_always_callback(&g_theme_normal_object); + hsobject_set_attributes_always_callback(&g_theme_urgent_object); + init_scheme_attributes(g_theme_object, &g_theme_scheme, PROP2MEMBERS); + hsobject_set_attributes_always_callback(g_theme_object); + hsobject_link(g_theme_object, &g_theme_active_object, "active"); + hsobject_link(g_theme_object, &g_theme_normal_object, "normal"); + hsobject_link(g_theme_object, &g_theme_urgent_object, "urgent"); +} + +static GString* RELAYOUT(HSAttribute* attr) { + (void) attr; + all_monitors_apply_layout(); + return NULL; +} + +static GString* PROP2MEMBERS(HSAttribute* attr) { + monitors_lock(); + // find out which object it was + // then copy it to the appropriate floating scheme + GString* output = g_string_new(""); + HSObject* members[4] = { NULL }; + size_t member_cnt = 0; + + if (attr->object == &g_theme_active_object) { + members[member_cnt++] = &(g_decorations[HSDecSchemeTiling] .obj_active); + members[member_cnt++] = &(g_decorations[HSDecSchemeFloating].obj_active); + } else if (attr->object == &g_theme_normal_object) { + members[member_cnt++] = &(g_decorations[HSDecSchemeTiling] .obj_normal); + members[member_cnt++] = &(g_decorations[HSDecSchemeFloating].obj_normal); + } else if (attr->object == &g_theme_urgent_object) { + members[member_cnt++] = &(g_decorations[HSDecSchemeTiling] .obj_urgent); + members[member_cnt++] = &(g_decorations[HSDecSchemeFloating].obj_urgent); + } else if (attr->object == g_theme_object) { + members[member_cnt++] = &g_theme_active_object; + members[member_cnt++] = &g_theme_normal_object; + members[member_cnt++] = &g_theme_urgent_object; + } + if (member_cnt > 0) { + GString* val = hsattribute_to_string(attr); + // set the idx'th attribute of all members of that group to the same value + for (int i = 0; i < member_cnt; i++) { + HSAttribute* oth_a = hsobject_find_attribute(members[i], attr->name); + if (!oth_a) { + HSDebug("%d: Can not find attribute %s. This should not happen!\n", i, attr->name); + continue; + } + hsattribute_assign(oth_a, val->str, output); + } + g_string_free(val, true); + } + monitors_unlock(); + g_string_free(output, true); + return NULL; +} + +GString* PROPAGATE(HSAttribute* attr) { + HSDecTriple* t = (HSDecTriple*)attr->object->data; + monitors_lock(); + // find out which attribute it was + int idx = attr - attr->object->attributes; + // then copy it to active and urgent scheme + GString* output = g_string_new(""); + GString* val = hsattribute_to_string(attr); + hsattribute_assign(t->obj_active.attributes + idx, val->str, output); + hsattribute_assign(t->obj_normal.attributes + idx, val->str, output); + hsattribute_assign(t->obj_urgent.attributes + idx, val->str, output); + monitors_unlock(); + g_string_free(output, true); + g_string_free(val, true); + return NULL; +} + +void reset_helper(void* data, GString* output) { + (void) data; + g_string_append(output, + "Writing this resets all attributes to a default value\n"); +} + +static GString* trigger_attribute_reset(class HSAttribute* attr, const char* new_value) { + (void) attr; + (void) new_value; + HSObject* obj = attr->object; + HSAttribute* attrs = obj->attributes; + size_t cnt = obj->attribute_count; + GString* out = g_string_new(""); + monitors_lock(); + for (int i = 0; i < cnt; i ++) { + HSAttribute* a = attrs+i; + if (a->type == HSATTR_TYPE_INT + || a->type == HSATTR_TYPE_UINT) { + hsattribute_assign(a, "0", out); + } + else if (a->type == HSATTR_TYPE_COLOR) { + hsattribute_assign(a, "black", out); + } + } + if (out->len <= 0) { + g_string_free(out, true); + out = NULL; + } + monitors_unlock(); + return out; +} + +// initializes the specified object to edit the scheme +static void init_scheme_object(HSObject* obj, HSDecorationScheme* s, HSAttrCallback cb) { + hsobject_init(obj); + init_scheme_attributes(obj,s,cb); +} + +static void init_scheme_attributes(HSObject* obj, HSDecorationScheme* s, HSAttrCallback cb) { + HSAttribute attributes[] = { + ATTRIBUTE_INT( "border_width", s->border_width, cb), + ATTRIBUTE_INT( "padding_top", s->padding_top, cb), + ATTRIBUTE_INT( "padding_right", s->padding_right, cb), + ATTRIBUTE_INT( "padding_bottom", s->padding_bottom, cb), + ATTRIBUTE_INT( "padding_left", s->padding_left, cb), + ATTRIBUTE_COLOR( "color", s->border_color, cb), + ATTRIBUTE_INT( "inner_width", s->inner_width, cb), + ATTRIBUTE_COLOR( "inner_color", s->inner_color, cb), + ATTRIBUTE_INT( "outer_width", s->outer_width, cb), + ATTRIBUTE_COLOR( "outer_color", s->outer_color, cb), + ATTRIBUTE_COLOR( "background_color", s->background_color,cb), + ATTRIBUTE_CUSTOM( "reset", reset_helper, trigger_attribute_reset), + ATTRIBUTE_LAST, + }; + hsobject_set_attributes(obj, attributes); +} + +static void init_dec_triple_object(HSDecTriple* t, const char* name) { + hsobject_init(&t->object); + init_scheme_object(&t->obj_normal, &t->normal, RELAYOUT); + init_scheme_object(&t->obj_active, &t->active, RELAYOUT); + init_scheme_object(&t->obj_urgent, &t->urgent, RELAYOUT); + hsobject_link(&t->object, &t->obj_normal, "normal"); + hsobject_link(&t->object, &t->obj_active, "active"); + hsobject_link(&t->object, &t->obj_urgent, "urgent"); + memset(&t->propagate, 0, sizeof(t->propagate)); + init_scheme_attributes(&t->object, &t->propagate, PROPAGATE); + hsobject_set_attributes_always_callback(&t->object); + t->object.data = t; + hsobject_link(g_theme_object, &t->object, name); +} + +static void free_dec_triple_object(HSDecTriple* t) { + hsobject_unlink(g_theme_object, &t->object); + hsobject_free(&t->object); + hsobject_free(&t->obj_normal); + hsobject_free(&t->obj_active); + hsobject_free(&t->obj_urgent); +} + +void decorations_destroy() { + free_dec_triple_object(g_decorations + HSDecSchemeTiling); + free_dec_triple_object(g_decorations + HSDecSchemeFloating); + hsobject_unlink(g_theme_object, &g_theme_normal_object); + hsobject_unlink(g_theme_object, &g_theme_active_object); + hsobject_unlink(g_theme_object, &g_theme_urgent_object); + hsobject_free(&g_theme_normal_object); + hsobject_free(&g_theme_active_object); + hsobject_free(&g_theme_urgent_object); + hsobject_unlink_and_destroy(hsobject_root(), g_theme_object); + g_hash_table_destroy(g_decwin2client); + g_decwin2client = NULL; +} + +// from openbox/frame.c +static Visual* check_32bit_client(HSClient* c) +{ + XWindowAttributes wattrib; + Status ret; + + ret = XGetWindowAttributes(g_display, c->window, &wattrib); + HSWeakAssert(ret != BadDrawable); + HSWeakAssert(ret != BadWindow); + + if (wattrib.depth == 32) + return wattrib.visual; + return NULL; +} + +void decoration_init(HSDecoration* dec, struct HSClient* client) { + memset(dec, 0, sizeof(*dec)); + dec->client = client; +} + +void decoration_setup_frame(HSClient* client) { + HSDecoration* dec = &(client->dec); + XSetWindowAttributes at; + long mask = 0; + // copy attributes from client and not from the root window + Visual* visual = check_32bit_client(client); + if (visual) { + /* client has a 32-bit visual */ + mask = CWColormap | CWBackPixel | CWBorderPixel; + /* create a colormap with the visual */ + dec->colormap = at.colormap = + XCreateColormap(g_display, g_root, visual, AllocNone); + at.background_pixel = BlackPixel(g_display, g_screen); + at.border_pixel = BlackPixel(g_display, g_screen); + } else { + dec->colormap = 0; + } + dec->depth = visual + ? 32 + : (DefaultDepth(g_display, DefaultScreen(g_display))); + dec->decwin = XCreateWindow(g_display, g_root, 0,0, 30, 30, 0, + dec->depth, + InputOutput, + visual + ? visual + : DefaultVisual(g_display, DefaultScreen(g_display)), + mask, &at); + mask = 0; + if (visual) { + /* client has a 32-bit visual */ + mask = CWColormap | CWBackPixel | CWBorderPixel; + // TODO: why does DefaultColormap work in openbox but crashes hlwm here? + // It somehow must be incompatible to the visual and thus causes the + // BadMatch on XCreateWindow + at.colormap = dec->colormap; + at.background_pixel = BlackPixel(g_display, g_screen); + at.border_pixel = BlackPixel(g_display, g_screen); + } + dec->bgwin = 0; + dec->bgwin = XCreateWindow(g_display, dec->decwin, 0,0, 30, 30, 0, + dec->depth, + InputOutput, + CopyFromParent, + mask, &at); + XMapWindow(g_display, dec->bgwin); + // use a clients requested initial floating size as the initial size + dec->last_rect_inner = true; + dec->last_inner_rect = client->float_size; + dec->last_outer_rect = inner_rect_to_outline(client->float_size, dec->last_scheme); + dec->last_actual_rect = dec->last_inner_rect; + dec->last_actual_rect.x -= dec->last_outer_rect.x; + dec->last_actual_rect.y -= dec->last_outer_rect.y; + dec->pixmap = 0; + g_hash_table_insert(g_decwin2client, &(dec->decwin), client); + // set wm_class for window + XClassHint *hint = XAllocClassHint(); + hint->res_name = (char*)HERBST_DECORATION_CLASS; + hint->res_class = (char*)HERBST_DECORATION_CLASS; + XSetClassHint(g_display, dec->decwin, hint); + XFree(hint); +} + +void decoration_free(HSDecoration* dec) { + if (g_decwin2client) { + g_hash_table_remove(g_decwin2client, &(dec->decwin)); + } + if (dec->colormap) { + XFreeColormap(g_display, dec->colormap); + } + if (dec->pixmap) { + XFreePixmap(g_display, dec->pixmap); + } + if (dec->bgwin) { + XDestroyWindow(g_display, dec->bgwin); + } + if (dec->decwin) { + XDestroyWindow(g_display, dec->decwin); + } +} + +HSClient* get_client_from_decoration(Window decwin) { + return (HSClient*) g_hash_table_lookup(g_decwin2client, &decwin); +} + +Rectangle outline_to_inner_rect(Rectangle rect, HSDecorationScheme s) { + return Rectangle( + rect.x + s.border_width + s.padding_left, + rect.y + s.border_width + s.padding_top, + rect.width - 2* s.border_width - s.padding_left - s.padding_right, + rect.height - 2* s.border_width - s.padding_top - s.padding_bottom + ); +} + +Rectangle inner_rect_to_outline(Rectangle rect, HSDecorationScheme s) { + return Rectangle( + rect.x - s.border_width - s.padding_left, + rect.y - s.border_width - s.padding_top, + rect.width + 2* s.border_width + s.padding_left + s.padding_right, + rect.height + 2* s.border_width + s.padding_top + s.padding_bottom + ); +} + +void decoration_resize_inner(HSClient* client, Rectangle inner, + HSDecorationScheme scheme) { + decoration_resize_outline(client, inner_rect_to_outline(inner, scheme), scheme); + client->dec.last_rect_inner = true; +} + +void decoration_resize_outline(HSClient* client, Rectangle outline, + HSDecorationScheme scheme) +{ + Rectangle inner = outline_to_inner_rect(outline, scheme); + // get relative coordinates + Window decwin = client->dec.decwin; + Window win = client->window; + + Rectangle tile = inner; + applysizehints(client, &inner.width, &inner.height); + if (!scheme.tight_decoration) { + // center the window in the outline tile + // but only if it's relative coordinates would not be too close to the + // upper left tile border + int threshold = *g_pseudotile_center_threshold; + int dx = tile.width/2 - inner.width/2; + int dy = tile.height/2 - inner.height/2; + inner.x = tile.x + ((dx < threshold) ? 0 : dx); + inner.y = tile.y + ((dy < threshold) ? 0 : dy); + } + + //if (RECTANGLE_EQUALS(client->last_size, rect) + // && client->last_border_width == border_width) { + // return; + //} + + if (scheme.tight_decoration) { + outline = inner_rect_to_outline(inner, scheme); + } + client->dec.last_inner_rect = inner; + inner.x -= outline.x; + inner.y -= outline.y; + XWindowChanges changes; + changes.x = inner.x; + changes.y = inner.y; + changes.width = inner.width; + changes.height = inner.height; + changes.border_width = 0; + + int mask = CWX | CWY | CWWidth | CWHeight | CWBorderWidth; + //if (*g_window_border_inner_width > 0 + // && *g_window_border_inner_width < *g_window_border_width) { + // unsigned long current_border_color = get_window_border_color(client); + // HSDebug("client_resize %s\n", + // current_border_color == g_window_border_active_color + // ? "ACTIVE" : "NORMAL"); + // set_window_double_border(g_display, win, *g_window_border_inner_width, + // g_window_border_inner_color, + // current_border_color); + //} + // send new size to client + // update structs + bool size_changed = outline.width != client->dec.last_outer_rect.width + || outline.height != client->dec.last_outer_rect.height; + client->dec.last_outer_rect = outline; + client->dec.last_rect_inner = false; + client->last_size = inner; + client->dec.last_scheme = scheme; + // redraw + // TODO: reduce flickering + if (!client->dragged || *g_update_dragged_clients) { + client->dec.last_actual_rect.x = changes.x; + client->dec.last_actual_rect.y = changes.y; + client->dec.last_actual_rect.width = changes.width; + client->dec.last_actual_rect.height = changes.height; + } + decoration_redraw_pixmap(client); + XSetWindowBackgroundPixmap(g_display, decwin, client->dec.pixmap); + if (!size_changed) { + // if size changes, then the window is cleared automatically + XClearWindow(g_display, decwin); + } + if (!client->dragged || *g_update_dragged_clients) { + XConfigureWindow(g_display, win, mask, &changes); + XMoveResizeWindow(g_display, client->dec.bgwin, + changes.x, changes.y, + changes.width, changes.height); + } + XMoveResizeWindow(g_display, decwin, + outline.x, outline.y, outline.width, outline.height); + decoration_update_frame_extents(client); + if (!client->dragged || *g_update_dragged_clients) { + client_send_configure(client); + } + XSync(g_display, False); +} + +static void decoration_update_frame_extents(struct HSClient* client) { + int left = client->dec.last_inner_rect.x - client->dec.last_outer_rect.x; + int top = client->dec.last_inner_rect.y - client->dec.last_outer_rect.y; + int right = client->dec.last_outer_rect.width - client->dec.last_inner_rect.width - left; + int bottom = client->dec.last_outer_rect.height - client->dec.last_inner_rect.height - top; + ewmh_update_frame_extents(client->window, left,right, top,bottom); +} + +void decoration_change_scheme(struct HSClient* client, + HSDecorationScheme scheme) { + if (client->dec.last_inner_rect.width < 0) { + // TODO: do something useful here + return; + } + if (client->dec.last_rect_inner) { + decoration_resize_inner(client, client->dec.last_inner_rect, scheme); + } else { + decoration_resize_outline(client, client->dec.last_outer_rect, scheme); + } +} + +static unsigned int get_client_color(HSClient* client, unsigned int pixel) { + if (client->dec.colormap) { + XColor xcol; + xcol.pixel = pixel; + /* get rbg value out of default colormap */ + XQueryColor(g_display, DefaultColormap(g_display, g_screen), &xcol); + /* get pixel value back appropriate for client */ + XAllocColor(g_display, client->dec.colormap, &xcol); + return xcol.pixel; + } else { + return pixel; + } +} + +// draw a decoration to the client->dec.pixmap +void decoration_redraw_pixmap(struct HSClient* client) { + HSDecorationScheme s = client->dec.last_scheme; + HSDecoration *const dec = &client->dec; + Window win = client->dec.decwin; + Rectangle outer = client->dec.last_outer_rect; + unsigned int depth = client->dec.depth; + // TODO: maybe do something like pixmap recreate threshhold? + bool recreate_pixmap = (dec->pixmap == 0) || (dec->pixmap_width != outer.width) + || (dec->pixmap_height != outer.height); + if (recreate_pixmap) { + if (dec->pixmap) { + XFreePixmap(g_display, dec->pixmap); + } + dec->pixmap = XCreatePixmap(g_display, win, outer.width, outer.height, depth); + } + Pixmap pix = dec->pixmap; + GC gc = XCreateGC(g_display, pix, 0, NULL); + + // draw background + XSetForeground(g_display, gc, get_client_color(client, s.border_color)); + XFillRectangle(g_display, pix, gc, 0, 0, outer.width, outer.height); + + // Draw inner border + int iw = s.inner_width; + Rectangle inner = client->dec.last_inner_rect; + inner.x -= client->dec.last_outer_rect.x; + inner.y -= client->dec.last_outer_rect.y; + if (iw > 0) { + /* fill rectangles because drawing does not work */ + XRectangle rects[] = { + { inner.x - iw, inner.y - iw, inner.width + 2*iw, iw }, /* top */ + { inner.x - iw, inner.y, iw, inner.height }, /* left */ + { inner.x + inner.width, inner.y, iw, inner.height }, /* right */ + { inner.x - iw, inner.y + inner.height, inner.width + 2*iw, iw }, /* bottom */ + }; + XSetForeground(g_display, gc, get_client_color(client, s.inner_color)); + XFillRectangles(g_display, pix, gc, rects, LENGTH(rects)); + } + + // Draw outer border + int ow = s.outer_width; + outer.x -= client->dec.last_outer_rect.x; + outer.y -= client->dec.last_outer_rect.y; + if (ow > 0) { + ow = MIN(ow, (outer.height+1) / 2); + XRectangle rects[] = { + { 0, 0, outer.width, ow }, /* top */ + { 0, ow, ow, outer.height - 2*ow }, /* left */ + { outer.width-ow, ow, ow, outer.height - 2*ow }, /* right */ + { 0, outer.height - ow, outer.width, ow }, /* bottom */ + }; + XSetForeground(g_display, gc, get_client_color(client, s.outer_color)); + XFillRectangles(g_display, pix, gc, rects, LENGTH(rects)); + } + // fill inner rect that is not covered by the client + XSetForeground(g_display, gc, get_client_color(client, s.background_color)); + if (dec->last_actual_rect.width < inner.width) { + XFillRectangle(g_display, pix, gc, + dec->last_actual_rect.x + dec->last_actual_rect.width, + dec->last_actual_rect.y, + inner.width - dec->last_actual_rect.width, + dec->last_actual_rect.height); + } + if (dec->last_actual_rect.height < inner.height) { + XFillRectangle(g_display, pix, gc, + dec->last_actual_rect.x, + dec->last_actual_rect.y + dec->last_actual_rect.height, + inner.width, + inner.height - dec->last_actual_rect.height); + } + // clean up + XFreeGC(g_display, gc); +} + diff -Nru herbstluftwm-0.6.2/src/desktopwindow.cpp herbstluftwm-0.7.0/src/desktopwindow.cpp --- herbstluftwm-0.6.2/src/desktopwindow.cpp 1970-01-01 00:00:00.000000000 +0000 +++ herbstluftwm-0.7.0/src/desktopwindow.cpp 2015-10-14 13:27:40.000000000 +0000 @@ -0,0 +1,41 @@ +#include "desktopwindow.h" +#include "globals.h" + +#include + +using namespace std; + +std::vector> DesktopWindow::windows; + +DesktopWindow::DesktopWindow(Window win, bool ifBelow) { + m_below = ifBelow; + this->win = win; +} + +Window DesktopWindow::window() const { + return win; +} + +bool DesktopWindow::below() const { + return m_below; +} + +void DesktopWindow::registerDesktop(Window win) { + auto dw = make_shared(win, true); + windows.push_back(dw); +} + +void DesktopWindow::lowerDesktopWindows() { + for (auto dw : windows) { + XLowerWindow(g_display, dw->win); + } +} + +void DesktopWindow::unregisterDesktop(Window win) { + windows.erase(std::remove_if( + windows.begin(), windows.end(), + [win](shared_ptr dw){ + return win == dw->window(); + }), + windows.end()); +} diff -Nru herbstluftwm-0.6.2/src/desktopwindow.h herbstluftwm-0.7.0/src/desktopwindow.h --- herbstluftwm-0.6.2/src/desktopwindow.h 1970-01-01 00:00:00.000000000 +0000 +++ herbstluftwm-0.7.0/src/desktopwindow.h 2015-10-14 13:27:40.000000000 +0000 @@ -0,0 +1,38 @@ +/** Copyright 2011-2013 Thorsten Wißmann. All rights reserved. + * + * This software is licensed under the "Simplified BSD License". + * See LICENSE for details */ + +#ifndef __HERBSTLUFT_DESKTOPWINDOW_H_ +#define __HERBSTLUFT_DESKTOPWINDOW_H_ + +#include +#include +#include +#include +#include + +/* container for unmanaged windows like + * - desktop windows + * - panels + */ + +class DesktopWindow { +public: + DesktopWindow(Window win, bool ifBelow); + bool below() const; + Window window() const; + static void registerDesktop(Window win); + static void lowerDesktopWindows(); + static void unregisterDesktop(Window win); +private: + + // members: + Window win; + bool m_below; + static std::vector> windows; + +}; + +#endif + diff -Nru herbstluftwm-0.6.2/src/ewmh.c herbstluftwm-0.7.0/src/ewmh.c --- herbstluftwm-0.6.2/src/ewmh.c 2014-03-27 01:37:02.000000000 +0000 +++ herbstluftwm-0.7.0/src/ewmh.c 1970-01-01 00:00:00.000000000 +0000 @@ -1,489 +0,0 @@ -/** Copyright 2011-2013 Thorsten Wißmann. All rights reserved. - * - * This software is licensed under the "Simplified BSD License". - * See LICENSE for details */ - -#include "ewmh.h" -#include "utils.h" -#include "globals.h" -#include "layout.h" -#include "clientlist.h" -#include "settings.h" -#include "stack.h" -#include "mouse.h" - -#include "glib-backports.h" -#include -#include -#include -#include - -#include -#include -#include -#include - -Window* g_windows; // array with Window-IDs -size_t g_window_count; -static Window g_wm_window; -int* g_focus_stealing_prevention; - -int WM_STATE; - -static Window* g_original_clients = NULL; -static unsigned long g_original_clients_count = 0; -static bool ewmh_read_client_list(Window** buf, unsigned long *count); - -/* list of names of all _NET-atoms */ -char* g_netatom_names[NetCOUNT] = { - [ NetSupported ] = "_NET_SUPPORTED" , - [ NetClientList ] = "_NET_CLIENT_LIST" , - [ NetClientListStacking ] = "_NET_CLIENT_LIST_STACKING" , - [ NetNumberOfDesktops ] = "_NET_NUMBER_OF_DESKTOPS" , - [ NetCurrentDesktop ] = "_NET_CURRENT_DESKTOP" , - [ NetDesktopNames ] = "_NET_DESKTOP_NAMES" , - [ NetWmDesktop ] = "_NET_WM_DESKTOP" , - [ NetDesktopViewport ] = "_NET_DESKTOP_VIEWPORT" , - [ NetActiveWindow ] = "_NET_ACTIVE_WINDOW" , - [ NetWmName ] = "_NET_WM_NAME" , - [ NetSupportingWmCheck ] = "_NET_SUPPORTING_WM_CHECK" , - [ NetWmWindowType ] = "_NET_WM_WINDOW_TYPE" , - [ NetWmState ] = "_NET_WM_STATE" , - [ NetWmWindowOpacity ] = "_NET_WM_WINDOW_OPACITY" , - [ NetMoveresizeWindow ] = "_NET_MOVERESIZE_WINDOW" , - [ NetWmMoveresize ] = "_NET_WM_MOVERESIZE" , - [ NetFrameExtents ] = "_NET_FRAME_EXTENTS" , - /* window states */ - [ NetWmStateFullscreen ] = "_NET_WM_STATE_FULLSCREEN" , - [ NetWmStateDemandsAttention ] = "_NET_WM_STATE_DEMANDS_ATTENTION" , - /* window types */ - [ NetWmWindowTypeDesktop ] = "_NET_WM_WINDOW_TYPE_DESKTOP" , - [ NetWmWindowTypeDock ] = "_NET_WM_WINDOW_TYPE_DOCK" , - [ NetWmWindowTypeToolbar ] = "_NET_WM_WINDOW_TYPE_TOOLBAR" , - [ NetWmWindowTypeMenu ] = "_NET_WM_WINDOW_TYPE_MENU" , - [ NetWmWindowTypeUtility ] = "_NET_WM_WINDOW_TYPE_UTILITY" , - [ NetWmWindowTypeSplash ] = "_NET_WM_WINDOW_TYPE_SPLASH" , - [ NetWmWindowTypeDialog ] = "_NET_WM_WINDOW_TYPE_DIALOG" , - [ NetWmWindowTypeDropdownMenu ] = "_NET_WM_WINDOW_TYPE_DROPDOWN_MENU" , - [ NetWmWindowTypePopupMenu ] = "_NET_WM_WINDOW_TYPE_POPUP_MENU" , - [ NetWmWindowTypeTooltip ] = "_NET_WM_WINDOW_TYPE_TOOLTIP" , - [ NetWmWindowTypeNotification ] = "_NET_WM_WINDOW_TYPE_NOTIFICATION" , - [ NetWmWindowTypeCombo ] = "_NET_WM_WINDOW_TYPE_COMBO" , - [ NetWmWindowTypeDnd ] = "_NET_WM_WINDOW_TYPE_DND" , - [ NetWmWindowTypeNormal ] = "_NET_WM_WINDOW_TYPE_NORMAL" , -}; - -void ewmh_init() { - /* init globals */ - g_focus_stealing_prevention = - &(settings_find("focus_stealing_prevention")->value.i); - - /* init ewmh net atoms */ - for (int i = 0; i < NetCOUNT; i++) { - if (g_netatom_names[i] == NULL) { - g_warning("no name specified in g_netatom_names " - "for atom number %d\n", i); - continue; - } - g_netatom[i] = XInternAtom(g_display, g_netatom_names[i], False); - } - - /* tell which ewmh atoms are supported */ - XChangeProperty(g_display, g_root, g_netatom[NetSupported], XA_ATOM, 32, - PropModeReplace, (unsigned char *) g_netatom, NetCOUNT); - - /* init some globals */ - g_windows = NULL; - g_window_count = 0; - if (!ewmh_read_client_list(&g_original_clients, &g_original_clients_count)) - { - g_original_clients = NULL; - g_original_clients_count = 0; - } - - /* init other atoms */ - WM_STATE = XInternAtom(g_display, "WM_STATE", False); - - /* init for the supporting wm check */ - g_wm_window = XCreateSimpleWindow(g_display, g_root, - 42, 42, 42, 42, 0, 0, 0); - XChangeProperty(g_display, g_root, g_netatom[NetSupportingWmCheck], - XA_WINDOW, 32, PropModeReplace, (unsigned char*)&(g_wm_window), 1); - XChangeProperty(g_display, g_wm_window, g_netatom[NetSupportingWmCheck], - XA_WINDOW, 32, PropModeReplace, (unsigned char*)&(g_wm_window), 1); - ewmh_update_wmname(); - - /* init atoms that never change */ - int buf[] = { 0, 0 }; - XChangeProperty(g_display, g_root, g_netatom[NetDesktopViewport], - XA_CARDINAL, 32, PropModeReplace, (unsigned char *) buf, LENGTH(buf)); -} - -void ewmh_update_all() { - /* init many properties */ - ewmh_update_client_list(); - ewmh_update_client_list_stacking(); - ewmh_update_desktops(); - ewmh_update_current_desktop(); - ewmh_update_desktop_names(); -} - -void ewmh_destroy() { - g_free(g_windows); - if (g_original_clients) { - XFree(g_original_clients); - } - XDeleteProperty(g_display, g_root, g_netatom[NetSupportingWmCheck]); - XDestroyWindow(g_display, g_wm_window); -} - -void ewmh_set_wmname(char* name) { - XChangeProperty(g_display, g_wm_window, g_netatom[NetWmName], - ATOM("UTF8_STRING"), 8, PropModeReplace, - (unsigned char*)name, strlen(name)); - XChangeProperty(g_display, g_root, g_netatom[NetWmName], - ATOM("UTF8_STRING"), 8, PropModeReplace, - (unsigned char*)name, strlen(name)); -} - -void ewmh_update_wmname() { - ewmh_set_wmname(settings_find_string("wmname")); -} - -void ewmh_update_client_list() { - XChangeProperty(g_display, g_root, g_netatom[NetClientList], - XA_WINDOW, 32, PropModeReplace, - (unsigned char *) g_windows, g_window_count); -} - -static bool ewmh_read_client_list(Window** buf, unsigned long *count) { - Atom actual_type; - int format; - unsigned long bytes_left; - if (Success != XGetWindowProperty(g_display, g_root, - g_netatom[NetClientList], 0, ~0L, False, XA_WINDOW, &actual_type, - &format, count, &bytes_left, (unsigned char**)buf)) { - return false; - } - if (bytes_left || actual_type != XA_WINDOW || format != 32) { - return false; - } - return true; -} - -void ewmh_get_original_client_list(Window** buf, unsigned long *count) { - *buf = g_original_clients; - *count = g_original_clients_count; -} - -struct ewmhstack { - Window* buf; - int count; - int i; // index of the next free element in buf -}; - -static void ewmh_add_tag_stack(HSTag* tag, void* data) { - struct ewmhstack* stack = (struct ewmhstack*)data; - if (find_monitor_with_tag(tag)) { - // do not add tags because they are already added - return; - } - int remain; - stack_to_window_buf(tag->stack, stack->buf + stack->i, - stack->count - stack->i, true, &remain); - if (remain >= 0) { - stack->i = stack->count - remain; - } else { - HSDebug("Warning: not enough space in the ewmh stack\n"); - stack->i = stack->count; - } -} - -void ewmh_update_client_list_stacking() { - // First: get the windows in the current stack - struct ewmhstack stack; - stack.count = g_window_count; - stack.buf = g_new(Window, stack.count); - int remain; - monitor_stack_to_window_buf(stack.buf, stack.count, true, &remain); - stack.i = stack.count - remain; - - // Then add all the others at the end - tag_foreach(ewmh_add_tag_stack, &stack); - - // reverse stacking order, because ewmh requires bottom to top order - array_reverse(stack.buf, stack.count, sizeof(stack.buf[0])); - - XChangeProperty(g_display, g_root, g_netatom[NetClientListStacking], - XA_WINDOW, 32, PropModeReplace, - (unsigned char *) stack.buf, stack.i); - g_free(stack.buf); -} - -void ewmh_add_client(Window win) { - g_windows = g_renew(Window, g_windows, g_window_count + 1); - g_windows[g_window_count] = win; - g_window_count++; - ewmh_update_client_list(); - ewmh_update_client_list_stacking(); -} - -void ewmh_remove_client(Window win) { - int index = array_find(g_windows, g_window_count, - sizeof(Window), &win); - if (index < 0) { - g_warning("could not find window %lx in g_windows\n", win); - } else { - g_memmove(g_windows + index, g_windows + index + 1, - sizeof(Window) *(g_window_count - index - 1)); - g_windows = g_renew(Window, g_windows, g_window_count - 1); - g_window_count--; - } - ewmh_update_client_list(); - ewmh_update_client_list_stacking(); -} - -void ewmh_update_desktops() { - XChangeProperty(g_display, g_root, g_netatom[NetNumberOfDesktops], - XA_CARDINAL, 32, PropModeReplace, (unsigned char*)&(g_tags->len), 1); -} - -void ewmh_update_desktop_names() { - char** names = g_new(char*, g_tags->len); - for (int i = 0; i < g_tags->len; i++) { - names[i] = g_array_index(g_tags, HSTag*,i)->name->str; - } - XTextProperty text_prop; - Xutf8TextListToTextProperty(g_display, names, g_tags->len, - XUTF8StringStyle, &text_prop); - XSetTextProperty(g_display, g_root, &text_prop, g_netatom[NetDesktopNames]); - XFree(text_prop.value); - g_free(names); -} - -void ewmh_update_current_desktop() { - HSTag* tag = get_current_monitor()->tag; - int index = array_find(g_tags->data, g_tags->len, sizeof(HSTag*), &tag); - if (index < 0) { - g_warning("tag %s not found in internal list\n", tag->name->str); - return; - } - XChangeProperty(g_display, g_root, g_netatom[NetCurrentDesktop], - XA_CARDINAL, 32, PropModeReplace, (unsigned char*)&(index), 1); -} - -void ewmh_window_update_tag(Window win, HSTag* tag) { - int index = array_find(g_tags->data, g_tags->len, sizeof(HSTag*), &tag); - if (index < 0) { - g_warning("tag %s not found in internal list\n", tag->name->str); - return; - } - XChangeProperty(g_display, win, g_netatom[NetWmDesktop], - XA_CARDINAL, 32, PropModeReplace, (unsigned char*)&(index), 1); -} - -void ewmh_update_active_window(Window win) { - XChangeProperty(g_display, g_root, g_netatom[NetActiveWindow], - XA_WINDOW, 32, PropModeReplace, (unsigned char*)&(win), 1); -} - -static bool focus_stealing_allowed(long source) { - if (*g_focus_stealing_prevention) { - /* only allow it to pagers/taskbars */ - return (source == 2); - } else { - /* no prevention */ - return true; - } -} - -void ewmh_handle_client_message(XEvent* event) { - HSDebug("Received event: ClientMessage\n"); - XClientMessageEvent* me = &(event->xclient); - int index; - for (index = 0; index < NetCOUNT; index++) { - if (me->message_type == g_netatom[index]) { - break; - } - } - if (index >= NetCOUNT) { - HSDebug("received unknown client message\n"); - return; - } - HSClient* client; - - int desktop_index; - switch (index) { - case NetActiveWindow: - // only steal focus it allowed to the current source - // (i.e. me->data.l[0] in this case as specified by EWMH) - if (focus_stealing_allowed(me->data.l[0])) { - HSClient* client = get_client_from_window(me->window); - if (client) { - focus_client(client, true, true); - } - } - break; - - case NetCurrentDesktop: - desktop_index = me->data.l[0]; - if (desktop_index < 0 || desktop_index >= g_tags->len) { - HSDebug("_NET_CURRENT_DESKTOP: invalid index \"%d\"\n", - desktop_index); - break; - } - HSTag* tag = g_array_index(g_tags, HSTag*, desktop_index); - monitor_set_tag(get_current_monitor(), tag); - break; - - case NetWmDesktop: - desktop_index = me->data.l[0]; - if (!focus_stealing_allowed(me->data.l[1])) { - break; - } - HSTag* target = get_tag_by_index(desktop_index); - client = get_client_from_window(me->window); - if (client && target) { - tag_move_client(client, target); - } - break; - - case NetWmState: - client = get_client_from_window(me->window); - /* ignore requests for unmanaged windows */ - if (!client || !client->ewmhrequests) break; - - /* mapping between EWMH atoms and client struct members */ - struct { - int atom_index; - bool enabled; - void (*callback)(HSClient*, bool); - } client_atoms[] = { - { NetWmStateFullscreen, - client->fullscreen, client_set_fullscreen }, - { NetWmStateDemandsAttention, - client->urgent, client_set_urgent }, - }; - - /* me->data.l[1] and [2] describe the properties to alter */ - for (int prop = 1; prop <= 2; prop++) { - if (me->data.l[prop] == 0) { - /* skip if no property is specified */ - continue; - } - /* check if we support the property data[prop] */ - int i; - for (i = 0; i < LENGTH(client_atoms); i++) { - if (g_netatom[client_atoms[i].atom_index] - == me->data.l[prop]) { - break; - } - } - if (i >= LENGTH(client_atoms)) { - /* property will not be handled */ - continue; - } - bool new_value[] = { - [ _NET_WM_STATE_REMOVE ] = false, - [ _NET_WM_STATE_ADD ] = true, - [ _NET_WM_STATE_TOGGLE ] = !client_atoms[i].enabled, - }; - int action = me->data.l[0]; - if (action >= LENGTH(new_value)) { - HSDebug("_NET_WM_STATE: invalid action %d\n", action); - } - /* change the value */ - client_atoms[i].callback(client, new_value[action]); - } - break; - - case NetWmMoveresize: - // TODO: handle requests more exactly - client = get_client_from_window(me->window); - mouse_initiate_resize(client, 0, NULL); - break; - - default: - HSDebug("no handler for the client message \"%s\"\n", - g_netatom_names[index]); - break; - } -} - -void ewmh_update_window_state(struct HSClient* client) { - /* mapping between EWMH atoms and client struct members */ - struct { - int atom_index; - bool enabled; - } client_atoms[] = { - { NetWmStateFullscreen, client->ewmhfullscreen }, - { NetWmStateDemandsAttention, client->urgent }, - }; - - /* find out which flags are set */ - Atom window_state[LENGTH(client_atoms)]; - size_t count_enabled = 0; - for (int i = 0; i < LENGTH(client_atoms); i++) { - if (client_atoms[i].enabled) { - window_state[count_enabled] = g_netatom[client_atoms[i].atom_index]; - count_enabled++; - } - } - - /* write it to the window */ - XChangeProperty(g_display, client->window, g_netatom[NetWmState], XA_ATOM, - 32, PropModeReplace, (unsigned char *) window_state, count_enabled); -} - -void ewmh_clear_client_properties(struct HSClient* client) { - XDeleteProperty(g_display, client->window, g_netatom[NetWmState]); -} - -bool ewmh_is_window_state_set(Window win, Atom hint) { - Atom* states; - Atom actual_type; - int format; - unsigned long actual_count, bytes_left; - if (Success != XGetWindowProperty(g_display, win, g_netatom[NetWmState], 0, - ~0L, False, XA_ATOM, &actual_type, &format, &actual_count, - &bytes_left, (unsigned char**)&states)) { - // NetWmState just is not set properly - return false; - } - if (actual_type != XA_ATOM || format != 32 || states == NULL) { - // invalid format or no entries - return false; - } - bool hint_set = false; - for (int i = 0; i < actual_count; i++) { - if (states[i] == hint) { - hint_set = true; - break; - } - } - XFree(states); - return hint_set; -} - -bool ewmh_is_fullscreen_set(Window win) { - return ewmh_is_window_state_set(win, g_netatom[NetWmStateFullscreen]); -} - -void ewmh_set_window_opacity(Window win, double opacity) { - uint32_t int_opacity = UINT32_MAX * CLAMP(opacity, 0, 1); - - XChangeProperty(g_display, win, g_netatom[NetWmWindowOpacity], XA_CARDINAL, - 32, PropModeReplace, (unsigned char*)&int_opacity, 1); -} -void ewmh_update_frame_extents(Window win, int left, int right, int top, int bottom) { - long extents[] = { left, right, top, bottom }; - XChangeProperty(g_display, win, g_netatom[NetFrameExtents], XA_CARDINAL, - 32, PropModeReplace, (unsigned char*)extents, LENGTH(extents)); -} - -void window_update_wm_state(Window win, WmState state) { - uint32_t int_state = state; - XChangeProperty(g_display, win, WM_STATE, WM_STATE, - 32, PropModeReplace, (unsigned char*)&int_state, 1); -} - diff -Nru herbstluftwm-0.6.2/src/ewmh.cpp herbstluftwm-0.7.0/src/ewmh.cpp --- herbstluftwm-0.6.2/src/ewmh.cpp 1970-01-01 00:00:00.000000000 +0000 +++ herbstluftwm-0.7.0/src/ewmh.cpp 2015-10-14 13:27:40.000000000 +0000 @@ -0,0 +1,565 @@ +/** Copyright 2011-2013 Thorsten Wißmann. All rights reserved. + * + * This software is licensed under the "Simplified BSD License". + * See LICENSE for details */ + +#include "ewmh.h" +#include "utils.h" +#include "globals.h" +#include "layout.h" +#include "clientlist.h" +#include "settings.h" +#include "stack.h" +#include "mouse.h" + +#include "glib-backports.h" +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +Atom g_netatom[NetCOUNT]; + +// module internal globals: +static Window* g_windows; // array with Window-IDs +static size_t g_window_count; +static Window g_wm_window; +static int* g_focus_stealing_prevention; + +static int WM_STATE; + +static Window* g_original_clients = NULL; +static unsigned long g_original_clients_count = 0; +static bool ewmh_read_client_list(Window** buf, unsigned long *count); + +/* list of names of all _NET-atoms */ +const std::arrayg_netatom_names = + ArrayInitializer({ + { NetSupported , "_NET_SUPPORTED" }, + { NetClientList , "_NET_CLIENT_LIST" }, + { NetClientListStacking , "_NET_CLIENT_LIST_STACKING" }, + { NetNumberOfDesktops , "_NET_NUMBER_OF_DESKTOPS" }, + { NetCurrentDesktop , "_NET_CURRENT_DESKTOP" }, + { NetDesktopNames , "_NET_DESKTOP_NAMES" }, + { NetWmDesktop , "_NET_WM_DESKTOP" }, + { NetDesktopViewport , "_NET_DESKTOP_VIEWPORT" }, + { NetActiveWindow , "_NET_ACTIVE_WINDOW" }, + { NetWmName , "_NET_WM_NAME" }, + { NetSupportingWmCheck , "_NET_SUPPORTING_WM_CHECK" }, + { NetWmWindowType , "_NET_WM_WINDOW_TYPE" }, + { NetWmState , "_NET_WM_STATE" }, + { NetWmWindowOpacity , "_NET_WM_WINDOW_OPACITY" }, + { NetMoveresizeWindow , "_NET_MOVERESIZE_WINDOW" }, + { NetWmMoveresize , "_NET_WM_MOVERESIZE" }, + { NetFrameExtents , "_NET_FRAME_EXTENTS" }, + /* window states */ + { NetWmStateFullscreen , "_NET_WM_STATE_FULLSCREEN" }, + { NetWmStateDemandsAttention , "_NET_WM_STATE_DEMANDS_ATTENTION" }, + /* window types */ + { NetWmWindowTypeDesktop , "_NET_WM_WINDOW_TYPE_DESKTOP" }, + { NetWmWindowTypeDock , "_NET_WM_WINDOW_TYPE_DOCK" }, + { NetWmWindowTypeToolbar , "_NET_WM_WINDOW_TYPE_TOOLBAR" }, + { NetWmWindowTypeMenu , "_NET_WM_WINDOW_TYPE_MENU" }, + { NetWmWindowTypeUtility , "_NET_WM_WINDOW_TYPE_UTILITY" }, + { NetWmWindowTypeSplash , "_NET_WM_WINDOW_TYPE_SPLASH" }, + { NetWmWindowTypeDialog , "_NET_WM_WINDOW_TYPE_DIALOG" }, + { NetWmWindowTypeDropdownMenu , "_NET_WM_WINDOW_TYPE_DROPDOWN_MENU" }, + { NetWmWindowTypePopupMenu , "_NET_WM_WINDOW_TYPE_POPUP_MENU" }, + { NetWmWindowTypeTooltip , "_NET_WM_WINDOW_TYPE_TOOLTIP" }, + { NetWmWindowTypeNotification , "_NET_WM_WINDOW_TYPE_NOTIFICATION" }, + { NetWmWindowTypeCombo , "_NET_WM_WINDOW_TYPE_COMBO" }, + { NetWmWindowTypeDnd , "_NET_WM_WINDOW_TYPE_DND" }, + { NetWmWindowTypeNormal , "_NET_WM_WINDOW_TYPE_NORMAL" }, +}).a; + +void ewmh_init() { + /* init globals */ + g_focus_stealing_prevention = + &(settings_find("focus_stealing_prevention")->value.i); + + /* init ewmh net atoms */ + for (int i = 0; i < NetCOUNT; i++) { + if (g_netatom_names[i] == NULL) { + g_warning("no name specified in g_netatom_names " + "for atom number %d\n", i); + continue; + } + g_netatom[i] = XInternAtom(g_display, g_netatom_names[i], False); + } + + /* tell which ewmh atoms are supported */ + XChangeProperty(g_display, g_root, g_netatom[NetSupported], XA_ATOM, 32, + PropModeReplace, (unsigned char *) g_netatom, NetCOUNT); + + /* init some globals */ + g_windows = NULL; + g_window_count = 0; + if (!ewmh_read_client_list(&g_original_clients, &g_original_clients_count)) + { + g_original_clients = NULL; + g_original_clients_count = 0; + } + + /* init other atoms */ + WM_STATE = XInternAtom(g_display, "WM_STATE", False); + + /* init for the supporting wm check */ + g_wm_window = XCreateSimpleWindow(g_display, g_root, + 42, 42, 42, 42, 0, 0, 0); + XChangeProperty(g_display, g_root, g_netatom[NetSupportingWmCheck], + XA_WINDOW, 32, PropModeReplace, (unsigned char*)&(g_wm_window), 1); + XChangeProperty(g_display, g_wm_window, g_netatom[NetSupportingWmCheck], + XA_WINDOW, 32, PropModeReplace, (unsigned char*)&(g_wm_window), 1); + ewmh_update_wmname(); + + /* init atoms that never change */ + int buf[] = { 0, 0 }; + XChangeProperty(g_display, g_root, g_netatom[NetDesktopViewport], + XA_CARDINAL, 32, PropModeReplace, (unsigned char *) buf, LENGTH(buf)); +} + +void ewmh_update_all() { + /* init many properties */ + ewmh_update_client_list(); + ewmh_update_client_list_stacking(); + ewmh_update_desktops(); + ewmh_update_current_desktop(); + ewmh_update_desktop_names(); +} + +void ewmh_destroy() { + g_free(g_windows); + if (g_original_clients) { + XFree(g_original_clients); + } + XDeleteProperty(g_display, g_root, g_netatom[NetSupportingWmCheck]); + XDestroyWindow(g_display, g_wm_window); +} + +void ewmh_set_wmname(char* name) { + XChangeProperty(g_display, g_wm_window, g_netatom[NetWmName], + ATOM("UTF8_STRING"), 8, PropModeReplace, + (unsigned char*)name, strlen(name)); + XChangeProperty(g_display, g_root, g_netatom[NetWmName], + ATOM("UTF8_STRING"), 8, PropModeReplace, + (unsigned char*)name, strlen(name)); +} + +void ewmh_update_wmname() { + ewmh_set_wmname(settings_find_string("wmname")); +} + +void ewmh_update_client_list() { + XChangeProperty(g_display, g_root, g_netatom[NetClientList], + XA_WINDOW, 32, PropModeReplace, + (unsigned char *) g_windows, g_window_count); +} + +static bool ewmh_read_client_list(Window** buf, unsigned long *count) { + Atom actual_type; + int format; + unsigned long bytes_left; + if (Success != XGetWindowProperty(g_display, g_root, + g_netatom[NetClientList], 0, ~0L, False, XA_WINDOW, &actual_type, + &format, count, &bytes_left, (unsigned char**)buf)) { + return false; + } + if (bytes_left || actual_type != XA_WINDOW || format != 32) { + return false; + } + return true; +} + +void ewmh_get_original_client_list(Window** buf, unsigned long *count) { + *buf = g_original_clients; + *count = g_original_clients_count; +} + +struct ewmhstack { + Window* buf; + int count; + int i; // index of the next free element in buf +}; + +static void ewmh_add_tag_stack(HSTag* tag, void* data) { + struct ewmhstack* stack = (struct ewmhstack*)data; + if (find_monitor_with_tag(tag)) { + // do not add tags because they are already added + return; + } + int remain; + stack_to_window_buf(tag->stack, stack->buf + stack->i, + stack->count - stack->i, true, &remain); + if (remain >= 0) { + stack->i = stack->count - remain; + } else { + HSDebug("Warning: not enough space in the ewmh stack\n"); + stack->i = stack->count; + } +} + +void ewmh_update_client_list_stacking() { + // First: get the windows in the current stack + struct ewmhstack stack; + stack.count = g_window_count; + stack.buf = g_new(Window, stack.count); + int remain; + monitor_stack_to_window_buf(stack.buf, stack.count, true, &remain); + stack.i = stack.count - remain; + + // Then add all the others at the end + tag_foreach(ewmh_add_tag_stack, &stack); + + // reverse stacking order, because ewmh requires bottom to top order + array_reverse(stack.buf, stack.count, sizeof(stack.buf[0])); + + XChangeProperty(g_display, g_root, g_netatom[NetClientListStacking], + XA_WINDOW, 32, PropModeReplace, + (unsigned char *) stack.buf, stack.i); + g_free(stack.buf); +} + +void ewmh_add_client(Window win) { + g_windows = g_renew(Window, g_windows, g_window_count + 1); + g_windows[g_window_count] = win; + g_window_count++; + ewmh_update_client_list(); + ewmh_update_client_list_stacking(); +} + +void ewmh_remove_client(Window win) { + int index = array_find(g_windows, g_window_count, + sizeof(Window), &win); + if (index < 0) { + g_warning("could not find window %lx in g_windows\n", win); + } else { + g_memmove(g_windows + index, g_windows + index + 1, + sizeof(Window) *(g_window_count - index - 1)); + g_windows = g_renew(Window, g_windows, g_window_count - 1); + g_window_count--; + } + ewmh_update_client_list(); + ewmh_update_client_list_stacking(); +} + +void ewmh_update_desktops() { + int cnt = tag_get_count(); + XChangeProperty(g_display, g_root, g_netatom[NetNumberOfDesktops], + XA_CARDINAL, 32, PropModeReplace, (unsigned char*)&cnt, 1); +} + +void ewmh_update_desktop_names() { + char** names = g_new(char*, tag_get_count()); + for (int i = 0; i < tag_get_count(); i++) { + names[i] = get_tag_by_index(i)->name->str; + } + XTextProperty text_prop; + Xutf8TextListToTextProperty(g_display, names, tag_get_count(), + XUTF8StringStyle, &text_prop); + XSetTextProperty(g_display, g_root, &text_prop, g_netatom[NetDesktopNames]); + XFree(text_prop.value); + g_free(names); +} + +void ewmh_update_current_desktop() { + HSTag* tag = get_current_monitor()->tag; + int index = tag_index_of(tag); + if (index < 0) { + g_warning("tag %s not found in internal list\n", tag->name->str); + return; + } + XChangeProperty(g_display, g_root, g_netatom[NetCurrentDesktop], + XA_CARDINAL, 32, PropModeReplace, (unsigned char*)&(index), 1); +} + +void ewmh_window_update_tag(Window win, HSTag* tag) { + int index = tag_index_of(tag); + if (index < 0) { + g_warning("tag %s not found in internal list\n", tag->name->str); + return; + } + XChangeProperty(g_display, win, g_netatom[NetWmDesktop], + XA_CARDINAL, 32, PropModeReplace, (unsigned char*)&(index), 1); +} + +void ewmh_update_active_window(Window win) { + XChangeProperty(g_display, g_root, g_netatom[NetActiveWindow], + XA_WINDOW, 32, PropModeReplace, (unsigned char*)&(win), 1); +} + +static bool focus_stealing_allowed(long source) { + if (*g_focus_stealing_prevention) { + /* only allow it to pagers/taskbars */ + return (source == 2); + } else { + /* no prevention */ + return true; + } +} + +void ewmh_handle_client_message(XEvent* event) { + HSDebug("Received event: ClientMessage\n"); + XClientMessageEvent* me = &(event->xclient); + int index; + for (index = 0; index < NetCOUNT; index++) { + if (me->message_type == g_netatom[index]) { + break; + } + } + if (index >= NetCOUNT) { + HSDebug("received unknown client message\n"); + return; + } + HSClient* client; + + int desktop_index; + switch (index) { + case NetActiveWindow: + // only steal focus it allowed to the current source + // (i.e. me->data.l[0] in this case as specified by EWMH) + if (focus_stealing_allowed(me->data.l[0])) { + HSClient* client = get_client_from_window(me->window); + if (client) { + focus_client(client, true, true); + } + } + break; + + case NetCurrentDesktop: { + desktop_index = me->data.l[0]; + if (desktop_index < 0 || desktop_index >= tag_get_count()) { + HSDebug("_NET_CURRENT_DESKTOP: invalid index \"%d\"\n", + desktop_index); + break; + } + HSTag* tag = get_tag_by_index(desktop_index); + monitor_set_tag(get_current_monitor(), tag); + break; + } + + case NetWmDesktop: { + desktop_index = me->data.l[0]; + if (!focus_stealing_allowed(me->data.l[1])) { + break; + } + HSTag* target = get_tag_by_index(desktop_index); + client = get_client_from_window(me->window); + if (client && target) { + tag_move_client(client, target); + } + break; + } + + case NetWmState: { + client = get_client_from_window(me->window); + /* ignore requests for unmanaged windows */ + if (!client || !client->ewmhrequests) break; + + /* mapping between EWMH atoms and client struct members */ + struct { + int atom_index; + bool enabled; + void (*callback)(HSClient*, bool); + } client_atoms[] = { + { NetWmStateFullscreen, + client->fullscreen, client_set_fullscreen }, + { NetWmStateDemandsAttention, + client->urgent, client_set_urgent }, + }; + + /* me->data.l[1] and [2] describe the properties to alter */ + for (int prop = 1; prop <= 2; prop++) { + if (me->data.l[prop] == 0) { + /* skip if no property is specified */ + continue; + } + /* check if we support the property data[prop] */ + int i; + for (i = 0; i < LENGTH(client_atoms); i++) { + if (g_netatom[client_atoms[i].atom_index] + == me->data.l[prop]) { + break; + } + } + if (i >= LENGTH(client_atoms)) { + /* property will not be handled */ + continue; + } + auto new_value = ArrayInitializer({ + { _NET_WM_STATE_REMOVE , false }, + { _NET_WM_STATE_ADD , true }, + { _NET_WM_STATE_TOGGLE , !client_atoms[i].enabled }, + }).a; + int action = me->data.l[0]; + if (action >= new_value.size()) { + HSDebug("_NET_WM_STATE: invalid action %d\n", action); + } + /* change the value */ + client_atoms[i].callback(client, new_value[action]); + } + break; + } + + case NetWmMoveresize: { + client = get_client_from_window(me->window); + if (!client) { + break; + } + int direction = me->data.l[2]; + if (direction == _NET_WM_MOVERESIZE_MOVE + || direction == _NET_WM_MOVERESIZE_MOVE_KEYBOARD) { + mouse_initiate_move(client, 0, NULL); + } else if (direction == _NET_WM_MOVERESIZE_CANCEL) { + if (mouse_is_dragging()) mouse_stop_drag(); + } else { + // anything else is a resize + mouse_initiate_resize(client, 0, NULL); + } + break; + } + + default: + HSDebug("no handler for the client message \"%s\"\n", + g_netatom_names[index]); + break; + } +} + +void ewmh_update_window_state(struct HSClient* client) { + /* mapping between EWMH atoms and client struct members */ + struct { + int atom_index; + bool enabled; + } client_atoms[] = { + { NetWmStateFullscreen, client->ewmhfullscreen }, + { NetWmStateDemandsAttention, client->urgent }, + }; + + /* find out which flags are set */ + Atom window_state[LENGTH(client_atoms)]; + size_t count_enabled = 0; + for (int i = 0; i < LENGTH(client_atoms); i++) { + if (client_atoms[i].enabled) { + window_state[count_enabled] = g_netatom[client_atoms[i].atom_index]; + count_enabled++; + } + } + + /* write it to the window */ + XChangeProperty(g_display, client->window, g_netatom[NetWmState], XA_ATOM, + 32, PropModeReplace, (unsigned char *) window_state, count_enabled); +} + +void ewmh_clear_client_properties(struct HSClient* client) { + XDeleteProperty(g_display, client->window, g_netatom[NetWmState]); +} + +bool ewmh_is_window_state_set(Window win, Atom hint) { + Atom* states; + Atom actual_type; + int format; + unsigned long actual_count, bytes_left; + if (Success != XGetWindowProperty(g_display, win, g_netatom[NetWmState], 0, + ~0L, False, XA_ATOM, &actual_type, &format, &actual_count, + &bytes_left, (unsigned char**)&states)) { + // NetWmState just is not set properly + return false; + } + if (actual_type != XA_ATOM || format != 32 || states == NULL) { + // invalid format or no entries + return false; + } + bool hint_set = false; + for (int i = 0; i < actual_count; i++) { + if (states[i] == hint) { + hint_set = true; + break; + } + } + XFree(states); + return hint_set; +} + +bool ewmh_is_fullscreen_set(Window win) { + return ewmh_is_window_state_set(win, g_netatom[NetWmStateFullscreen]); +} + +int ewmh_get_window_type(Window win) { // returns an element of the NetWm-Enum + // that only works for atom-type utf8-string, _NET_WM_WINDOW_TYPE is int + // GString* wintype= + // window_property_to_g_string(g_display, client->window, wintype_atom); + // => + // kinda duplicate from src/utils.c:window_properties_to_g_string() + // using different xatom type, and only calling XGetWindowProperty + // once, because we are sure we only need four bytes + long bufsize = 10; + char *buf; + Atom type_ret, wintype; + int format; + unsigned long items, bytes_left; + long offset = 0; + + int status = XGetWindowProperty( + g_display, + win, + g_netatom[NetWmWindowType], + offset, + bufsize, + False, + ATOM("ATOM"), + &type_ret, + &format, + &items, + &bytes_left, + (unsigned char**)&buf + ); + // we only need precisely four bytes (one Atom) + // if there are bytes left, something went wrong + if(status != Success || bytes_left > 0 || items < 1 || buf == NULL) { + return -1; + } else { + wintype= *(Atom *)buf; + XFree(buf); + } + + for (int i = NetWmWindowTypeFIRST; i <= NetWmWindowTypeLAST; i++) { + // try to find the window type + if (wintype == g_netatom[i]) { + return i; + } + } + return -1; +} + +bool ewmh_is_desktop_window(Window win) { + return ewmh_get_window_type(win) == NetWmWindowTypeDesktop; +} + +void ewmh_set_window_opacity(Window win, double opacity) { + uint32_t int_opacity = std::numeric_limits::max() + * CLAMP(opacity, 0, 1); + + XChangeProperty(g_display, win, g_netatom[NetWmWindowOpacity], XA_CARDINAL, + 32, PropModeReplace, (unsigned char*)&int_opacity, 1); +} +void ewmh_update_frame_extents(Window win, int left, int right, int top, int bottom) { + long extents[] = { left, right, top, bottom }; + XChangeProperty(g_display, win, g_netatom[NetFrameExtents], XA_CARDINAL, + 32, PropModeReplace, (unsigned char*)extents, LENGTH(extents)); +} + +void window_update_wm_state(Window win, WmState state) { + /* set full WM_STATE according to + * http://www.x.org/releases/X11R7.7/doc/xorg-docs/icccm/icccm.html#WM_STATE_Property + */ + unsigned long wmstate[] = { state, None }; + XChangeProperty(g_display, win, WM_STATE, WM_STATE, + 32, PropModeReplace, (unsigned char*)wmstate, LENGTH(wmstate)); +} + diff -Nru herbstluftwm-0.6.2/src/ewmh.h herbstluftwm-0.7.0/src/ewmh.h --- herbstluftwm-0.6.2/src/ewmh.h 2014-03-27 01:23:54.000000000 +0000 +++ herbstluftwm-0.7.0/src/ewmh.h 2015-10-14 13:27:40.000000000 +0000 @@ -10,6 +10,7 @@ #include #include #include +#include #define ENUM_WITH_ALIAS(Identifier, Alias) \ Identifier, Alias = Identifier @@ -59,12 +60,26 @@ NetCOUNT }; +// possible values for direction of a _NET_WM_MOVERESIZE client message +#define _NET_WM_MOVERESIZE_SIZE_TOPLEFT 0 +#define _NET_WM_MOVERESIZE_SIZE_TOP 1 +#define _NET_WM_MOVERESIZE_SIZE_TOPRIGHT 2 +#define _NET_WM_MOVERESIZE_SIZE_RIGHT 3 +#define _NET_WM_MOVERESIZE_SIZE_BOTTOMRIGHT 4 +#define _NET_WM_MOVERESIZE_SIZE_BOTTOM 5 +#define _NET_WM_MOVERESIZE_SIZE_BOTTOMLEFT 6 +#define _NET_WM_MOVERESIZE_SIZE_LEFT 7 +#define _NET_WM_MOVERESIZE_MOVE 8 /* movement only */ +#define _NET_WM_MOVERESIZE_SIZE_KEYBOARD 9 /* size via keyboard */ +#define _NET_WM_MOVERESIZE_MOVE_KEYBOARD 10 /* move via keyboard */ +#define _NET_WM_MOVERESIZE_CANCEL 11 /* cancel operation */ + struct HSTag; struct HSClient; -Atom g_netatom[NetCOUNT]; +extern Atom g_netatom[NetCOUNT]; -extern char* g_netatom_names[]; +extern const std::array g_netatom_names; void ewmh_init(); void ewmh_destroy(); @@ -86,6 +101,8 @@ void ewmh_update_frame_extents(Window win, int left, int right, int top, int bottom); bool ewmh_is_window_state_set(Window win, Atom hint); bool ewmh_is_fullscreen_set(Window win); +bool ewmh_is_desktop_window(Window win); +int ewmh_get_window_type(Window win); // returns an element of the NetWm-Enum void ewmh_clear_client_properties(struct HSClient* client); // set the desktop property of a window diff -Nru herbstluftwm-0.6.2/src/floating.c herbstluftwm-0.7.0/src/floating.c --- herbstluftwm-0.6.2/src/floating.c 2014-03-26 23:54:22.000000000 +0000 +++ herbstluftwm-0.7.0/src/floating.c 1970-01-01 00:00:00.000000000 +0000 @@ -1,330 +0,0 @@ -#include "floating.h" - -#include -#include -#include - -#include "utils.h" -#include "mouse.h" -#include "clientlist.h" -#include "tag.h" -#include "layout.h" -#include "settings.h" - -static int* g_snap_gap; -static int* g_monitors_locked; - -void floating_init() { - g_snap_gap = &(settings_find("snap_gap")->value.i); - g_monitors_locked = &(settings_find("monitors_locked")->value.i); -} - -void floating_destroy() { -} - -enum HSDirection char_to_direction(char ch) { - switch (ch) { - case 'u': return DirUp; - case 'r': return DirRight; - case 'l': return DirLeft; - case 'd': return DirDown; - default: return -1; - } -} - -// rectlist_rotate rotates the list of given rectangles, s.t. the direction dir -// becomes the direction "right". idx is some distinguished element, whose -// index may change -static void rectlist_rotate(RectangleIdx* rects, size_t cnt, int* idx, - enum HSDirection dir) { - switch (dir) { - case DirRight: - return; // nothing to do - case DirUp: - // just flip by the horizontal axis - FOR (i,0,cnt) { - Rectangle* r = &(rects[i].r); - r->y = - r->y - r->height; - } - // and flip order to reverse the order for rectangles with the same - // center - for (int i = 0; i < (cnt - 1 - i); i++) { - int j = (cnt - 1 - i); - SWAP(RectangleIdx, rects[i], rects[j]); - } - *idx = cnt - 1 - *idx; - // and then direction up now has become direction down - case DirDown: - // flip by the diagonal - // - // *-------------> x *-------------> x - // | +------+ | +---+[] - // | | | ==> | | | - // | +------+ | | | - // | [] | +---+ - // V V - FOR (i,0,cnt) { - Rectangle* r = &(rects[i].r); - SWAP(int, r->x, r->y); - SWAP(int, r->height, r->width); - } - return; - case DirLeft: - // flip by the vertical axis - FOR (i,0,cnt) { - Rectangle* r = &(rects[i].r); - r->x = - r->x - r->width; - } - // and flip order to reverse the order for rectangles with the same - // center - for (int i = 0; i < (cnt - 1 - i); i++) { - int j = (cnt - 1 - i); - SWAP(RectangleIdx, rects[i], rects[j]); - } - *idx = cnt - 1 - *idx; - return; - } -} - -// returns the found index in the original buffer -int find_rectangle_in_direction(RectangleIdx* rects, size_t cnt, int idx, - enum HSDirection dir) { - rectlist_rotate(rects, cnt, &idx, dir); - return find_rectangle_right_of(rects, cnt, idx); -} - -static bool rectangle_is_right_of(Rectangle RC, Rectangle R2) { - int cx = RC.x + RC.width / 2; - int cy = RC.y + RC.height / 2; - // only consider rectangles right of that with specified idx, called RC. A - // rectangle R2 is considered right, if the angle of the vector from the - // center of RC to the center of R2 is in the interval [-45 deg, + 45 deg]. - // In a picture: ... - // / - // RC +----------+ - // | / | area right of RC - // | c | - // | \ | - // +----------+ - // \... - int rcx = R2.x + R2.width / 2; - int rcy = R2.y + R2.height / 2; - // get vector from center of RC to center of R2 - rcx -= cx; - rcy -= cy; - if (rcx < 0) return false; - if (abs(rcy) > rcx) return false; - if (rcx == 0 && rcy == 0) { - // if centers match, then disallow R2 to have a larger width - return true; - } - return true; -} - -int find_rectangle_right_of(RectangleIdx* rects, size_t cnt, int idx) { - Rectangle RC = rects[idx].r; - int cx = RC.x + RC.width / 2; - int cy = RC.y + RC.height / 2; - int write_i = 0; // next rectangle to write - // filter out rectangles not right of RC - FOR (i,0,cnt) { - if (idx == i) continue; - Rectangle R2 = rects[i].r; - int rcx = R2.x + R2.width / 2; - int rcy = R2.y + R2.height / 2; - if (!rectangle_is_right_of(RC, R2)) continue; - // if two rectangles have exactly the same geometry, then sort by index - // compare centers and not topleft corner because rectangle_is_right_of - // does it the same way - if (rcx == cx && rcy == cy) { - if (i < idx) continue; - } - if (i == write_i) { write_i++; } - else { - rects[write_i++] = rects[i]; - } - } - // find the rectangle with the smallest distance to RC - if (write_i == 0) return -1; - int idxbest = -1; - int ibest = -1; - int distbest = INT_MAX; - FOR (i,0,write_i) { - Rectangle R2 = rects[i].r; - int rcx = R2.x + R2.width / 2; - int rcy = R2.y + R2.height / 2; - // another method that checks the closes point - int anchor_y = rcy; // (rcy > cy) ? rcy : MIN(rcy + R2.height, cy); - int anchor_x = rcx; // MAX(cx, R2.x); - // get manhatten distance to the anchor - int dist = abs(anchor_x - cx) + abs(anchor_y - cy); - if (dist < distbest - || (dist == distbest && ibest > i)) { - distbest = dist; - idxbest = rects[i].idx; - ibest = i; - } - } - return idxbest; -} - -// returns the found index in the modified buffer -int find_edge_in_direction(RectangleIdx* rects, size_t cnt, int idx, - enum HSDirection dir) { - rectlist_rotate(rects, cnt, &idx, dir); - int found = find_edge_right_of(rects, cnt, idx); - if (found < 0) return found; - // rotate back, by requesting the inverse rotation - //switch (dir) { - // case DirLeft: break; // DirLeft is inverse to itself - // case DirRight: break; // DirRight is the identity - // case DirUp: dir = DirDown; break; // once was rotated 90 deg counterclockwise.. - // // now has to be rotate 90 deg clockwise back - // case DirDown: dir = DirUp; break; - //} - rectlist_rotate(rects, cnt, &found, dir); - rectlist_rotate(rects, cnt, &found, dir); - rectlist_rotate(rects, cnt, &found, dir); - return found; -} -int find_edge_right_of(RectangleIdx* rects, size_t cnt, int idx) { - int xbound = rects[idx].r.x + rects[idx].r.width; - int ylow = rects[idx].r.y; - int yhigh = rects[idx].r.y + rects[idx].r.height; - // only keep rectangles with a x coordinate right of the xbound - // and with an appropriate y/height - // - // +---------+ - - - - - - - - - - - - // | idx | area of intrest - // +---------+ - - - - - - - - - - - - int leftmost = -1; - int dist = INT_MAX; - FOR (i,0,cnt) { - if (i == idx) continue; - if (rects[i].r.x <= xbound) continue; - int low = rects[i].r.y; - int high = low + rects[i].r.height; - if (!intervals_intersect(ylow, yhigh, low, high)) { - continue; - } - if (rects[i].r.x - xbound < dist) { - dist = rects[i].r.x - xbound; - leftmost = i; - } - } - return leftmost; -} - - -static int collectclients_helper(HSClient* client, void* data) { - GQueue* q = data; - g_queue_push_tail(q, client); - return 0; -} - -bool floating_focus_direction(enum HSDirection dir) { - if (*g_monitors_locked) { return false; } - HSTag* tag = g_cur_frame->tag; - GQueue* q = g_queue_new(); - frame_foreach_client(tag->frame, collectclients_helper, q); - int cnt = q->length; - RectangleIdx* rects = g_new0(RectangleIdx, cnt); - int i = 0; - int curfocusidx = -1; - HSClient* curfocus = get_current_client(); - bool success = true; - if (curfocus == NULL && cnt == 0) { - success = false; - } - for (GList* cur = q->head; cur != NULL; cur = cur->next, i++) { - HSClient* client = cur->data; - if (curfocus == client) curfocusidx = i; - rects[i].idx = i; - rects[i].r = client->dec.last_outer_rect; - } - int idx = (cnt > 0) - ? find_rectangle_in_direction(rects, cnt, curfocusidx, dir) - : -1; - if (idx < 0) { - success = false; - } else { - HSClient* client = g_queue_peek_nth(q, idx); - client_raise(client); - focus_client(client, false, false); - } - g_free(rects); - g_queue_free(q); - return success; -} - -bool floating_shift_direction(enum HSDirection dir) { - if (*g_monitors_locked) { return false; } - HSTag* tag = g_cur_frame->tag; - HSClient* curfocus = get_current_client(); - if (!curfocus) return false; - GQueue* q = g_queue_new(); - frame_foreach_client(tag->frame, collectclients_helper, q); - int cnt = q->length; - if (cnt == 0) { - g_queue_free(q); - return false; - } - RectangleIdx* rects = g_new0(RectangleIdx, cnt + 4); - int i = 0; - int curfocusidx = -1; - bool success = true; - for (GList* cur = q->head; cur != NULL; cur = cur->next, i++) { - HSClient* client = cur->data; - if (curfocus == client) curfocusidx = i; - rects[i].idx = i; - rects[i].r = client->dec.last_outer_rect; - } - g_queue_free(q); - // add artifical rects for screen edges - { - Rectangle mr = monitor_get_floating_area(get_current_monitor()); - Rectangle tmp[4] = { - { mr.x, mr.y, mr.width, 0 }, // top - { mr.x, mr.y, 0, mr.height }, // left - { mr.x + mr.width, mr.y, 0, mr.height }, // right - { mr.x, mr.y + mr.height, mr.y + mr.width, 0 }, // bottom - }; - FOR (i,0,4) { - rects[cnt + i].idx = -1; - rects[cnt + i].r = tmp[i]; - } - } - FOR (i,0, cnt + 4) { - // expand anything by the snap gap - rects[i].r.x -= *g_snap_gap; - rects[i].r.y -= *g_snap_gap; - rects[i].r.width += 2 * *g_snap_gap; - rects[i].r.height += 2 * *g_snap_gap; - } - // don't apply snapgap to focused client, so there will be exactly - // *g_snap_gap pixels between the focused client and the found edge - Rectangle focusrect = curfocus->dec.last_outer_rect; - int idx = find_edge_in_direction(rects, cnt + 4, curfocusidx, dir); - if (idx < 0) success = false; - else { - // shift client - int dx = 0, dy = 0; - Rectangle r = rects[idx].r; - //printf("edge: %dx%d at %d,%d\n", r.width, r.height, r.x, r.y); - //printf("focus: %dx%d at %d,%d\n", focusrect.width, focusrect.height, focusrect.x, focusrect.y); - switch (dir) { - // delta = new edge - old edge - case DirRight: dx = r.x - (focusrect.x + focusrect.width); break; - case DirLeft: dx = r.x + r.width - focusrect.x; break; - case DirDown: dy = r.y - (focusrect.y + focusrect.height); break; - case DirUp: dy = r.y + r.height - focusrect.y; break; - } - //printf("dx=%d, dy=%d\n", dx, dy); - curfocus->float_size.x += dx; - curfocus->float_size.y += dy; - monitor_apply_layout(get_current_monitor()); - } - g_free(rects); - return success; -} - diff -Nru herbstluftwm-0.6.2/src/floating.cpp herbstluftwm-0.7.0/src/floating.cpp --- herbstluftwm-0.6.2/src/floating.cpp 1970-01-01 00:00:00.000000000 +0000 +++ herbstluftwm-0.7.0/src/floating.cpp 2015-10-14 13:27:40.000000000 +0000 @@ -0,0 +1,330 @@ +#include "floating.h" + +#include +#include +#include + +#include "utils.h" +#include "mouse.h" +#include "clientlist.h" +#include "tag.h" +#include "layout.h" +#include "settings.h" + +static int* g_snap_gap; +static int* g_monitors_locked; + +void floating_init() { + g_snap_gap = &(settings_find("snap_gap")->value.i); + g_monitors_locked = &(settings_find("monitors_locked")->value.i); +} + +void floating_destroy() { +} + +enum HSDirection char_to_direction(char ch) { + switch (ch) { + case 'u': return DirUp; + case 'r': return DirRight; + case 'l': return DirLeft; + case 'd': return DirDown; + default: return (HSDirection)-1; + } +} + +// rectlist_rotate rotates the list of given rectangles, s.t. the direction dir +// becomes the direction "right". idx is some distinguished element, whose +// index may change +static void rectlist_rotate(RectangleIdx* rects, size_t cnt, int* idx, + enum HSDirection dir) { + switch (dir) { + case DirRight: + return; // nothing to do + case DirUp: + // just flip by the horizontal axis + FOR (i,0,cnt) { + Rectangle* r = &(rects[i].r); + r->y = - r->y - r->height; + } + // and flip order to reverse the order for rectangles with the same + // center + for (int i = 0; i < (cnt - 1 - i); i++) { + int j = (cnt - 1 - i); + SWAP(RectangleIdx, rects[i], rects[j]); + } + *idx = cnt - 1 - *idx; + // and then direction up now has become direction down + case DirDown: + // flip by the diagonal + // + // *-------------> x *-------------> x + // | +------+ | +---+[] + // | | | ==> | | | + // | +------+ | | | + // | [] | +---+ + // V V + FOR (i,0,cnt) { + Rectangle* r = &(rects[i].r); + SWAP(int, r->x, r->y); + SWAP(int, r->height, r->width); + } + return; + case DirLeft: + // flip by the vertical axis + FOR (i,0,cnt) { + Rectangle* r = &(rects[i].r); + r->x = - r->x - r->width; + } + // and flip order to reverse the order for rectangles with the same + // center + for (int i = 0; i < (cnt - 1 - i); i++) { + int j = (cnt - 1 - i); + SWAP(RectangleIdx, rects[i], rects[j]); + } + *idx = cnt - 1 - *idx; + return; + } +} + +// returns the found index in the original buffer +int find_rectangle_in_direction(RectangleIdx* rects, size_t cnt, int idx, + enum HSDirection dir) { + rectlist_rotate(rects, cnt, &idx, dir); + return find_rectangle_right_of(rects, cnt, idx); +} + +static bool rectangle_is_right_of(Rectangle RC, Rectangle R2) { + int cx = RC.x + RC.width / 2; + int cy = RC.y + RC.height / 2; + // only consider rectangles right of that with specified idx, called RC. A + // rectangle R2 is considered right, if the angle of the vector from the + // center of RC to the center of R2 is in the interval [-45 deg, + 45 deg]. + // In a picture: ... + // / + // RC +----------+ + // | / | area right of RC + // | c | + // | \ | + // +----------+ + // \... + int rcx = R2.x + R2.width / 2; + int rcy = R2.y + R2.height / 2; + // get vector from center of RC to center of R2 + rcx -= cx; + rcy -= cy; + if (rcx < 0) return false; + if (abs(rcy) > rcx) return false; + if (rcx == 0 && rcy == 0) { + // if centers match, then disallow R2 to have a larger width + return true; + } + return true; +} + +int find_rectangle_right_of(RectangleIdx* rects, size_t cnt, int idx) { + Rectangle RC = rects[idx].r; + int cx = RC.x + RC.width / 2; + int cy = RC.y + RC.height / 2; + int write_i = 0; // next rectangle to write + // filter out rectangles not right of RC + FOR (i,0,cnt) { + if (idx == i) continue; + Rectangle R2 = rects[i].r; + int rcx = R2.x + R2.width / 2; + int rcy = R2.y + R2.height / 2; + if (!rectangle_is_right_of(RC, R2)) continue; + // if two rectangles have exactly the same geometry, then sort by index + // compare centers and not topleft corner because rectangle_is_right_of + // does it the same way + if (rcx == cx && rcy == cy) { + if (i < idx) continue; + } + if (i == write_i) { write_i++; } + else { + rects[write_i++] = rects[i]; + } + } + // find the rectangle with the smallest distance to RC + if (write_i == 0) return -1; + int idxbest = -1; + int ibest = -1; + int distbest = INT_MAX; + FOR (i,0,write_i) { + Rectangle R2 = rects[i].r; + int rcx = R2.x + R2.width / 2; + int rcy = R2.y + R2.height / 2; + // another method that checks the closes point + int anchor_y = rcy; // (rcy > cy) ? rcy : MIN(rcy + R2.height, cy); + int anchor_x = rcx; // MAX(cx, R2.x); + // get manhatten distance to the anchor + int dist = abs(anchor_x - cx) + abs(anchor_y - cy); + if (dist < distbest + || (dist == distbest && ibest > i)) { + distbest = dist; + idxbest = rects[i].idx; + ibest = i; + } + } + return idxbest; +} + +// returns the found index in the modified buffer +int find_edge_in_direction(RectangleIdx* rects, size_t cnt, int idx, + enum HSDirection dir) { + rectlist_rotate(rects, cnt, &idx, dir); + int found = find_edge_right_of(rects, cnt, idx); + if (found < 0) return found; + // rotate back, by requesting the inverse rotation + //switch (dir) { + // case DirLeft: break; // DirLeft is inverse to itself + // case DirRight: break; // DirRight is the identity + // case DirUp: dir = DirDown; break; // once was rotated 90 deg counterclockwise.. + // // now has to be rotate 90 deg clockwise back + // case DirDown: dir = DirUp; break; + //} + rectlist_rotate(rects, cnt, &found, dir); + rectlist_rotate(rects, cnt, &found, dir); + rectlist_rotate(rects, cnt, &found, dir); + return found; +} +int find_edge_right_of(RectangleIdx* rects, size_t cnt, int idx) { + int xbound = rects[idx].r.x + rects[idx].r.width; + int ylow = rects[idx].r.y; + int yhigh = rects[idx].r.y + rects[idx].r.height; + // only keep rectangles with a x coordinate right of the xbound + // and with an appropriate y/height + // + // +---------+ - - - - - - - - - - - + // | idx | area of intrest + // +---------+ - - - - - - - - - - - + int leftmost = -1; + int dist = INT_MAX; + FOR (i,0,cnt) { + if (i == idx) continue; + if (rects[i].r.x <= xbound) continue; + int low = rects[i].r.y; + int high = low + rects[i].r.height; + if (!intervals_intersect(ylow, yhigh, low, high)) { + continue; + } + if (rects[i].r.x - xbound < dist) { + dist = rects[i].r.x - xbound; + leftmost = i; + } + } + return leftmost; +} + + +static int collectclients_helper(HSClient* client, void* data) { + GQueue* q = (GQueue*)data; + g_queue_push_tail(q, client); + return 0; +} + +bool floating_focus_direction(enum HSDirection dir) { + if (*g_monitors_locked) { return false; } + HSTag* tag = g_cur_frame->tag; + GQueue* q = g_queue_new(); + frame_foreach_client(tag->frame, collectclients_helper, q); + int cnt = q->length; + RectangleIdx* rects = g_new0(RectangleIdx, cnt); + int i = 0; + int curfocusidx = -1; + HSClient* curfocus = get_current_client(); + bool success = true; + if (curfocus == NULL && cnt == 0) { + success = false; + } + for (GList* cur = q->head; cur != NULL; cur = cur->next, i++) { + HSClient* client = (HSClient*)cur->data; + if (curfocus == client) curfocusidx = i; + rects[i].idx = i; + rects[i].r = client->dec.last_outer_rect; + } + int idx = (cnt > 0) + ? find_rectangle_in_direction(rects, cnt, curfocusidx, dir) + : -1; + if (idx < 0) { + success = false; + } else { + HSClient* client = (HSClient*)g_queue_peek_nth(q, idx); + client_raise(client); + focus_client(client, false, false); + } + g_free(rects); + g_queue_free(q); + return success; +} + +bool floating_shift_direction(enum HSDirection dir) { + if (*g_monitors_locked) { return false; } + HSTag* tag = g_cur_frame->tag; + HSClient* curfocus = get_current_client(); + if (!curfocus) return false; + GQueue* q = g_queue_new(); + frame_foreach_client(tag->frame, collectclients_helper, q); + int cnt = q->length; + if (cnt == 0) { + g_queue_free(q); + return false; + } + RectangleIdx* rects = g_new0(RectangleIdx, cnt + 4); + int i = 0; + int curfocusidx = -1; + bool success = true; + for (GList* cur = q->head; cur != NULL; cur = cur->next, i++) { + HSClient* client = (HSClient*)cur->data; + if (curfocus == client) curfocusidx = i; + rects[i].idx = i; + rects[i].r = client->dec.last_outer_rect; + } + g_queue_free(q); + // add artifical rects for screen edges + { + Rectangle mr = monitor_get_floating_area(get_current_monitor()); + Rectangle tmp[4] = { + { mr.x, mr.y, mr.width, 0 }, // top + { mr.x, mr.y, 0, mr.height }, // left + { mr.x + mr.width, mr.y, 0, mr.height }, // right + { mr.x, mr.y + mr.height, mr.y + mr.width, 0 }, // bottom + }; + FOR (i,0,4) { + rects[cnt + i].idx = -1; + rects[cnt + i].r = tmp[i]; + } + } + FOR (i,0, cnt + 4) { + // expand anything by the snap gap + rects[i].r.x -= *g_snap_gap; + rects[i].r.y -= *g_snap_gap; + rects[i].r.width += 2 * *g_snap_gap; + rects[i].r.height += 2 * *g_snap_gap; + } + // don't apply snapgap to focused client, so there will be exactly + // *g_snap_gap pixels between the focused client and the found edge + Rectangle focusrect = curfocus->dec.last_outer_rect; + int idx = find_edge_in_direction(rects, cnt + 4, curfocusidx, dir); + if (idx < 0) success = false; + else { + // shift client + int dx = 0, dy = 0; + Rectangle r = rects[idx].r; + //printf("edge: %dx%d at %d,%d\n", r.width, r.height, r.x, r.y); + //printf("focus: %dx%d at %d,%d\n", focusrect.width, focusrect.height, focusrect.x, focusrect.y); + switch (dir) { + // delta = new edge - old edge + case DirRight: dx = r.x - (focusrect.x + focusrect.width); break; + case DirLeft: dx = r.x + r.width - focusrect.x; break; + case DirDown: dy = r.y - (focusrect.y + focusrect.height); break; + case DirUp: dy = r.y + r.height - focusrect.y; break; + } + //printf("dx=%d, dy=%d\n", dx, dy); + curfocus->float_size.x += dx; + curfocus->float_size.y += dy; + monitor_apply_layout(get_current_monitor()); + } + g_free(rects); + return success; +} + diff -Nru herbstluftwm-0.6.2/src/globals.h herbstluftwm-0.7.0/src/globals.h --- herbstluftwm-0.6.2/src/globals.h 2014-03-26 23:54:22.000000000 +0000 +++ herbstluftwm-0.7.0/src/globals.h 2015-10-14 13:27:40.000000000 +0000 @@ -29,14 +29,13 @@ #define HERBST_MAX_TREE_HEIGHT 3 // connection to x-server -Display* g_display; -int g_screen; -Window g_root; -int g_screen_width; -int g_screen_height; -// some settings/info -bool g_aboutToQuit; -int g_verbose; +extern Display* g_display; +extern int g_screen; +extern Window g_root; +extern int g_screen_width; +extern int g_screen_height; +extern bool g_aboutToQuit; +extern int g_verbose; // bufsize to get some error strings #define ERROR_STRING_BUF_SIZE 1000 diff -Nru herbstluftwm-0.6.2/src/hook.c herbstluftwm-0.7.0/src/hook.c --- herbstluftwm-0.6.2/src/hook.c 2013-12-14 17:08:13.000000000 +0000 +++ herbstluftwm-0.7.0/src/hook.c 1970-01-01 00:00:00.000000000 +0000 @@ -1,98 +0,0 @@ -/** Copyright 2011-2013 Thorsten Wißmann. All rights reserved. - * - * This software is licensed under the "Simplified BSD License". - * See LICENSE for details */ - -#include "hook.h" -#include "globals.h" -#include "utils.h" -#include "ipc-protocol.h" -// std -#include -#include -#include -// other -#include "glib-backports.h" -// gui -#include -#include -#include -#include - -static Window g_event_window; - -void hook_init() { - g_event_window = XCreateSimpleWindow(g_display, g_root, 42, 42, 42, 42, 0, 0, 0); - // set wm_class for window - XClassHint *hint = XAllocClassHint(); - hint->res_name = HERBST_HOOK_CLASS; - hint->res_class = HERBST_HOOK_CLASS; - XSetClassHint(g_display, g_event_window, hint); - XFree(hint); - // ignore all events for this window - XSelectInput(g_display, g_event_window, 0l); - // set its window id in root window - XChangeProperty(g_display, g_root, ATOM(HERBST_HOOK_WIN_ID_ATOM), - XA_ATOM, 32, PropModeReplace, (unsigned char*)&g_event_window, 1); -} - -void hook_destroy() { - // remove property from root window - XDeleteProperty(g_display, g_root, ATOM(HERBST_HOOK_WIN_ID_ATOM)); - XDestroyWindow(g_display, g_event_window); -} - -void hook_emit(int argc, char** argv) { - static int last_property_number = 0; - if (argc <= 0) { - // nothing to do - return; - } - XTextProperty text_prop; - static char atom_name[STRING_BUF_SIZE]; - snprintf(atom_name, STRING_BUF_SIZE, HERBST_HOOK_PROPERTY_FORMAT, last_property_number); - Atom atom = ATOM(atom_name); - Xutf8TextListToTextProperty(g_display, argv, argc, XUTF8StringStyle, &text_prop); - XSetTextProperty(g_display, g_event_window, &text_prop, atom); - XFree(text_prop.value); - // set counter for next property - last_property_number += 1; - last_property_number %= HERBST_HOOK_PROPERTY_COUNT; -} - -void emit_tag_changed(HSTag* tag, int monitor) { - assert(tag != NULL); - static char monitor_name[STRING_BUF_SIZE]; - snprintf(monitor_name, STRING_BUF_SIZE, "%d", monitor); - char* argv[3]; - argv[0] = "tag_changed"; - argv[1] = tag->name->str; - argv[2] = monitor_name; - hook_emit(LENGTH(argv), argv); -} - -void hook_emit_list(char* name, ...) { - assert(name != NULL); - int count = 1; - va_list ap; - // first count number of arguments - va_start(ap, name); - while (va_arg(ap, char*)) { - count++; - } - va_end(ap); - // then fill arguments into argv array - char** argv = g_new(char*, count); - int i = 0; - argv[i++] = name; - va_start(ap, name); - while (i < count) { - argv[i] = va_arg(ap, char*); - i++; - } - va_end(ap); - hook_emit(count, argv); - // cleanup - g_free(argv); -} - diff -Nru herbstluftwm-0.6.2/src/hook.cpp herbstluftwm-0.7.0/src/hook.cpp --- herbstluftwm-0.6.2/src/hook.cpp 1970-01-01 00:00:00.000000000 +0000 +++ herbstluftwm-0.7.0/src/hook.cpp 2015-10-14 13:27:40.000000000 +0000 @@ -0,0 +1,98 @@ +/** Copyright 2011-2013 Thorsten Wißmann. All rights reserved. + * + * This software is licensed under the "Simplified BSD License". + * See LICENSE for details */ + +#include "hook.h" +#include "globals.h" +#include "utils.h" +#include "ipc-protocol.h" +// std +#include +#include +#include +// other +#include "glib-backports.h" +// gui +#include +#include +#include +#include + +static Window g_event_window; + +void hook_init() { + g_event_window = XCreateSimpleWindow(g_display, g_root, 42, 42, 42, 42, 0, 0, 0); + // set wm_class for window + XClassHint *hint = XAllocClassHint(); + hint->res_name = (char*)HERBST_HOOK_CLASS; + hint->res_class = (char*)HERBST_HOOK_CLASS; + XSetClassHint(g_display, g_event_window, hint); + XFree(hint); + // ignore all events for this window + XSelectInput(g_display, g_event_window, 0l); + // set its window id in root window + XChangeProperty(g_display, g_root, ATOM(HERBST_HOOK_WIN_ID_ATOM), + XA_ATOM, 32, PropModeReplace, (unsigned char*)&g_event_window, 1); +} + +void hook_destroy() { + // remove property from root window + XDeleteProperty(g_display, g_root, ATOM(HERBST_HOOK_WIN_ID_ATOM)); + XDestroyWindow(g_display, g_event_window); +} + +void hook_emit(int argc, const char** argv) { + static int last_property_number = 0; + if (argc <= 0) { + // nothing to do + return; + } + XTextProperty text_prop; + static char atom_name[STRING_BUF_SIZE]; + snprintf(atom_name, STRING_BUF_SIZE, HERBST_HOOK_PROPERTY_FORMAT, last_property_number); + Atom atom = ATOM(atom_name); + Xutf8TextListToTextProperty(g_display, (char**)argv, argc, XUTF8StringStyle, &text_prop); + XSetTextProperty(g_display, g_event_window, &text_prop, atom); + XFree(text_prop.value); + // set counter for next property + last_property_number += 1; + last_property_number %= HERBST_HOOK_PROPERTY_COUNT; +} + +void emit_tag_changed(HSTag* tag, int monitor) { + assert(tag != NULL); + static char monitor_name[STRING_BUF_SIZE]; + snprintf(monitor_name, STRING_BUF_SIZE, "%d", monitor); + const char* argv[3]; + argv[0] = "tag_changed"; + argv[1] = tag->name->str; + argv[2] = monitor_name; + hook_emit(LENGTH(argv), argv); +} + +void hook_emit_list(const char* name, ...) { + assert(name != NULL); + int count = 1; + va_list ap; + // first count number of arguments + va_start(ap, name); + while (va_arg(ap, char*)) { + count++; + } + va_end(ap); + // then fill arguments into argv array + const char** argv = g_new(const char*, count); + int i = 0; + argv[i++] = name; + va_start(ap, name); + while (i < count) { + argv[i] = va_arg(ap, char*); + i++; + } + va_end(ap); + hook_emit(count, argv); + // cleanup + g_free(argv); +} + diff -Nru herbstluftwm-0.6.2/src/hook.h herbstluftwm-0.7.0/src/hook.h --- herbstluftwm-0.6.2/src/hook.h 2013-12-14 17:08:13.000000000 +0000 +++ herbstluftwm-0.7.0/src/hook.h 2015-10-14 13:27:40.000000000 +0000 @@ -11,9 +11,9 @@ void hook_init(); void hook_destroy(); -void hook_emit(int argc, char** argv); +void hook_emit(int argc, const char** argv); void emit_tag_changed(HSTag* tag, int monitor); -void hook_emit_list(char* name, ...); +void hook_emit_list(const char* name, ...); #endif diff -Nru herbstluftwm-0.6.2/src/ipc-server.c herbstluftwm-0.7.0/src/ipc-server.c --- herbstluftwm-0.6.2/src/ipc-server.c 2013-12-14 17:08:13.000000000 +0000 +++ herbstluftwm-0.7.0/src/ipc-server.c 1970-01-01 00:00:00.000000000 +0000 @@ -1,83 +0,0 @@ -/** Copyright 2011-2013 Thorsten Wißmann. All rights reserved. - * - * This software is licensed under the "Simplified BSD License". - * See LICENSE for details */ - -#include "globals.h" -#include "command.h" -#include "utils.h" -#include "ipc-protocol.h" -#include "ipc-server.h" - -#include -#include -#include "glib-backports.h" - -#include -#include -#include -#include - -// public callable functions -// -void ipc_init() { -} - -void ipc_destroy() { -} - -void ipc_add_connection(Window window) { - XSelectInput(g_display, window, PropertyChangeMask); - // check, if property already exists - ipc_handle_connection(window); -} - -bool ipc_handle_connection(Window win) { - XTextProperty text_prop; - if (!XGetTextProperty(g_display, win, &text_prop, ATOM(HERBST_IPC_ARGS_ATOM))) { - // if the args atom is not present any more then it already has been - // executed (e.g. after being called by ipc_add_connection()) - return false; - } - char** list_return; - int count; - if (Success != Xutf8TextPropertyToTextList(g_display, &text_prop, &list_return, &count)) { - fprintf(stderr, "herbstluftwm: Warning: could not parse the %s atom of herbstclient " - "window %d to utf8 list\n", - HERBST_IPC_ARGS_ATOM, (unsigned int)win); - XFree(text_prop.value); - return false; - } - GString* output = g_string_new(""); - int status = call_command(count, list_return, output); - // send output back - // Mark this command as executed - XDeleteProperty(g_display, win, ATOM(HERBST_IPC_ARGS_ATOM)); - XChangeProperty(g_display, win, ATOM(HERBST_IPC_OUTPUT_ATOM), - ATOM("UTF8_STRING"), 8, PropModeReplace, - (unsigned char*)output->str, 1+strlen(output->str)); - // and also set the exit status - XChangeProperty(g_display, win, ATOM(HERBST_IPC_STATUS_ATOM), - XA_ATOM, 32, PropModeReplace, (unsigned char*)&(status), 1); - // cleanup - XFreeStringList(list_return); - XFree(text_prop.value); - g_string_free(output, true); - return true; -} - -bool is_ipc_connectable(Window window) { - XClassHint hint; - if (0 == XGetClassHint(g_display, window, &hint)) { - return false; - } - bool is_ipc = false; - if (hint.res_name && hint.res_class && - !strcmp(hint.res_class, HERBST_IPC_CLASS)) { - is_ipc = true; - } - if (hint.res_name) XFree(hint.res_name); - if (hint.res_class) XFree(hint.res_class); - return is_ipc; -} - diff -Nru herbstluftwm-0.6.2/src/ipc-server.cpp herbstluftwm-0.7.0/src/ipc-server.cpp --- herbstluftwm-0.6.2/src/ipc-server.cpp 1970-01-01 00:00:00.000000000 +0000 +++ herbstluftwm-0.7.0/src/ipc-server.cpp 2015-10-14 13:27:40.000000000 +0000 @@ -0,0 +1,83 @@ +/** Copyright 2011-2013 Thorsten Wißmann. All rights reserved. + * + * This software is licensed under the "Simplified BSD License". + * See LICENSE for details */ + +#include "globals.h" +#include "command.h" +#include "utils.h" +#include "ipc-protocol.h" +#include "ipc-server.h" + +#include +#include +#include "glib-backports.h" + +#include +#include +#include +#include + +// public callable functions +// +void ipc_init() { +} + +void ipc_destroy() { +} + +void ipc_add_connection(Window window) { + XSelectInput(g_display, window, PropertyChangeMask); + // check, if property already exists + ipc_handle_connection(window); +} + +bool ipc_handle_connection(Window win) { + XTextProperty text_prop; + if (!XGetTextProperty(g_display, win, &text_prop, ATOM(HERBST_IPC_ARGS_ATOM))) { + // if the args atom is not present any more then it already has been + // executed (e.g. after being called by ipc_add_connection()) + return false; + } + char** list_return; + int count; + if (Success != Xutf8TextPropertyToTextList(g_display, &text_prop, &list_return, &count)) { + fprintf(stderr, "herbstluftwm: Warning: could not parse the %s atom of herbstclient " + "window %d to utf8 list\n", + HERBST_IPC_ARGS_ATOM, (unsigned int)win); + XFree(text_prop.value); + return false; + } + GString* output = g_string_new(""); + int status = call_command(count, list_return, output); + // send output back + // Mark this command as executed + XDeleteProperty(g_display, win, ATOM(HERBST_IPC_ARGS_ATOM)); + XChangeProperty(g_display, win, ATOM(HERBST_IPC_OUTPUT_ATOM), + ATOM("UTF8_STRING"), 8, PropModeReplace, + (unsigned char*)output->str, 1+strlen(output->str)); + // and also set the exit status + XChangeProperty(g_display, win, ATOM(HERBST_IPC_STATUS_ATOM), + XA_ATOM, 32, PropModeReplace, (unsigned char*)&(status), 1); + // cleanup + XFreeStringList(list_return); + XFree(text_prop.value); + g_string_free(output, true); + return true; +} + +bool is_ipc_connectable(Window window) { + XClassHint hint; + if (0 == XGetClassHint(g_display, window, &hint)) { + return false; + } + bool is_ipc = false; + if (hint.res_name && hint.res_class && + !strcmp(hint.res_class, HERBST_IPC_CLASS)) { + is_ipc = true; + } + if (hint.res_name) XFree(hint.res_name); + if (hint.res_class) XFree(hint.res_class); + return is_ipc; +} + diff -Nru herbstluftwm-0.6.2/src/key.c herbstluftwm-0.7.0/src/key.c --- herbstluftwm-0.6.2/src/key.c 2014-03-26 23:54:22.000000000 +0000 +++ herbstluftwm-0.7.0/src/key.c 1970-01-01 00:00:00.000000000 +0000 @@ -1,425 +0,0 @@ -/** Copyright 2011-2013 Thorsten Wißmann. All rights reserved. - * - * This software is licensed under the "Simplified BSD License". - * See LICENSE for details */ - -#include "key.h" -#include "globals.h" -#include "utils.h" -#include "ipc-protocol.h" -#include "command.h" - -#include -#include -#include -#include "glib-backports.h" -#include -#include - -static unsigned int numlockmask = 0; -#define CLEANMASK(mask) (mask & ~(numlockmask|LockMask)) - -unsigned int* get_numlockmask_ptr() { - return &numlockmask; -} - -GList* g_key_binds = NULL; - -void key_init() { - update_numlockmask(); -} - -void key_destroy() { - key_remove_all_binds(); -} - -void key_remove_all_binds() { - g_list_free_full(g_key_binds, (GDestroyNotify)keybinding_free); - g_key_binds = NULL; - regrab_keys(); -} - -void keybinding_free(KeyBinding* binding) { - argv_free(binding->cmd_argc, binding->cmd_argv); - g_free(binding); -} - -typedef struct { - char* name; - unsigned int mask; -} Name2Modifier; - -static Name2Modifier g_modifier_names[] = { - { "Mod1", Mod1Mask }, - { "Mod2", Mod2Mask }, - { "Mod3", Mod3Mask }, - { "Mod4", Mod4Mask }, - { "Mod5", Mod5Mask }, - { "Alt", Mod1Mask }, - { "Super", Mod4Mask }, - { "Shift", ShiftMask }, - { "Control", ControlMask }, - { "Ctrl", ControlMask }, -}; - -unsigned int modifiername2mask(const char* name) { - Name2Modifier* elem; - elem = STATIC_TABLE_FIND_STR(Name2Modifier, g_modifier_names, name, - (char*)name); - return elem ? elem->mask : 0; -} - -/** - * \brief finds a (any) modifier in mask and returns its name - * - * \return the name as a char string. You must not free it. - */ -char* modifiermask2name(unsigned int mask) { - for (int i = 0; i < LENGTH(g_modifier_names); i++) { - if (g_modifier_names[i].mask & mask) { - return g_modifier_names[i].name; - } - } - return NULL; -} - -int keybind(int argc, char** argv, GString* output) { - if (argc <= 2) { - return HERBST_NEED_MORE_ARGS; - } - KeyBinding new_bind; - // get keycode - if (!string2key(argv[1], &(new_bind.modifiers), &(new_bind.keysym))) { - g_string_append_printf(output, - "%s: No such KeySym/modifier\n", argv[0]); - return HERBST_INVALID_ARGUMENT; - } - KeyCode keycode = XKeysymToKeycode(g_display, new_bind.keysym); - if (!keycode) { - g_string_append_printf(output, - "%s: No keycode for symbol %s\n", - argv[0], XKeysymToString(new_bind.keysym)); - return HERBST_INVALID_ARGUMENT; - } - // remove existing binding with same keysym/modifiers - key_remove_bind_with_keysym(new_bind.modifiers, new_bind.keysym); - // create a copy of the command to execute on this key - new_bind.cmd_argc = argc - 2; - new_bind.cmd_argv = argv_duplicate(new_bind.cmd_argc, argv+2); - // add keybinding - KeyBinding* data = g_new(KeyBinding, 1); - *data = new_bind; - g_key_binds = g_list_append(g_key_binds, data); - // grab for events on this keycode - grab_keybind(data, NULL); - return 0; -} - -bool string2modifiers(char* string, unsigned int* modmask) { - // example strings: "Mod1-space" "Mod4+f" "f" - // split string at "+-" - char** splitted = g_strsplit_set(string, KEY_COMBI_SEPARATORS, 0); - // this should give at least one part - if (NULL == splitted[0]) { - fprintf(stderr, "warning: empty keysym\n"); - g_strfreev(splitted); - return false; - } - // all parts except last one are modifiers - int i; - *modmask = 0; - for (i = 0; splitted[i+1] != NULL; i++) { - // while the i'th element is not the last part - unsigned int cur_mask = modifiername2mask(splitted[i]); - if (cur_mask == 0) { - fprintf(stderr, "warning: unknown Modifier \"%s\"\n", splitted[i]); - g_strfreev(splitted); - return false; - } - *modmask |= cur_mask; - } - // splitted string is not needed anymore - g_strfreev(splitted); - return true; -} - -bool string2key(char* string, unsigned int* modmask, KeySym* keysym) { - if (!string2modifiers(string, modmask)) { - return false; - } - // last one is the key - char* last_token = strlasttoken(string, KEY_COMBI_SEPARATORS); - *keysym = XStringToKeysym(last_token); - if (*keysym == NoSymbol) { - fprintf(stderr, "warning: unknown KeySym \"%s\"\n", last_token); - return false; - } - return true; -} - -static gint keysym_equals(const KeyBinding* a, const KeyBinding* b) { - bool equal = (CLEANMASK(a->modifiers) == CLEANMASK(b->modifiers)); - equal = equal && (a->keysym == b->keysym); - return equal ? 0 : -1; -} - -void handle_key_press(XEvent* ev) { - KeyBinding pressed; - pressed.keysym = XkbKeycodeToKeysym(g_display, ev->xkey.keycode, 0, 0); - pressed.modifiers = ev->xkey.state; - GList* element = g_list_find_custom(g_key_binds, &pressed, (GCompareFunc)keysym_equals); - if (element && element->data) { - KeyBinding* found = (KeyBinding*)element->data; - // duplicate the args in the case this keybinding removes itself - char** argv = argv_duplicate(found->cmd_argc, found->cmd_argv); - int argc = found->cmd_argc; - // call the command - call_command_no_output(argc, argv); - argv_free(argc, argv); - } -} - -int keyunbind(int argc, char** argv, GString* output) { - if (argc <= 1) { - return HERBST_NEED_MORE_ARGS; - } - // remove all keybinds if wanted - if (!strcmp(argv[1], "-F") || !strcmp(argv[1], "--all")) { - key_remove_all_binds(); - return 0; - } - unsigned int modifiers; - KeySym keysym; - // get keycode - if (!string2key(argv[1], &modifiers, &keysym)) { - g_string_append_printf(output, - "%s: No such KeySym/modifier\n", argv[0]); - return HERBST_INVALID_ARGUMENT; - } - if (key_remove_bind_with_keysym(modifiers, keysym) == false) { - g_string_append_printf(output, - "%s: Key \"%s\" is not bound\n", argv[0], argv[1]); - } - regrab_keys(); - return 0; -} - -bool key_remove_bind_with_keysym(unsigned int modifiers, KeySym keysym){ - KeyBinding bind; - bind.modifiers = modifiers; - bind.keysym = keysym; - // search this keysym in list and remove it - GList* element = g_list_find_custom(g_key_binds, &bind, (GCompareFunc)keysym_equals); - if (!element) { - return false; - } - KeyBinding* data = element->data; - keybinding_free(data); - g_key_binds = g_list_remove_link(g_key_binds, element); - g_list_free_1(element); - return true; -} - -void regrab_keys() { - update_numlockmask(); - // init modifiers after updating numlockmask - XUngrabKey(g_display, AnyKey, AnyModifier, g_root); // remove all current grabs - g_list_foreach(g_key_binds, (GFunc)grab_keybind, NULL); -} - -void grab_keybind(KeyBinding* binding, void* useless_pointer) { - unsigned int modifiers[] = { 0, LockMask, numlockmask, numlockmask|LockMask }; - KeyCode keycode = XKeysymToKeycode(g_display, binding->keysym); - if (!keycode) { - // ignore unknown keysyms - return; - } - // grab key for each modifier that is ignored (capslock, numlock) - for (int i = 0; i < LENGTH(modifiers); i++) { - XGrabKey(g_display, keycode, modifiers[i]|binding->modifiers, g_root, - True, GrabModeAsync, GrabModeAsync); - } - // mark the keybinding as enabled - binding->enabled = true; -} - -void ungrab_keybind(KeyBinding* binding, void* useless_pointer) { - unsigned int modifiers[] = { 0, LockMask, numlockmask, numlockmask|LockMask }; - KeyCode keycode = XKeysymToKeycode(g_display, binding->keysym); - if (!keycode) { - // ignore unknown keysyms - return; - } - // grab key for each modifier that is ignored (capslock, numlock) - for (int i = 0; i < LENGTH(modifiers); i++) { - XUngrabKey(g_display, keycode, modifiers[i]|binding->modifiers, g_root); - } - binding->enabled = false; -} - - -// update the numlockmask -// from dwm.c -void update_numlockmask() { - unsigned int i, j; - XModifierKeymap *modmap; - - numlockmask = 0; - modmap = XGetModifierMapping(g_display); - for(i = 0; i < 8; i++) - for(j = 0; j < modmap->max_keypermod; j++) - if(modmap->modifiermap[i * modmap->max_keypermod + j] - == XKeysymToKeycode(g_display, XK_Num_Lock)) - numlockmask = (1 << i); - XFreeModifiermap(modmap); -} - -/** - * \brief converts the given binding to a GString - * \return the new created GString. You have to destroy it. - */ -GString* keybinding_to_g_string(KeyBinding* binding) { - GString* str = g_string_new(""); - - /* add modifiers */ - unsigned int old_mask = 0, new_mask = binding->modifiers; - while (new_mask != 0 && new_mask != old_mask) { - old_mask = new_mask; - - /* try to find a modifier */ - char* name = modifiermask2name(old_mask); - if (!name) { - break; - } - g_string_append(str, name); - g_string_append_c(str, KEY_COMBI_SEPARATORS[0]); - /* remove found mask from mask */ - new_mask = old_mask & ~ modifiername2mask(name); - } - - /* add keysym */ - char* name = XKeysymToString(binding->keysym); - if (!name) { - g_warning("XKeysymToString failed! using \'?\' instead\n"); - name = "?"; - } - g_string_append(str, name); - - return str; -} - -struct key_find_context { - GString* output; - char* needle; - size_t needle_len; -}; - -static void key_find_binds_helper(KeyBinding* b, struct key_find_context* c) { - GString* name = keybinding_to_g_string(b); - if (!strncmp(c->needle, name->str, c->needle_len)) { - /* add to output if key starts with searched needle */ - g_string_append(c->output, name->str); - g_string_append_c(c->output, '\n'); - } - g_string_free(name, true); -} - -void key_find_binds(char* needle, GString* output) { - struct key_find_context c = { - output, needle, strlen(needle) - }; - g_list_foreach(g_key_binds, (GFunc)key_find_binds_helper, &c); -} - -static void key_list_binds_helper(KeyBinding* b, GString* output) { - // add keybinding - GString* name = keybinding_to_g_string(b); - g_string_append(output, name->str); - g_string_free(name, true); - // add associated command - for (int i = 0; i < b->cmd_argc; i++) { - g_string_append_c(output, '\t'); - g_string_append(output, b->cmd_argv[i]); - } - g_string_append_c(output, '\n'); -} - -int key_list_binds(int argc, char** argv, GString* output) { - g_list_foreach(g_key_binds, (GFunc)key_list_binds_helper, output); - return 0; -} - -void complete_against_keysyms(char* needle, char* prefix, GString* output) { - // get all possible keysyms - int min, max; - XDisplayKeycodes(g_display, &min, &max); - int kc_count = max - min + 1; - int ks_per_kc; // count of keysysms per keycode - KeySym* keysyms; - keysyms = XGetKeyboardMapping(g_display, min, kc_count, &ks_per_kc); - // only symbols at a position i*ks_per_kc are symbols that are recieved in - // a keyevent, it should be the symbol for the keycode if no modifier is - // pressed - for (int i = 0; i < kc_count; i++) { - if (keysyms[i * ks_per_kc] != NoSymbol) { - char* str = XKeysymToString(keysyms[i * ks_per_kc]); - try_complete_prefix(needle, str, prefix, output); - } - } - XFree(keysyms); -} - -void complete_against_modifiers(char* needle, char seperator, - char* prefix, GString* output) { - GString* buf = g_string_sized_new(20); - for (int i = 0; i < LENGTH(g_modifier_names); i++) { - g_string_printf(buf, "%s%c", g_modifier_names[i].name, seperator); - try_complete_prefix_partial(needle, buf->str, prefix, output); - } - g_string_free(buf, true); -} - -static void key_set_keymask_helper(KeyBinding* b, regex_t *keymask_regex) { - // add keybinding - bool enabled = true; - if (keymask_regex != NULL) { - GString* name = keybinding_to_g_string(b); - regmatch_t match; - int status = regexec(keymask_regex, name->str, 1, &match, 0); - // only accept it, if it matches the entire string - if (status == 0 - && match.rm_so == 0 - && match.rm_eo == strlen(name->str)) { - enabled = true; - } else { - // Keybinding did not match, therefore we disable it - enabled = false; - } - g_string_free(name, true); - } - - if (enabled && !b->enabled) { - grab_keybind(b, NULL); - } else if(!enabled && b->enabled) { - ungrab_keybind(b, NULL); - } -} - -void key_set_keymask(HSTag *tag, HSClient *client) { - regex_t keymask_regex; - if (client && strlen(client->keymask->str) > 0) { - int status = regcomp(&keymask_regex, client->keymask->str, REG_EXTENDED); - if (status == 0) { - g_list_foreach(g_key_binds, (GFunc)key_set_keymask_helper, - &keymask_regex); - return; - } else { - char buf[ERROR_STRING_BUF_SIZE]; - regerror(status, &keymask_regex, buf, ERROR_STRING_BUF_SIZE); - HSDebug("keymask: Can not parse regex \"%s\" from keymask: %s", - client->keymask->str, buf); - } - } - // Enable all keys again - g_list_foreach(g_key_binds, (GFunc)key_set_keymask_helper, 0); -} diff -Nru herbstluftwm-0.6.2/src/key.cpp herbstluftwm-0.7.0/src/key.cpp --- herbstluftwm-0.6.2/src/key.cpp 1970-01-01 00:00:00.000000000 +0000 +++ herbstluftwm-0.7.0/src/key.cpp 2015-10-14 13:27:40.000000000 +0000 @@ -0,0 +1,425 @@ +/** Copyright 2011-2013 Thorsten Wißmann. All rights reserved. + * + * This software is licensed under the "Simplified BSD License". + * See LICENSE for details */ + +#include "key.h" +#include "globals.h" +#include "utils.h" +#include "ipc-protocol.h" +#include "command.h" + +#include +#include +#include +#include "glib-backports.h" +#include +#include + +static unsigned int numlockmask = 0; +#define CLEANMASK(mask) (mask & ~(numlockmask|LockMask)) + +unsigned int* get_numlockmask_ptr() { + return &numlockmask; +} + +static GList* g_key_binds = NULL; + +void key_init() { + update_numlockmask(); +} + +void key_destroy() { + key_remove_all_binds(); +} + +void key_remove_all_binds() { + g_list_free_full(g_key_binds, (GDestroyNotify)keybinding_free); + g_key_binds = NULL; + regrab_keys(); +} + +void keybinding_free(KeyBinding* binding) { + argv_free(binding->cmd_argc, binding->cmd_argv); + g_free(binding); +} + +typedef struct { + const char* name; + unsigned int mask; +} Name2Modifier; + +static Name2Modifier g_modifier_names[] = { + { "Mod1", Mod1Mask }, + { "Mod2", Mod2Mask }, + { "Mod3", Mod3Mask }, + { "Mod4", Mod4Mask }, + { "Mod5", Mod5Mask }, + { "Alt", Mod1Mask }, + { "Super", Mod4Mask }, + { "Shift", ShiftMask }, + { "Control", ControlMask }, + { "Ctrl", ControlMask }, +}; + +unsigned int modifiername2mask(const char* name) { + Name2Modifier* elem; + elem = STATIC_TABLE_FIND_STR(Name2Modifier, g_modifier_names, name, + (char*)name); + return elem ? elem->mask : 0; +} + +/** + * \brief finds a (any) modifier in mask and returns its name + * + * \return the name as a char string. You must not free it. + */ +const char* modifiermask2name(unsigned int mask) { + for (int i = 0; i < LENGTH(g_modifier_names); i++) { + if (g_modifier_names[i].mask & mask) { + return g_modifier_names[i].name; + } + } + return NULL; +} + +int keybind(int argc, char** argv, GString* output) { + if (argc <= 2) { + return HERBST_NEED_MORE_ARGS; + } + KeyBinding new_bind; + // get keycode + if (!string2key(argv[1], &(new_bind.modifiers), &(new_bind.keysym))) { + g_string_append_printf(output, + "%s: No such KeySym/modifier\n", argv[0]); + return HERBST_INVALID_ARGUMENT; + } + KeyCode keycode = XKeysymToKeycode(g_display, new_bind.keysym); + if (!keycode) { + g_string_append_printf(output, + "%s: No keycode for symbol %s\n", + argv[0], XKeysymToString(new_bind.keysym)); + return HERBST_INVALID_ARGUMENT; + } + // remove existing binding with same keysym/modifiers + key_remove_bind_with_keysym(new_bind.modifiers, new_bind.keysym); + // create a copy of the command to execute on this key + new_bind.cmd_argc = argc - 2; + new_bind.cmd_argv = argv_duplicate(new_bind.cmd_argc, argv+2); + // add keybinding + KeyBinding* data = g_new(KeyBinding, 1); + *data = new_bind; + g_key_binds = g_list_append(g_key_binds, data); + // grab for events on this keycode + grab_keybind(data, NULL); + return 0; +} + +bool string2modifiers(char* string, unsigned int* modmask) { + // example strings: "Mod1-space" "Mod4+f" "f" + // split string at "+-" + char** splitted = g_strsplit_set(string, KEY_COMBI_SEPARATORS, 0); + // this should give at least one part + if (NULL == splitted[0]) { + fprintf(stderr, "warning: empty keysym\n"); + g_strfreev(splitted); + return false; + } + // all parts except last one are modifiers + int i; + *modmask = 0; + for (i = 0; splitted[i+1] != NULL; i++) { + // while the i'th element is not the last part + unsigned int cur_mask = modifiername2mask(splitted[i]); + if (cur_mask == 0) { + fprintf(stderr, "warning: unknown Modifier \"%s\"\n", splitted[i]); + g_strfreev(splitted); + return false; + } + *modmask |= cur_mask; + } + // splitted string is not needed anymore + g_strfreev(splitted); + return true; +} + +bool string2key(char* string, unsigned int* modmask, KeySym* keysym) { + if (!string2modifiers(string, modmask)) { + return false; + } + // last one is the key + const char* last_token = strlasttoken(string, KEY_COMBI_SEPARATORS); + *keysym = XStringToKeysym(last_token); + if (*keysym == NoSymbol) { + fprintf(stderr, "warning: unknown KeySym \"%s\"\n", last_token); + return false; + } + return true; +} + +static gint keysym_equals(const KeyBinding* a, const KeyBinding* b) { + bool equal = (CLEANMASK(a->modifiers) == CLEANMASK(b->modifiers)); + equal = equal && (a->keysym == b->keysym); + return equal ? 0 : -1; +} + +void handle_key_press(XEvent* ev) { + KeyBinding pressed; + pressed.keysym = XkbKeycodeToKeysym(g_display, ev->xkey.keycode, 0, 0); + pressed.modifiers = ev->xkey.state; + GList* element = g_list_find_custom(g_key_binds, &pressed, (GCompareFunc)keysym_equals); + if (element && element->data) { + KeyBinding* found = (KeyBinding*)element->data; + // duplicate the args in the case this keybinding removes itself + char** argv = argv_duplicate(found->cmd_argc, found->cmd_argv); + int argc = found->cmd_argc; + // call the command + call_command_no_output(argc, argv); + argv_free(argc, argv); + } +} + +int keyunbind(int argc, char** argv, GString* output) { + if (argc <= 1) { + return HERBST_NEED_MORE_ARGS; + } + // remove all keybinds if wanted + if (!strcmp(argv[1], "-F") || !strcmp(argv[1], "--all")) { + key_remove_all_binds(); + return 0; + } + unsigned int modifiers; + KeySym keysym; + // get keycode + if (!string2key(argv[1], &modifiers, &keysym)) { + g_string_append_printf(output, + "%s: No such KeySym/modifier\n", argv[0]); + return HERBST_INVALID_ARGUMENT; + } + if (key_remove_bind_with_keysym(modifiers, keysym) == false) { + g_string_append_printf(output, + "%s: Key \"%s\" is not bound\n", argv[0], argv[1]); + } + regrab_keys(); + return 0; +} + +bool key_remove_bind_with_keysym(unsigned int modifiers, KeySym keysym){ + KeyBinding bind; + bind.modifiers = modifiers; + bind.keysym = keysym; + // search this keysym in list and remove it + GList* element = g_list_find_custom(g_key_binds, &bind, (GCompareFunc)keysym_equals); + if (!element) { + return false; + } + KeyBinding* data = (KeyBinding*)element->data; + keybinding_free(data); + g_key_binds = g_list_remove_link(g_key_binds, element); + g_list_free_1(element); + return true; +} + +void regrab_keys() { + update_numlockmask(); + // init modifiers after updating numlockmask + XUngrabKey(g_display, AnyKey, AnyModifier, g_root); // remove all current grabs + g_list_foreach(g_key_binds, (GFunc)grab_keybind, NULL); +} + +void grab_keybind(KeyBinding* binding, void* useless_pointer) { + unsigned int modifiers[] = { 0, LockMask, numlockmask, numlockmask|LockMask }; + KeyCode keycode = XKeysymToKeycode(g_display, binding->keysym); + if (!keycode) { + // ignore unknown keysyms + return; + } + // grab key for each modifier that is ignored (capslock, numlock) + for (int i = 0; i < LENGTH(modifiers); i++) { + XGrabKey(g_display, keycode, modifiers[i]|binding->modifiers, g_root, + True, GrabModeAsync, GrabModeAsync); + } + // mark the keybinding as enabled + binding->enabled = true; +} + +void ungrab_keybind(KeyBinding* binding, void* useless_pointer) { + unsigned int modifiers[] = { 0, LockMask, numlockmask, numlockmask|LockMask }; + KeyCode keycode = XKeysymToKeycode(g_display, binding->keysym); + if (!keycode) { + // ignore unknown keysyms + return; + } + // grab key for each modifier that is ignored (capslock, numlock) + for (int i = 0; i < LENGTH(modifiers); i++) { + XUngrabKey(g_display, keycode, modifiers[i]|binding->modifiers, g_root); + } + binding->enabled = false; +} + + +// update the numlockmask +// from dwm.c +void update_numlockmask() { + unsigned int i, j; + XModifierKeymap *modmap; + + numlockmask = 0; + modmap = XGetModifierMapping(g_display); + for(i = 0; i < 8; i++) + for(j = 0; j < modmap->max_keypermod; j++) + if(modmap->modifiermap[i * modmap->max_keypermod + j] + == XKeysymToKeycode(g_display, XK_Num_Lock)) + numlockmask = (1 << i); + XFreeModifiermap(modmap); +} + +/** + * \brief converts the given binding to a GString + * \return the new created GString. You have to destroy it. + */ +GString* keybinding_to_g_string(KeyBinding* binding) { + GString* str = g_string_new(""); + + /* add modifiers */ + unsigned int old_mask = 0, new_mask = binding->modifiers; + while (new_mask != 0 && new_mask != old_mask) { + old_mask = new_mask; + + /* try to find a modifier */ + const char* name = modifiermask2name(old_mask); + if (!name) { + break; + } + g_string_append(str, name); + g_string_append_c(str, KEY_COMBI_SEPARATORS[0]); + /* remove found mask from mask */ + new_mask = old_mask & ~ modifiername2mask(name); + } + + /* add keysym */ + const char* name = XKeysymToString(binding->keysym); + if (!name) { + g_warning("XKeysymToString failed! using \'?\' instead\n"); + name = "?"; + } + g_string_append(str, name); + + return str; +} + +struct key_find_context { + GString* output; + const char* needle; + size_t needle_len; +}; + +static void key_find_binds_helper(KeyBinding* b, struct key_find_context* c) { + GString* name = keybinding_to_g_string(b); + if (!strncmp(c->needle, name->str, c->needle_len)) { + /* add to output if key starts with searched needle */ + g_string_append(c->output, name->str); + g_string_append_c(c->output, '\n'); + } + g_string_free(name, true); +} + +void key_find_binds(const char* needle, GString* output) { + struct key_find_context c = { + output, needle, strlen(needle) + }; + g_list_foreach(g_key_binds, (GFunc)key_find_binds_helper, &c); +} + +static void key_list_binds_helper(KeyBinding* b, GString* output) { + // add keybinding + GString* name = keybinding_to_g_string(b); + g_string_append(output, name->str); + g_string_free(name, true); + // add associated command + for (int i = 0; i < b->cmd_argc; i++) { + g_string_append_c(output, '\t'); + g_string_append(output, b->cmd_argv[i]); + } + g_string_append_c(output, '\n'); +} + +int key_list_binds(int argc, char** argv, GString* output) { + g_list_foreach(g_key_binds, (GFunc)key_list_binds_helper, output); + return 0; +} + +void complete_against_keysyms(const char* needle, char* prefix, GString* output) { + // get all possible keysyms + int min, max; + XDisplayKeycodes(g_display, &min, &max); + int kc_count = max - min + 1; + int ks_per_kc; // count of keysysms per keycode + KeySym* keysyms; + keysyms = XGetKeyboardMapping(g_display, min, kc_count, &ks_per_kc); + // only symbols at a position i*ks_per_kc are symbols that are recieved in + // a keyevent, it should be the symbol for the keycode if no modifier is + // pressed + for (int i = 0; i < kc_count; i++) { + if (keysyms[i * ks_per_kc] != NoSymbol) { + char* str = XKeysymToString(keysyms[i * ks_per_kc]); + try_complete_prefix(needle, str, prefix, output); + } + } + XFree(keysyms); +} + +void complete_against_modifiers(const char* needle, char seperator, + char* prefix, GString* output) { + GString* buf = g_string_sized_new(20); + for (int i = 0; i < LENGTH(g_modifier_names); i++) { + g_string_printf(buf, "%s%c", g_modifier_names[i].name, seperator); + try_complete_prefix_partial(needle, buf->str, prefix, output); + } + g_string_free(buf, true); +} + +static void key_set_keymask_helper(KeyBinding* b, regex_t *keymask_regex) { + // add keybinding + bool enabled = true; + if (keymask_regex != NULL) { + GString* name = keybinding_to_g_string(b); + regmatch_t match; + int status = regexec(keymask_regex, name->str, 1, &match, 0); + // only accept it, if it matches the entire string + if (status == 0 + && match.rm_so == 0 + && match.rm_eo == strlen(name->str)) { + enabled = true; + } else { + // Keybinding did not match, therefore we disable it + enabled = false; + } + g_string_free(name, true); + } + + if (enabled && !b->enabled) { + grab_keybind(b, NULL); + } else if(!enabled && b->enabled) { + ungrab_keybind(b, NULL); + } +} + +void key_set_keymask(HSTag *tag, HSClient *client) { + regex_t keymask_regex; + if (client && strlen(client->keymask->str) > 0) { + int status = regcomp(&keymask_regex, client->keymask->str, REG_EXTENDED); + if (status == 0) { + g_list_foreach(g_key_binds, (GFunc)key_set_keymask_helper, + &keymask_regex); + return; + } else { + char buf[ERROR_STRING_BUF_SIZE]; + regerror(status, &keymask_regex, buf, ERROR_STRING_BUF_SIZE); + HSDebug("keymask: Can not parse regex \"%s\" from keymask: %s", + client->keymask->str, buf); + } + } + // Enable all keys again + g_list_foreach(g_key_binds, (GFunc)key_set_keymask_helper, 0); +} diff -Nru herbstluftwm-0.6.2/src/key.h herbstluftwm-0.7.0/src/key.h --- herbstluftwm-0.6.2/src/key.h 2014-03-26 23:54:22.000000000 +0000 +++ herbstluftwm-0.7.0/src/key.h 2015-10-14 13:27:40.000000000 +0000 @@ -22,7 +22,7 @@ } KeyBinding; unsigned int modifiername2mask(const char* name); -char* modifiermask2name(unsigned int mask); +const char* modifiermask2name(unsigned int mask); bool string2modifiers(char* string, unsigned int* modmask); bool string2key(char* string, unsigned int* modmask, KeySym* keysym); @@ -35,10 +35,10 @@ bool key_remove_bind_with_keysym(unsigned int modifiers, KeySym sym); void key_remove_all_binds(); GString* keybinding_to_g_string(KeyBinding* binding); -void key_find_binds(char* needle, GString* output); -void complete_against_modifiers(char* needle, char seperator, +void key_find_binds(const char* needle, GString* output); +void complete_against_modifiers(const char* needle, char seperator, char* prefix, GString* output); -void complete_against_keysyms(char* needle, char* prefix, GString* output); +void complete_against_keysyms(const char* needle, char* prefix, GString* output); void regrab_keys(); void grab_keybind(KeyBinding* binding, void* useless_pointer); void update_numlockmask(); diff -Nru herbstluftwm-0.6.2/src/layout.c herbstluftwm-0.7.0/src/layout.c --- herbstluftwm-0.6.2/src/layout.c 2014-03-26 23:54:22.000000000 +0000 +++ herbstluftwm-0.7.0/src/layout.c 1970-01-01 00:00:00.000000000 +0000 @@ -1,1993 +0,0 @@ -/** Copyright 2011-2013 Thorsten Wißmann. All rights reserved. - * - * This software is licensed under the "Simplified BSD License". - * See LICENSE for details */ - -#include "clientlist.h" -#include "globals.h" -#include "utils.h" -#include "x11-utils.h" -#include "hook.h" -#include "ewmh.h" -#include "ipc-protocol.h" -#include "settings.h" -#include "layout.h" -#include "stack.h" -#include "monitor.h" -#include "floating.h" - -#include -#include "glib-backports.h" -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include - -int* g_frame_border_width; -int* g_frame_border_inner_width; -int* g_always_show_frame; -int* g_default_frame_layout; -int* g_frame_bg_transparent; -int* g_frame_transparent_width; -int* g_direction_external_only; -int* g_gapless_grid; -int* g_smart_frame_surroundings; -int* g_smart_window_surroundings; -int* g_focus_crosses_monitor_boundaries; -int* g_frame_padding; -unsigned long g_frame_border_active_color; -unsigned long g_frame_border_normal_color; -unsigned long g_frame_border_inner_color; -unsigned long g_frame_bg_active_color; -unsigned long g_frame_bg_normal_color; -unsigned long g_frame_active_opacity; -unsigned long g_frame_normal_opacity; -char* g_tree_style = NULL; - -char* g_align_names[] = { - "vertical", - "horizontal", -}; - -char* g_layout_names[] = { - "vertical", - "horizontal", - "max", - "grid", - NULL, -}; - -static void fetch_frame_colors() { - // load settings - g_frame_gap = &(settings_find("frame_gap")->value.i); - g_frame_padding = &(settings_find("frame_padding")->value.i); - g_window_gap = &(settings_find("window_gap")->value.i); - g_frame_border_width = &(settings_find("frame_border_width")->value.i); - g_frame_border_inner_width = &(settings_find("frame_border_inner_width")->value.i); - g_always_show_frame = &(settings_find("always_show_frame")->value.i); - g_frame_bg_transparent = &(settings_find("frame_bg_transparent")->value.i); - g_frame_transparent_width = &(settings_find("frame_transparent_width")->value.i); - g_default_frame_layout = &(settings_find("default_frame_layout")->value.i); - g_direction_external_only = &(settings_find("default_direction_external_only")->value.i); - g_gapless_grid = &(settings_find("gapless_grid")->value.i); - g_smart_frame_surroundings = &(settings_find("smart_frame_surroundings")->value.i); - g_smart_window_surroundings = &(settings_find("smart_window_surroundings")->value.i); - g_focus_crosses_monitor_boundaries = &(settings_find("focus_crosses_monitor_boundaries")->value.i); - *g_default_frame_layout = CLAMP(*g_default_frame_layout, 0, LAYOUT_COUNT - 1); - char* str = settings_find_string("frame_border_normal_color"); - g_frame_border_normal_color = getcolor(str); - str = settings_find_string("frame_border_active_color"); - g_frame_border_active_color = getcolor(str); - str = settings_find_string("frame_border_inner_color"); - g_frame_border_inner_color = getcolor(str); - // background color - str = settings_find_string("frame_bg_normal_color"); - g_frame_bg_normal_color = getcolor(str); - str = settings_find_string("frame_bg_active_color"); - g_frame_bg_active_color = getcolor(str); - g_frame_active_opacity = CLAMP(settings_find("frame_active_opacity")->value.i, 0, 100); - g_frame_normal_opacity = CLAMP(settings_find("frame_normal_opacity")->value.i, 0, 100); - - // tree style - g_tree_style = settings_find_string("tree_style"); - if (g_utf8_strlen(g_tree_style, -1) < 8) { - g_warning("too few characters in setting tree_style\n"); - // ensure that it is long enough - char* argv[] = { "set", "tree_style", "01234567" }; - settings_set_command(LENGTH(argv), argv, NULL); - } -} - -void layout_init() { - fetch_frame_colors(); -} -void reset_frame_colors() { - fetch_frame_colors(); - all_monitors_apply_layout(); -} - -void layout_destroy() { -} - - -/* create a new frame - * you can either specify a frame or a tag as its parent - */ -HSFrame* frame_create_empty(HSFrame* parent, HSTag* parenttag) { - HSFrame* frame = g_new0(HSFrame, 1); - frame->type = TYPE_CLIENTS; - frame->window_visible = false; - frame->content.clients.layout = *g_default_frame_layout; - frame->parent = parent; - frame->tag = parent ? parent->tag : parenttag; - // set window attributes - XSetWindowAttributes at; - at.background_pixel = getcolor("red"); - at.background_pixmap = ParentRelative; - at.override_redirect = True; - at.bit_gravity = StaticGravity; - at.event_mask = SubstructureRedirectMask|SubstructureNotifyMask - |ExposureMask|VisibilityChangeMask - |EnterWindowMask|LeaveWindowMask|FocusChangeMask; - frame->window = XCreateWindow(g_display, g_root, - 42, 42, 42, 42, *g_frame_border_width, - DefaultDepth(g_display, DefaultScreen(g_display)), - CopyFromParent, - DefaultVisual(g_display, DefaultScreen(g_display)), - CWOverrideRedirect | CWBackPixmap | CWEventMask, &at); - // insert it to the stack - frame->slice = slice_create_frame(frame->window); - stack_insert_slice(frame->tag->stack, frame->slice); - // set wm_class for window - XClassHint *hint = XAllocClassHint(); - hint->res_name = HERBST_FRAME_CLASS; - hint->res_class = HERBST_FRAME_CLASS; - XSetClassHint(g_display, frame->window, hint); - XFree(hint); - return frame; -} - -void frame_insert_client(HSFrame* frame, struct HSClient* client) { - if (frame->type == TYPE_CLIENTS) { - // insert it here - HSClient** buf = frame->content.clients.buf; - // append it to buf - size_t count = frame->content.clients.count; - count++; - // insert it after the selection - int index = frame->content.clients.selection + 1; - index = CLAMP(index, 0, count - 1); - buf = g_renew(HSClient*, buf, count); - // shift other windows to the back to insert the new one at index - memmove(buf + index + 1, buf + index, sizeof(*buf) * (count - index - 1)); - buf[index] = client; - // write results back - frame->content.clients.count = count; - frame->content.clients.buf = buf; - // check for focus - if (g_cur_frame == frame - && frame->content.clients.selection >= (count-1)) { - frame->content.clients.selection = count - 1; - client_window_focus(client); - } - } else { /* frame->type == TYPE_FRAMES */ - HSLayout* layout = &frame->content.layout; - frame_insert_client((layout->selection == 0)? layout->a : layout->b, client); - } -} - -HSFrame* lookup_frame(HSFrame* root, char *index) { - if (index == NULL || index[0] == '\0') return root; - if (root->type == TYPE_CLIENTS) return root; - - HSFrame* new_root; - char *new_index = index + 1; - HSLayout* layout = &root->content.layout; - - switch (index[0]) { - case '0': new_root = layout->a; break; - case '1': new_root = layout->b; break; - /* opposite selection */ - case '/': new_root = (layout->selection == 0) - ? layout->b - : layout->a; - break; - /* else just follow selection */ - case '@': new_index = index; - case '.': - default: new_root = (layout->selection == 0) - ? layout->a - : layout->b; break; - } - return lookup_frame(new_root, new_index); -} - -HSFrame* find_frame_with_client(HSFrame* frame, struct HSClient* client) { - if (frame->type == TYPE_CLIENTS) { - HSClient** buf = frame->content.clients.buf; - size_t count = frame->content.clients.count; - for (int i = 0; i < count; i++) { - if (buf[i] == client) { - return frame; - } - } - return NULL; - } else { /* frame->type == TYPE_FRAMES */ - HSFrame* found = find_frame_with_client(frame->content.layout.a, client); - if (!found) { - found = find_frame_with_client(frame->content.layout.b, client); - } - return found; - } -} - -bool frame_remove_client(HSFrame* frame, HSClient* client) { - if (frame->type == TYPE_CLIENTS) { - HSClient** buf = frame->content.clients.buf; - size_t count = frame->content.clients.count; - int i; - for (i = 0; i < count; i++) { - if (buf[i] == client) { - // if window was found - // them remove it - memmove(buf+i, buf+i+1, sizeof(buf[0])*(count - i - 1)); - count--; - buf = g_renew(HSClient*, buf, count); - frame->content.clients.buf = buf; - frame->content.clients.count = count; - // find out new selection - int selection = frame->content.clients.selection; - // if selection was before removed window - // then do nothing - // else shift it by 1 - selection -= (selection < i) ? 0 : 1; - // ensure, that it's a valid index - selection = count ? CLAMP(selection, 0, count-1) : 0; - frame->content.clients.selection = selection; - return true; - } - } - return false; - } else { /* frame->type == TYPE_FRAMES */ - bool found = frame_remove_client(frame->content.layout.a, client); - found = found || frame_remove_client(frame->content.layout.b, client); - return found; - } -} - -void frame_destroy(HSFrame* frame, HSClient*** buf, size_t* count) { - if (frame->type == TYPE_CLIENTS) { - *buf = frame->content.clients.buf; - *count = frame->content.clients.count; - } else { /* frame->type == TYPE_FRAMES */ - size_t c1, c2; - HSClient **buf1, **buf2; - frame_destroy(frame->content.layout.a, &buf1, &c1); - frame_destroy(frame->content.layout.b, &buf2, &c2); - // append buf2 to buf1 - buf1 = g_renew(HSClient*, buf1, c1 + c2); - memcpy(buf1+c1, buf2, sizeof(buf1[0]) * c2); - // free unused things - g_free(buf2); - // return; - *buf = buf1; - *count = c1 + c2; - } - stack_remove_slice(frame->tag->stack, frame->slice); - slice_destroy(frame->slice); - // free other things - XDestroyWindow(g_display, frame->window); - g_free(frame); -} - -void dump_frame_tree(HSFrame* frame, GString* output) { - if (frame->type == TYPE_CLIENTS) { - g_string_append_printf(output, "%cclients%c%s:%d", - LAYOUT_DUMP_BRACKETS[0], - LAYOUT_DUMP_WHITESPACES[0], - g_layout_names[frame->content.clients.layout], - frame->content.clients.selection); - HSClient** buf = frame->content.clients.buf; - size_t i, count = frame->content.clients.count; - for (i = 0; i < count; i++) { - g_string_append_printf(output, "%c0x%lx", - LAYOUT_DUMP_WHITESPACES[0], - buf[i]->window); - } - g_string_append_c(output, LAYOUT_DUMP_BRACKETS[1]); - } else { - /* type == TYPE_FRAMES */ - g_string_append_printf(output, "%csplit%c%s%c%lf%c%d%c", - LAYOUT_DUMP_BRACKETS[0], - LAYOUT_DUMP_WHITESPACES[0], - g_align_names[frame->content.layout.align], - LAYOUT_DUMP_SEPARATOR, - ((double)frame->content.layout.fraction) / (double)FRACTION_UNIT, - LAYOUT_DUMP_SEPARATOR, - frame->content.layout.selection, - LAYOUT_DUMP_WHITESPACES[0]); - dump_frame_tree(frame->content.layout.a, output); - g_string_append_c(output, LAYOUT_DUMP_WHITESPACES[0]); - dump_frame_tree(frame->content.layout.b, output); - g_string_append_c(output, LAYOUT_DUMP_BRACKETS[1]); - } -} - -char* load_frame_tree(HSFrame* frame, char* description, GString* errormsg) { - // find next ( - description = strchr(description, LAYOUT_DUMP_BRACKETS[0]); - if (!description) { - g_string_append_printf(errormsg, "Missing %c\n", - LAYOUT_DUMP_BRACKETS[0]); - return NULL; - } - description++; // jump over ( - - // goto frame type - description += strspn(description, LAYOUT_DUMP_WHITESPACES); - int type = TYPE_CLIENTS; - if (description[0] == 's') { - // if it could be "split" - type = TYPE_FRAMES; - } - - // get substring with frame args - // jump to whitespaces and over them - description += strcspn(description, LAYOUT_DUMP_WHITESPACES); - description += strspn(description, LAYOUT_DUMP_WHITESPACES); - // jump to whitespaces or brackets - size_t args_len = strcspn(description, LAYOUT_DUMP_WHITESPACES LAYOUT_DUMP_BRACKETS); - char args[args_len + 1]; - strncpy(args, description, args_len); - args[args_len] = '\0'; - // jump over args substring - description += args_len; - if (!*description) { - g_string_append_printf(errormsg, "Missing %c or arguments\n", LAYOUT_DUMP_BRACKETS[1]); - return NULL; - } - description += strspn(description, LAYOUT_DUMP_WHITESPACES); - if (!*description) { - g_string_append_printf(errormsg, "Missing %c or arguments\n", LAYOUT_DUMP_BRACKETS[1]); - return NULL; - } - - // apply type to frame - if (type == TYPE_FRAMES) { - // parse args - char* align_name = g_new(char, strlen(args)+1); - int selection; - double fraction_double; -#define SEP LAYOUT_DUMP_SEPARATOR_STR - if (3 != sscanf(args, "%[^"SEP"]"SEP"%lf"SEP"%d", - align_name, &fraction_double, &selection)) { - g_string_append_printf(errormsg, - "Can not parse frame args \"%s\"\n", args); - return NULL; - } -#undef SEP - int align = find_align_by_name(align_name); - g_free(align_name); - if (align < 0) { - g_string_append_printf(errormsg, - "Invalid alignment name in args \"%s\"\n", args); - return NULL; - } - selection = !!selection; // CLAMP it to [0;1] - int fraction = (int)(fraction_double * (double)FRACTION_UNIT); - - // ensure that it is split - if (frame->type == TYPE_FRAMES) { - // nothing to do - frame->content.layout.align = align; - frame->content.layout.fraction = fraction; - } else { - frame_split(frame, align, fraction); - if (frame->type != TYPE_FRAMES) { - g_string_append_printf(errormsg, - "Can not split frame\n"); - return NULL; - } - } - frame->content.layout.selection = selection; - - // now parse subframes - description = load_frame_tree(frame->content.layout.a, - description, errormsg); - if (!description) return NULL; - description = load_frame_tree(frame->content.layout.b, - description, errormsg); - if (!description) return NULL; - } else { - // parse args - char* layout_name = g_new(char, strlen(args)+1); - int selection; -#define SEP LAYOUT_DUMP_SEPARATOR_STR - if (2 != sscanf(args, "%[^"SEP"]"SEP"%d", - layout_name, &selection)) { - g_string_append_printf(errormsg, - "Can not parse frame args \"%s\"\n", args); - return NULL; - } -#undef SEP - int layout = find_layout_by_name(layout_name); - g_free(layout_name); - if (layout < 0) { - g_string_append_printf(errormsg, - "Can not parse layout from args \"%s\"\n", args); - return NULL; - } - - // ensure that it is a client frame - if (frame->type == TYPE_FRAMES) { - // remove childs - HSClient **buf1, **buf2; - size_t count1, count2; - frame_destroy(frame->content.layout.a, &buf1, &count1); - frame_destroy(frame->content.layout.b, &buf2, &count2); - - // merge bufs - size_t count = count1 + count2; - HSClient** buf = g_new(HSClient*, count); - memcpy(buf, buf1, sizeof(buf[0]) * count1); - memcpy(buf + count1, buf2, sizeof(buf[0]) * count2); - g_free(buf1); - g_free(buf2); - - // setup frame - frame->type = TYPE_CLIENTS; - frame->content.clients.buf = buf; - frame->content.clients.count = count; - frame->content.clients.selection = 0; // only some sane defaults - frame->content.clients.layout = 0; // only some sane defaults - } - - // bring child wins - // jump over whitespaces - description += strspn(description, LAYOUT_DUMP_WHITESPACES); - int index = 0; - HSTag* tag = find_tag_with_toplevel_frame(get_toplevel_frame(frame)); - while (*description != LAYOUT_DUMP_BRACKETS[1]) { - Window win; - if (1 != sscanf(description, "0x%lx\n", &win)) { - g_string_append_printf(errormsg, - "Can not parse window id from \"%s\"\n", description); - return NULL; - } - // jump over window id and over whitespaces - description += strspn(description, "0x123456789abcdef"); - description += strspn(description, LAYOUT_DUMP_WHITESPACES); - - // bring window here - HSClient* client = get_client_from_window(win); - if (!client) { - // client not managed... ignore it - continue; - } - - // remove window from old tag - HSMonitor* clientmonitor = find_monitor_with_tag(client->tag); - if (!frame_remove_client(client->tag->frame, client)) { - g_warning("window %lx was not found on tag %s\n", - win, client->tag->name->str); - } - if (clientmonitor) { - monitor_apply_layout(clientmonitor); - } - stack_remove_slice(client->tag->stack, client->slice); - - // insert it to buf - HSClient** buf = frame->content.clients.buf; - size_t count = frame->content.clients.count; - count++; - index = CLAMP(index, 0, count - 1); - buf = g_renew(HSClient*, buf, count); - memmove(buf + index + 1, buf + index, - sizeof(buf[0]) * (count - index - 1)); - buf[index] = client; - frame->content.clients.buf = buf; - frame->content.clients.count = count; - - client->tag = tag; - stack_insert_slice(client->tag->stack, client->slice); - ewmh_window_update_tag(client->window, client->tag); - - index++; - } - // apply layout and selection - selection = (selection < frame->content.clients.count) ? selection : 0; - selection = (selection >= 0) ? selection : 0; - frame->content.clients.layout = layout; - frame->content.clients.selection = selection; - } - // jump over closing bracket - if (*description == LAYOUT_DUMP_BRACKETS[1]) { - description++; - } else { - g_string_append_printf(errormsg, "warning: missing closing bracket %c\n", LAYOUT_DUMP_BRACKETS[1]); - } - // and over whitespaces - description += strspn(description, LAYOUT_DUMP_WHITESPACES); - return description; -} - -int find_layout_by_name(char* name) { - for (int i = 0; i < LENGTH(g_layout_names); i++) { - if (!g_layout_names[i]) { - break; - } - if (!strcmp(name, g_layout_names[i])) { - return i; - } - } - return -1; -} - -int find_align_by_name(char* name) { - for (int i = 0; i < LENGTH(g_align_names); i++) { - if (!strcmp(name, g_align_names[i])) { - return i; - } - } - return -1; -} - -HSFrame* get_toplevel_frame(HSFrame* frame) { - if (!frame) return NULL; - while (frame->parent) { - frame = frame->parent; - } - return frame; -} - -static void frame_append_caption(HSTree tree, GString* output) { - HSFrame* frame = (HSFrame*) tree; - if (frame->type == TYPE_CLIENTS) { - // list of clients - g_string_append_printf(output, "%s:", - g_layout_names[frame->content.clients.layout]); - HSClient** buf = frame->content.clients.buf; - size_t i, count = frame->content.clients.count; - for (i = 0; i < count; i++) { - g_string_append_printf(output, " 0x%lx", buf[i]->window); - } - if (g_cur_frame == frame) { - g_string_append(output, " [FOCUS]"); - } - } else { - /* type == TYPE_FRAMES */ - g_string_append_printf(output, "%s %d%% selection=%d", - g_layout_names[frame->content.layout.align], - frame->content.layout.fraction * 100 / FRACTION_UNIT, - frame->content.layout.selection); - } -} - -static size_t frame_child_count(HSTree tree) { - HSFrame* frame = (HSFrame*) tree; - return (frame->type == TYPE_CLIENTS) ? 0 : 2; -} - -static HSTreeInterface frame_nth_child(HSTree tree, size_t idx) { - HSFrame* frame = (HSFrame*) tree; - assert(frame->type != TYPE_CLIENTS); - HSTreeInterface intf = { - .nth_child = frame_nth_child, - .data = (idx == 0) - ? frame->content.layout.a - : frame->content.layout.b, - .destructor = NULL, - .child_count = frame_child_count, - .append_caption = frame_append_caption, - }; - return intf; -} - -void print_frame_tree(HSFrame* frame, GString* output) { - HSTreeInterface frameintf = { - .child_count = frame_child_count, - .nth_child = frame_nth_child, - .append_caption = frame_append_caption, - .destructor = NULL, - .data = (HSTree) frame, - }; - tree_print_to(&frameintf, output); -} - -void frame_apply_floating_layout(HSFrame* frame, HSMonitor* m) { - if (!frame) return; - if (frame->type == TYPE_FRAMES) { - frame_apply_floating_layout(frame->content.layout.a, m); - frame_apply_floating_layout(frame->content.layout.b, m); - } else { - /* if(frame->type == TYPE_CLIENTS) */ - frame_set_visible(frame, false); - HSClient** buf = frame->content.clients.buf; - size_t count = frame->content.clients.count; - size_t selection = frame->content.clients.selection; - /* border color */ - for (int i = 0; i < count; i++) { - HSClient* client = buf[i]; - client_setup_border(client, (g_cur_frame == frame) && (i == selection)); - client_resize_floating(client, m); - } - } -} - -int frame_current_cycle_client_layout(int argc, char** argv, GString* output) { - char* cmd_name = argv[0]; // save this before shifting - int delta = 1; - if (argc >= 2) { - delta = atoi(argv[1]); - } - assert(g_cur_frame && g_cur_frame->type == TYPE_CLIENTS); - (void)SHIFT(argc, argv); - (void)SHIFT(argc, argv); - int layout_index; - if (argc > 0) { - /* cycle through a given list of layouts */ - char* curname = g_layout_names[g_cur_frame->content.clients.layout]; - char** pcurrent = table_find(argv, sizeof(*argv), argc, 0, - memberequals_string, curname); - int idx = pcurrent ? (INDEX_OF(argv, pcurrent) + delta) : 0; - idx %= argc; - idx += argc; - idx %= argc; - layout_index = find_layout_by_name(argv[idx]); - if (layout_index < 0) { - g_string_append_printf(output, - "%s: Invalid layout name \"%s\"\n", cmd_name, argv[idx]); - return HERBST_INVALID_ARGUMENT; - } - } else { - /* cycle through the default list of layouts */ - layout_index = g_cur_frame->content.clients.layout + delta; - layout_index %= LAYOUT_COUNT; - layout_index += LAYOUT_COUNT; - layout_index %= LAYOUT_COUNT; - } - g_cur_frame->content.clients.layout = layout_index; - monitor_apply_layout(get_current_monitor()); - return 0; -} - -int frame_current_set_client_layout(int argc, char** argv, GString* output) { - int layout = 0; - if (argc <= 1) { - return HERBST_NEED_MORE_ARGS; - } - layout = find_layout_by_name(argv[1]); - if (layout < 0) { - g_string_append_printf(output, - "%s: Invalid layout name \"%s\"\n", argv[0], argv[1]); - return HERBST_INVALID_ARGUMENT; - } - if (g_cur_frame && g_cur_frame->type == TYPE_CLIENTS) { - g_cur_frame->content.clients.layout = layout; - monitor_apply_layout(get_current_monitor()); - } - return 0; -} - -void frame_apply_client_layout_linear(HSFrame* frame, Rectangle rect, bool vertical) { - HSClient** buf = frame->content.clients.buf; - size_t count = frame->content.clients.count; - Rectangle cur = rect; - int last_step_y; - int last_step_x; - int step_y; - int step_x; - if (vertical) { - // only do steps in y direction - last_step_y = cur.height % count; // get the space on bottom - last_step_x = 0; - cur.height /= count; - step_y = cur.height; - step_x = 0; - } else { - // only do steps in x direction - last_step_y = 0; - last_step_x = cur.width % count; // get the space on the right - cur.width /= count; - step_y = 0; - step_x = cur.width; - } - for (int i = 0; i < count; i++) { - HSClient* client = buf[i]; - // add the space, if count does not divide frameheight without remainder - cur.height += (i == count-1) ? last_step_y : 0; - cur.width += (i == count-1) ? last_step_x : 0; - client_resize_tiling(client, cur, frame); - cur.y += step_y; - cur.x += step_x; - } -} - -void frame_apply_client_layout_horizontal(HSFrame* frame, Rectangle rect) { - frame_apply_client_layout_linear(frame, rect, false); -} - -void frame_apply_client_layout_vertical(HSFrame* frame, Rectangle rect) { - frame_apply_client_layout_linear(frame, rect, true); -} - -void frame_apply_client_layout_max(HSFrame* frame, Rectangle rect) { - HSClient** buf = frame->content.clients.buf; - size_t count = frame->content.clients.count; - int selection = frame->content.clients.selection; - for (int i = 0; i < count; i++) { - HSClient* client = buf[i]; - client_setup_border(client, (g_cur_frame == frame) && (i == selection)); - client_resize_tiling(client, rect, frame); - if (i == selection) { - client_raise(client); - } - } -} - -void frame_layout_grid_get_size(size_t count, int* res_rows, int* res_cols) { - int cols = 0; - while (cols * cols < count) { - cols++; - } - *res_cols = cols; - if (*res_cols != 0) { - *res_rows = (count / cols) + (count % cols ? 1 : 0); - } else { - *res_rows = 0; - } -} - -void frame_apply_client_layout_grid(HSFrame* frame, Rectangle rect) { - HSClient** buf = frame->content.clients.buf; - size_t count = frame->content.clients.count; - int selection = frame->content.clients.selection; - if (count == 0) { - return; - } - - int rows, cols; - frame_layout_grid_get_size(count, &rows, &cols); - int width = rect.width / cols; - int height = rect.height / rows; - int i = 0; - Rectangle cur = rect; // current rectangle - for (int r = 0; r < rows; r++) { - // reset to left - cur.x = rect.x; - cur.width = width; - cur.height = height; - if (r == rows -1) { - // fill small pixel gap below last row - cur.height += rect.height % rows; - } - for (int c = 0; c < cols && i < count; c++) { - if (*g_gapless_grid && (i == count - 1) // if last client - && (count % cols != 0)) { // if cols remain - // fill remaining cols with client - cur.width = rect.x + rect.width - cur.x; - } else if (c == cols - 1) { - // fill small pixel gap in last col - cur.width += rect.width % cols; - } - - // apply size - HSClient* client = buf[i]; - client_setup_border(client, (g_cur_frame == frame) && (i == selection)); - client_resize_tiling(client, cur, frame); - cur.x += width; - i++; - } - cur.y += height; - } - -} - -void frame_apply_layout(HSFrame* frame, Rectangle rect) { - frame->last_rect = rect; - if (frame->type == TYPE_CLIENTS) { - size_t count = frame->content.clients.count; - if (!*g_smart_frame_surroundings || frame->parent) { - // apply frame gap - rect.height -= *g_frame_gap; - rect.width -= *g_frame_gap; - // apply frame border - rect.x += *g_frame_border_width; - rect.y += *g_frame_border_width; - rect.height -= *g_frame_border_width * 2; - rect.width -= *g_frame_border_width * 2; - } - - if (rect.width <= WINDOW_MIN_WIDTH || rect.height <= WINDOW_MIN_HEIGHT) { - // do nothing on invalid size - return; - } - unsigned long border_color = g_frame_border_normal_color; - unsigned long bg_color = g_frame_bg_normal_color; - int bw = *g_frame_border_width; - if (g_cur_frame == frame) { - border_color = g_frame_border_active_color; - bg_color = g_frame_bg_active_color; - } - if (*g_smart_frame_surroundings && !frame->parent) { - bw = 0; - } - XSetWindowBorderWidth(g_display, frame->window, bw); - XMoveResizeWindow(g_display, frame->window, - rect.x - bw, - rect.y - bw, - rect.width, rect.height); - - frame_update_border(frame->window, border_color); - - XSetWindowBackground(g_display, frame->window, bg_color); - if (*g_frame_bg_transparent) { // != ) { - window_cut_rect_hole(frame->window, rect.width, rect.height, - *g_frame_transparent_width); - } else if (frame->window_transparent) { - window_make_intransparent(frame->window, rect.width, rect.height); - } - frame->window_transparent = *g_frame_bg_transparent; - if (g_cur_frame == frame) { - ewmh_set_window_opacity(frame->window, g_frame_active_opacity/100.0); - } else { - ewmh_set_window_opacity(frame->window, g_frame_normal_opacity/100.0); - } - XClearWindow(g_display, frame->window); - // move windows - if (count == 0) { - return; - } - - if (!smart_window_surroundings_active(frame)) { - // apply window gap - rect.x += *g_window_gap; - rect.y += *g_window_gap; - rect.width -= *g_window_gap; - rect.height -= *g_window_gap; - - // apply frame padding - rect.x += *g_frame_padding; - rect.y += *g_frame_padding; - rect.width -= *g_frame_padding * 2; - rect.height -= *g_frame_padding * 2; - } - - if (frame->content.clients.layout == LAYOUT_MAX) { - frame_apply_client_layout_max(frame, rect); - } else if (frame->content.clients.layout == LAYOUT_GRID) { - frame_apply_client_layout_grid(frame, rect); - } else { - frame_apply_client_layout_linear(frame, rect, - (frame->content.clients.layout == LAYOUT_VERTICAL)); - } - } else { /* frame->type == TYPE_FRAMES */ - HSLayout* layout = &frame->content.layout; - Rectangle first = rect; - Rectangle second = rect; - if (layout->align == ALIGN_VERTICAL) { - first.height = (rect.height * layout->fraction) / FRACTION_UNIT; - second.y += first.height; - second.height -= first.height; - } else { // (layout->align == ALIGN_HORIZONTAL) - first.width = (rect.width * layout->fraction) / FRACTION_UNIT; - second.x += first.width; - second.width -= first.width; - } - frame_apply_layout(layout->a, first); - frame_apply_layout(layout->b, second); - } -} - -static void frame_update_frame_window_visibility_helper(HSFrame* frame) { - if (frame->type == TYPE_CLIENTS) { - int count = frame->content.clients.count; - frame_set_visible(frame, *g_always_show_frame - || (count != 0) || (g_cur_frame == frame)); - } else { - frame_set_visible(frame, false); - } -} - -void frame_update_frame_window_visibility(HSFrame* frame) { - frame_do_recursive(frame, frame_update_frame_window_visibility_helper, 2); -} - -HSFrame* frame_current_selection_below(HSFrame* frame) { - while (frame->type == TYPE_FRAMES) { - frame = (frame->content.layout.selection == 0) ? - frame->content.layout.a : - frame->content.layout.b; - } - return frame; -} - -HSFrame* frame_current_selection() { - HSMonitor* m = get_current_monitor(); - if (!m->tag) return NULL; - return frame_current_selection_below(m->tag->frame); -} - -int frame_current_bring(int argc, char** argv, GString* output) { - HSClient* client = NULL; - - if (argc < 2) { - return HERBST_NEED_MORE_ARGS; - } - string_to_client(argv[1], &client); - if (!client) { - g_string_append_printf(output, - "%s: Could not find client", argv[0]); - if (argc > 1) { - g_string_append_printf(output, " \"%s\".\n", argv[1]); - } else { - g_string_append(output, ".\n"); - } - return HERBST_INVALID_ARGUMENT; - } - HSTag* tag = get_current_monitor()->tag; - tag_move_client(client, tag); - HSFrame* frame = find_frame_with_client(tag->frame, client); - if (frame != g_cur_frame) { - frame_remove_client(frame, client); - frame_insert_client(g_cur_frame, client); - } - focus_client(client, false, false); - return 0; -} - -int frame_current_set_selection(int argc, char** argv) { - int index = 0; - if (argc >= 2) { - index = atoi(argv[1]); - } else { - return HERBST_NEED_MORE_ARGS; - } - // find current selection - HSFrame* frame = frame_current_selection(); - if (frame->content.clients.count == 0) { - // nothing to do - return 0; - } - if (index < 0 || index >= frame->content.clients.count) { - index = frame->content.clients.count - 1; - } - frame->content.clients.selection = index; - HSClient* client = frame->content.clients.buf[index]; - client_window_focus(client); - return 0; -} - -int frame_current_cycle_selection(int argc, char** argv) { - int delta = 1; - if (argc >= 2) { - delta = atoi(argv[1]); - } - // find current selection - HSFrame* frame = frame_current_selection(); - if (frame->content.clients.count == 0) { - // nothing to do - return 0; - } - int index = frame->content.clients.selection; - // use an integer variable to avoid strange happenings when computing - // (-1) % (size_t)6 - int count = (int) frame->content.clients.count; - index += delta; - index %= count; - index += count; - index %= count; - frame->content.clients.selection = index; - monitor_apply_layout(get_current_monitor()); - return 0; -} - -int cycle_all_command(int argc, char** argv) { - int delta = 1; - bool skip_invisible = false; - if (argc >= 2) { - if (!strcmp(argv[1], "--skip-invisible")) { - skip_invisible = true; - (void) SHIFT(argc, argv); - } - } - if (argc >= 2) { - delta = atoi(argv[1]); - } - delta = CLAMP(delta, -1, 1); // only delta -1, 0, 1 is allowed - if (delta == 0) { - // nothing to do - return 0; - } - // find current selection - HSFrame* frame = frame_current_selection(); - int index = frame->content.clients.selection; - bool change_frame = false; - int direction; - int new_window_index; // tells where to start in a new frame - if (delta > 0 && (index + 1) >= frame->content.clients.count) { - // change selection from 0 to 1 - direction = 1; - change_frame = true; - new_window_index = 0; // focus first window in in a frame - } - if (delta < 0 && index == 0) { - // change selection from 1 to 0 - direction = 0; - change_frame = true; - new_window_index = -1; // focus last window in in a frame - } - if (skip_invisible && frame->content.clients.layout == LAYOUT_MAX) { - direction = (delta > 0) ? 1 : 0; - change_frame = true; - } - if (change_frame) { - cycle_frame(direction, new_window_index, skip_invisible); - } else { - // only change the selection within one frame - index += delta; - // ensure it is a valid index - index %= frame->content.clients.count; - index += frame->content.clients.count; - index %= frame->content.clients.count; - frame->content.clients.selection = index; - } - HSClient* c = frame_focused_client(g_cur_frame); - if (c) { - client_raise(c); - } - monitor_apply_layout(get_current_monitor()); - return 0; -} - -int cycle_frame_command(int argc, char** argv) { - int delta = 1; - if (argc >= 2) { - delta = atoi(argv[1]); - } - if (delta == 0) return 0; - int direction = (delta > 0) ? 1 : 0; - cycle_frame(direction, -2, false); - return 0; -} - -// cycle_frame: -// direction: 1 for forward cycling, 0 for backwards cycling -// new_window_index: which index in the new frame should be focused, -2 does -// not change the window selection in the new frame -// skip_invisible: if set, don't touch the selection in frames in max layout -void cycle_frame(int direction, int new_window_index, bool skip_invisible) { - HSFrame* frame = frame_current_selection(); - int other_direction = 1 - direction; - if (true) { - HSFrame* top_frame; - // these things can be visualized easily for direction = 1 - /* - * . - * / \ - * . \ - * / \ ... - * / \ - * . . - * / \ / \ - * . * . . - * the star shows the current focus - */ - // go to next frame in tree - // find first frame, where we can change the selection from 0 to 1 - // i.e. from other_direction to direction we want to use - while (frame->parent && frame->parent->content.layout.selection == direction) { - frame = frame->parent; - } - /* - * . - * / \ - * . \ - * / \ ... - * / \ - * * . - * / \ / \ - * . . . . - */ - if (frame->parent) { - // go to the top - frame = frame->parent; - } else { - // if we reached the top, do nothing.. - } - top_frame = frame; - /* \ . - * \ / \ - * `-> * \ - * / \ ... - * / \ - * . . - * / \ / \ - * . . . . - */ - // go one step to the right (i.e. in desired direction - if (frame->type == TYPE_FRAMES) { - int oldselection = frame->content.layout.selection; - if (oldselection == direction) { - // if we already reached the end, - // i.e. if we cannot go in the desired direction - // then wrap around - frame->content.layout.selection = other_direction; - frame = frame->content.layout.a; - } else { - frame->content.layout.selection = direction; - frame = frame->content.layout.b; - } - } - /* - * . - * / \ - * . \ - * / \ ... - * / \ - * . * - * / \ / \ - * . . . . - */ - // and then to the left (i.e. find first leaf) - while (frame->type == TYPE_FRAMES) { - // then go deeper, with the other direction - frame->content.layout.selection = other_direction; - frame = frame->content.layout.a; - } - /* . - * / \ - * . \ - * / \ ... - * / \ - * . . - * / \ / \ - * . . * . - */ - // now we reached the next client containing frame - - if (new_window_index != -2 && frame->content.clients.count > 0) { - if (!skip_invisible - || frame->content.clients.layout != LAYOUT_MAX) - { - frame->content.clients.selection = new_window_index; - // ensure it is a valid index - size_t count = frame->content.clients.count; - frame->content.clients.selection += count; - frame->content.clients.selection %= count; - } - } - - // reset focus and g_cur_frame - // all changes were made below top_frame - frame_focus_recursive(top_frame); - - } - monitor_apply_layout(get_current_monitor()); - return; -} - - -// counts the splits of a kind align to root window -int frame_split_count_to_root(HSFrame* frame, int align) { - int height = 0; - // count steps until root node - // root node has no parent - while (frame->parent) { - frame = frame->parent; - if (frame->content.layout.align == align) { - height++; - } - } - return height; -} - -bool frame_split(HSFrame* frame, int align, int fraction) { - if (frame_split_count_to_root(frame, align) > HERBST_MAX_TREE_HEIGHT) { - // do nothing if tree would be to large - return false; - } - assert(frame->type == TYPE_CLIENTS); - // ensure fraction is allowed - fraction = CLAMP(fraction, - FRACTION_UNIT * (0.0 + FRAME_MIN_FRACTION), - FRACTION_UNIT * (1.0 - FRAME_MIN_FRACTION)); - - HSFrame* first = frame_create_empty(frame, NULL); - HSFrame* second = frame_create_empty(frame, NULL); - first->content = frame->content; - first->type = frame->type; - second->type = TYPE_CLIENTS; - frame->type = TYPE_FRAMES; - frame->content.layout.align = align; - frame->content.layout.a = first; - frame->content.layout.b = second; - frame->content.layout.selection = 0; - frame->content.layout.fraction = fraction; - return true; -} - -int frame_split_command(int argc, char** argv, GString* output) { - // usage: split t|b|l|r|h|v FRACTION - if (argc < 2) { - return HERBST_NEED_MORE_ARGS; - } - int align = -1; - bool userDefinedFraction = true; - char* strFraction = "0.5"; - if (argc < 3) { - userDefinedFraction = false; - } else { - strFraction = argv[2]; - } - bool frameToFirst = true; - int fraction = FRACTION_UNIT* CLAMP(atof(strFraction), - 0.0 + FRAME_MIN_FRACTION, - 1.0 - FRAME_MIN_FRACTION); - int selection = 0; - int lh = g_cur_frame->last_rect.height; - int lw = g_cur_frame->last_rect.width; - int align_auto = (lw > lh) ? ALIGN_HORIZONTAL : ALIGN_VERTICAL; - struct { - char* name; - int align; - bool frameToFirst; // if former frame moves to first child - int selection; // which child to select after the split - } splitModes[] = { - { "top", ALIGN_VERTICAL, false, 1 }, - { "bottom", ALIGN_VERTICAL, true, 0 }, - { "vertical", ALIGN_VERTICAL, true, 0 }, - { "right", ALIGN_HORIZONTAL, true, 0 }, - { "horizontal", ALIGN_HORIZONTAL, true, 0 }, - { "left", ALIGN_HORIZONTAL, false, 1 }, - { "explode", ALIGN_EXPLODE, true, 0 }, - { "auto", align_auto, true, 0 }, - }; - for (int i = 0; i < LENGTH(splitModes); i++) { - if (splitModes[i].name[0] == argv[1][0]) { - align = splitModes[i].align; - frameToFirst = splitModes[i].frameToFirst; - selection = splitModes[i].selection; - break; - } - } - if (align < 0) { - g_string_append_printf(output, - "%s: Invalid alignment \"%s\"\n", argv[0], argv[1]); - return HERBST_INVALID_ARGUMENT; - } - HSFrame* frame = frame_current_selection(); - if (!frame) return 0; // nothing to do - bool exploding = align == ALIGN_EXPLODE; - int layout = frame->content.clients.layout; - int windowcount = frame->content.clients.count; - if (exploding) { - if (windowcount <= 1) { - align = align_auto; - } else if (layout == LAYOUT_MAX) { - align = align_auto; - } else if (layout == LAYOUT_GRID && windowcount == 2) { - align = ALIGN_HORIZONTAL; - } else if (layout == LAYOUT_HORIZONTAL) { - align = ALIGN_HORIZONTAL; - } else { - align = ALIGN_VERTICAL; - } - int layout = frame->content.clients.layout; - size_t count1 = frame->content.clients.count; - size_t nc1 = (count1 + 1) / 2; // new count for the first frame - if ((layout == LAYOUT_HORIZONTAL || layout == LAYOUT_VERTICAL) - && !userDefinedFraction && count1 > 1) { - fraction = (nc1 * FRACTION_UNIT) / count1; - } - } - if (!frame_split(frame, align, fraction)) { - return 0; - } - if (exploding) { - // move second half of the window buf to second frame - size_t count1 = frame->content.layout.a->content.clients.count; - size_t count2 = frame->content.layout.b->content.clients.count; - // assert: count2 == 0 - size_t nc1 = (count1 + 1) / 2; // new count for the first frame - size_t nc2 = count1 - nc1 + count2; // new count for the 2nd frame - HSFrame* child1 = frame->content.layout.a; - HSFrame* child2 = frame->content.layout.b; - HSClient*** buf1 = &child1->content.clients.buf; - HSClient*** buf2 = &child2->content.clients.buf; - *buf2 = g_renew(HSClient*, *buf2, nc2); - memcpy(*buf2 + count2, *buf1 + nc1, (nc2 - count2) * sizeof(**buf2)); - *buf1 = g_renew(HSClient*, *buf1, nc1); - child1->content.clients.count = nc1; - child2->content.clients.count = nc2; - child2->content.clients.layout = child1->content.clients.layout; - if (child1->content.clients.selection >= nc1 && nc1 > 0) { - child2->content.clients.selection = - child1->content.clients.selection - nc1 + count2; - child1->content.clients.selection = nc1 - 1; - selection = 1; - } else { - selection = 0; - } - - } else if (!frameToFirst) { - HSFrame* emptyFrame = frame->content.layout.b; - frame->content.layout.b = frame->content.layout.a; - frame->content.layout.a = emptyFrame; - } - frame->content.layout.selection = selection; - // reset focus - g_cur_frame = frame_current_selection(); - // redraw monitor - monitor_apply_layout(get_current_monitor()); - return 0; -} - -int frame_change_fraction_command(int argc, char** argv, GString* output) { - // usage: fraction DIRECTION DELTA - if (argc < 3) { - return HERBST_NEED_MORE_ARGS; - } - char direction = argv[1][0]; - double delta_double = atof(argv[2]); - delta_double = CLAMP(delta_double, -1.0, 1.0); - int delta = FRACTION_UNIT * delta_double; - // if direction is left or up we have to flip delta - // because e.g. resize up by 0.1 actually means: - // reduce fraction by 0.1, i.e. delta = -0.1 - switch (direction) { - case 'l': delta *= -1; break; - case 'r': break; - case 'u': delta *= -1; break; - case 'd': break; - default: - g_string_append_printf(output, - "%s: Invalid direction \"%s\"\n", argv[0], argv[1]); - return HERBST_INVALID_ARGUMENT; - } - HSFrame* neighbour = frame_neighbour(g_cur_frame, direction); - if (!neighbour) { - // then try opposite direction - switch (direction) { - case 'l': direction = 'r'; break; - case 'r': direction = 'l'; break; - case 'u': direction = 'd'; break; - case 'd': direction = 'u'; break; - default: assert(false); break; - } - neighbour = frame_neighbour(g_cur_frame, direction); - if (!neighbour) { - g_string_append_printf(output, - "%s: No neighbour found\n", argv[0]); - return HERBST_FORBIDDEN; - } - } - HSFrame* parent = neighbour->parent; - assert(parent != NULL); // if has neighbour, it also must have a parent - assert(parent->type == TYPE_FRAMES); - int fraction = parent->content.layout.fraction; - fraction += delta; - fraction = CLAMP(fraction, (int)(FRAME_MIN_FRACTION * FRACTION_UNIT), (int)((1.0 - FRAME_MIN_FRACTION) * FRACTION_UNIT)); - parent->content.layout.fraction = fraction; - // arrange monitor - monitor_apply_layout(get_current_monitor()); - return 0; -} - -HSFrame* frame_neighbour(HSFrame* frame, char direction) { - HSFrame* other; - bool found = false; - while (frame->parent) { - // find frame, where we can change the - // selection in the desired direction - HSLayout* layout = &frame->parent->content.layout; - switch(direction) { - case 'r': - if (layout->align == ALIGN_HORIZONTAL - && layout->a == frame) { - found = true; - other = layout->b; - } - break; - case 'l': - if (layout->align == ALIGN_HORIZONTAL - && layout->b == frame) { - found = true; - other = layout->a; - } - break; - case 'd': - if (layout->align == ALIGN_VERTICAL - && layout->a == frame) { - found = true; - other = layout->b; - } - break; - case 'u': - if (layout->align == ALIGN_VERTICAL - && layout->b == frame) { - found = true; - other = layout->a; - } - break; - default: - return NULL; - break; - } - if (found) { - break; - } - // else: go one step closer to root - frame = frame->parent; - } - if (!found) { - return NULL; - } - return other; -} - -// finds a neighbour within frame in the specified direction -// returns its index or -1 if there is none -int frame_inner_neighbour_index(HSFrame* frame, char direction) { - int index = -1; - if (frame->type != TYPE_CLIENTS) { - fprintf(stderr, "warning: frame has invalid type\n"); - return -1; - } - int selection = frame->content.clients.selection; - int count = frame->content.clients.count; - int rows, cols; - switch (frame->content.clients.layout) { - case LAYOUT_VERTICAL: - if (direction == 'd') index = selection + 1; - if (direction == 'u') index = selection - 1; - break; - case LAYOUT_HORIZONTAL: - if (direction == 'r') index = selection + 1; - if (direction == 'l') index = selection - 1; - break; - case LAYOUT_MAX: - break; - case LAYOUT_GRID: - frame_layout_grid_get_size(count, &rows, &cols); - if (cols == 0) break; - int r = selection / cols; - int c = selection % cols; - switch (direction) { - case 'd': - index = selection + cols; - if (*g_gapless_grid && index >= count && r == (rows - 2)) { - // if grid is gapless and we're in the second-last row - // then it means last client is below us - index = count - 1; - } - break; - case 'u': index = selection - cols; break; - case 'r': if (c < cols-1) index = selection + 1; break; - case 'l': if (c > 0) index = selection - 1; break; - } - break; - default: - break; - } - // check that index is valid - if (index < 0 || index >= count) { - index = -1; - } - return index; -} - -int frame_focus_command(int argc, char** argv, GString* output) { - // usage: focus [-e|-i] left|right|up|down - if (argc < 2) return HERBST_NEED_MORE_ARGS; - if (!g_cur_frame) { - fprintf(stderr, "warning: no frame is selected\n"); - return HERBST_UNKNOWN_ERROR; - } - int external_only = *g_direction_external_only; - char direction = argv[1][0]; - if (argc > 2 && !strcmp(argv[1], "-i")) { - external_only = false; - direction = argv[2][0]; - } - if (argc > 2 && !strcmp(argv[1], "-e")) { - external_only = true; - direction = argv[2][0]; - } - //HSFrame* frame = g_cur_frame; - int index; - bool neighbour_found = true; - if (g_cur_frame->tag->floating) { - enum HSDirection dir = char_to_direction(direction); - if (dir < 0) return HERBST_INVALID_ARGUMENT; - neighbour_found = floating_focus_direction(dir); - } else if (!external_only && - (index = frame_inner_neighbour_index(g_cur_frame, direction)) != -1) { - g_cur_frame->content.clients.selection = index; - frame_focus_recursive(g_cur_frame); - monitor_apply_layout(get_current_monitor()); - } else { - HSFrame* neighbour = frame_neighbour(g_cur_frame, direction); - if (neighbour != NULL) { // if neighbour was found - HSFrame* parent = neighbour->parent; - // alter focus (from 0 to 1, from 1 to 0) - int selection = parent->content.layout.selection; - selection = (selection == 1) ? 0 : 1; - parent->content.layout.selection = selection; - // change focus if possible - frame_focus_recursive(parent); - monitor_apply_layout(get_current_monitor()); - } else { - neighbour_found = false; - } - } - if (!neighbour_found && !*g_focus_crosses_monitor_boundaries) { - g_string_append_printf(output, - "%s: No neighbour found\n", argv[0]); - return HERBST_FORBIDDEN; - } - if (!neighbour_found && *g_focus_crosses_monitor_boundaries) { - // find monitor in the specified direction - enum HSDirection dir = char_to_direction(direction); - if (dir < 0) return HERBST_INVALID_ARGUMENT; - int idx = monitor_index_in_direction(get_current_monitor(), dir); - if (idx < 0) { - g_string_append_printf(output, - "%s: No neighbour found\n", argv[0]); - return HERBST_FORBIDDEN; - } - monitor_focus_by_index(idx); - } - return 0; -} - -int frame_move_window_command(int argc, char** argv, GString* output) { - // usage: move left|right|up|down - if (argc < 2) return HERBST_NEED_MORE_ARGS; - if (!g_cur_frame) { - fprintf(stderr, "error: no frame is selected\n"); - return HERBST_UNKNOWN_ERROR; - } - char direction = argv[1][0]; - int external_only = *g_direction_external_only; - if (argc > 2 && !strcmp(argv[1], "-i")) { - external_only = false; - direction = argv[2][0]; - } - if (argc > 2 && !strcmp(argv[1], "-e")) { - external_only = true; - direction = argv[2][0]; - } - if (!frame_focused_client(g_cur_frame)) { - return HERBST_FORBIDDEN; - } - if (is_client_floated(get_current_client())) { - // try to move the floating window - enum HSDirection dir = char_to_direction(direction); - if (dir < 0) return HERBST_INVALID_ARGUMENT; - bool success = floating_shift_direction(dir); - return success ? 0 : HERBST_FORBIDDEN; - } - int index; - if (!external_only && - (index = frame_inner_neighbour_index(g_cur_frame, direction)) != -1) { - int selection = g_cur_frame->content.clients.selection; - HSClient** buf = g_cur_frame->content.clients.buf; - // if internal neighbour was found, then swap - HSClient* tmp = buf[selection]; - buf[selection] = buf[index]; - buf[index] = tmp; - - g_cur_frame->content.clients.selection = index; - frame_focus_recursive(g_cur_frame); - monitor_apply_layout(get_current_monitor()); - } else { - HSFrame* neighbour = frame_neighbour(g_cur_frame, direction); - HSClient* client = frame_focused_client(g_cur_frame); - if (client && neighbour != NULL) { // if neighbour was found - // move window to neighbour - frame_remove_client(g_cur_frame, client); - frame_insert_client(neighbour, client); - - // change selection in parent - HSFrame* parent = neighbour->parent; - assert(parent); - parent->content.layout.selection = ! parent->content.layout.selection; - frame_focus_recursive(parent); - // focus right window in frame - HSFrame* frame = g_cur_frame; - assert(frame); - int i; - HSClient** buf = frame->content.clients.buf; - size_t count = frame->content.clients.count; - for (i = 0; i < count; i++) { - if (buf[i] == client) { - frame->content.clients.selection = i; - client_window_focus(buf[i]); - break; - } - } - - // layout was changed, so update it - monitor_apply_layout(get_current_monitor()); - } else { - g_string_append_printf(output, - "%s: No neighbour found\n", argv[0]); - return HERBST_FORBIDDEN; - } - } - return 0; -} - -void frame_unfocus() { - //XSetInputFocus(g_display, g_root, RevertToPointerRoot, CurrentTime); -} - -HSClient* frame_focused_client(HSFrame* frame) { - if (!frame) { - return NULL; - } - // follow the selection to a leaf - while (frame->type == TYPE_FRAMES) { - frame = (frame->content.layout.selection == 0) ? - frame->content.layout.a : - frame->content.layout.b; - } - if (frame->content.clients.count) { - int selection = frame->content.clients.selection; - return frame->content.clients.buf[selection]; - } // else, if there are no windows - return NULL; -} - -// try to focus window in frame -// it does not require anything from the frame. it may be infocused or even -// hidden. -// returns true if win was found and focused, else returns false -bool frame_focus_client(HSFrame* frame, HSClient* client) { - if (!frame) { - return false; - } - if (frame->type == TYPE_CLIENTS) { - int i; - size_t count = frame->content.clients.count; - HSClient** buf = frame->content.clients.buf; - // search for win in buf - for (i = 0; i < count; i++) { - if (buf[i] == client) { - // if found, set focus to it - frame->content.clients.selection = i; - return true; - } - } - return false; - } else { - // type == TYPE_FRAMES - // search in subframes - bool found = frame_focus_client(frame->content.layout.a, client); - if (found) { - // set selection to first frame - frame->content.layout.selection = 0; - return true; - } - found = frame_focus_client(frame->content.layout.b, client); - if (found) { - // set selection to second frame - frame->content.layout.selection = 1; - return true; - } - return false; - } -} - -// focus a window -// switch_tag tells, whether to switch tag to focus to window -// switch_monitor tells, whether to switch monitor to focus to window -// returns if window was focused or not -bool focus_client(struct HSClient* client, bool switch_tag, bool switch_monitor) { - if (!client) { - // client is not managed - return false; - } - HSTag* tag = client->tag; - assert(client->tag); - HSMonitor* monitor = find_monitor_with_tag(tag); - HSMonitor* cur_mon = get_current_monitor(); - if (monitor != cur_mon && !switch_monitor) { - // if we are not allowed to switch tag - // and tag is not on current monitor (or on no monitor) - // than we cannot focus the window - return false; - } - if (monitor == NULL && !switch_tag) { - return false; - } - if (monitor != cur_mon && monitor != NULL) { - if (!switch_monitor) { - return false; - } else { - // switch monitor - monitor_focus_by_index(monitor_index_of(monitor)); - cur_mon = get_current_monitor(); - assert(cur_mon == monitor); - } - } - monitors_lock(); - monitor_set_tag(cur_mon, tag); - cur_mon = get_current_monitor(); - if (cur_mon->tag != tag) { - // could not set tag on monitor - monitors_unlock(); - return false; - } - // now the right tag is visible - // now focus it - bool found = frame_focus_client(tag->frame, client); - frame_focus_recursive(tag->frame); - monitor_apply_layout(cur_mon); - monitors_unlock(); - return found; -} - -int frame_focus_recursive(HSFrame* frame) { - // follow the selection to a leaf - while (frame->type == TYPE_FRAMES) { - frame = (frame->content.layout.selection == 0) ? - frame->content.layout.a : - frame->content.layout.b; - } - g_cur_frame = frame; - frame_unfocus(); - if (frame->content.clients.count) { - int selection = frame->content.clients.selection; - client_window_focus(frame->content.clients.buf[selection]); - } else { - client_window_unfocus_last(); - } - return 0; -} - -// do recursive for each element of the (binary) frame tree -// if order <= 0 -> action(node); action(left); action(right); -// if order == 1 -> action(left); action(node); action(right); -// if order >= 2 -> action(left); action(right); action(node); -void frame_do_recursive(HSFrame* frame, void (*action)(HSFrame*), int order) { - if (!frame) { - return; - } - if (frame->type == TYPE_FRAMES) { - // clients and subframes - HSLayout* layout = &(frame->content.layout); - if (order <= 0) action(frame); - frame_do_recursive(layout->a, action, order); - if (order == 1) action(frame); - frame_do_recursive(layout->b, action, order); - if (order >= 2) action(frame); - } else { - // action only - action(frame); - } -} - -void frame_do_recursive_data(HSFrame* frame, void (*action)(HSFrame*,void*), - int order, void* data) { - if (frame->type == TYPE_FRAMES) { - // clients and subframes - HSLayout* layout = &(frame->content.layout); - if (order <= 0) action(frame, data); - frame_do_recursive_data(layout->a, action, order, data); - if (order == 1) action(frame, data); - frame_do_recursive_data(layout->b, action, order, data); - if (order >= 2) action(frame, data); - } else { - // action only - action(frame, data); - } -} - -static void frame_hide(HSFrame* frame) { - frame_set_visible(frame, false); - if (frame->type == TYPE_CLIENTS) { - int i; - HSClient** buf = frame->content.clients.buf; - size_t count = frame->content.clients.count; - for (i = 0; i < count; i++) { - client_set_visible(buf[i], false); - } - } -} - -void frame_hide_recursive(HSFrame* frame) { - // first hide children => order = 2 - frame_do_recursive(frame, frame_hide, 2); -} - -static void frame_show_clients(HSFrame* frame) { - if (frame->type == TYPE_CLIENTS) { - int i; - HSClient** buf = frame->content.clients.buf; - size_t count = frame->content.clients.count; - for (i = 0; i < count; i++) { - client_set_visible(buf[i], true); - } - } -} - -void frame_show_recursive(HSFrame* frame) { - // first show parents, then children => order = 0 - frame_do_recursive(frame, frame_show_clients, 2); -} - -static void frame_rotate(HSFrame* frame) { - if (frame && frame->type == TYPE_FRAMES) { - HSLayout* l = &frame->content.layout; - switch (l->align) { - case ALIGN_VERTICAL: - l->align = ALIGN_HORIZONTAL; - break; - case ALIGN_HORIZONTAL: - l->align = ALIGN_VERTICAL; - l->selection = l->selection ? 0 : 1; - HSFrame* temp = l->a; - l->a = l->b; - l->b = temp; - l->fraction = FRACTION_UNIT - l->fraction; - break; - } - } -} - -int layout_rotate_command() { - frame_do_recursive(get_current_monitor()->tag->frame, frame_rotate, -1); - monitor_apply_layout(get_current_monitor()); - return 0; -} - -int frame_remove_command(int argc, char** argv) { - if (!g_cur_frame->parent) { - // do nothing if is toplevel frame - return 0; - } - assert(g_cur_frame->type == TYPE_CLIENTS); - HSFrame* parent = g_cur_frame->parent; - HSFrame* first = g_cur_frame; - HSFrame* second; - if (first == parent->content.layout.a) { - second = parent->content.layout.b; - } else { - assert(first == parent->content.layout.b); - second = parent->content.layout.a; - } - size_t count; - HSClient** wins; - // get all wins from first child - frame_destroy(first, &wins, &count); - // and insert them to other child.. inefficiently - int i; - for (i = 0; i < count; i++) { - frame_insert_client(second, wins[i]); - } - g_free(wins); - XDestroyWindow(g_display, parent->window); - // now do tree magic - // and make second child the new parent - // set parent - second->parent = parent->parent; - // TODO: call frame destructor here - stack_remove_slice(parent->tag->stack, parent->slice); - slice_destroy(parent->slice); - // copy all other elements - *parent = *second; - // fix childs' parent-pointer - if (parent->type == TYPE_FRAMES) { - parent->content.layout.a->parent = parent; - parent->content.layout.b->parent = parent; - } - g_free(second); - // re-layout - frame_focus_recursive(parent); - monitor_apply_layout(get_current_monitor()); - return 0; -} - -int close_or_remove_command(int argc, char** argv) { - HSClient* client = frame_focused_client(g_cur_frame); - if (client) { - window_close(client->window); - return 0; - } else { - return frame_remove_command(argc, argv); - } -} - -void frame_set_visible(HSFrame* frame, bool visible) { - if (!frame) { - return; - } - if (frame->window_visible == visible) { - return; - } - window_set_visible(frame->window, visible); - frame->window_visible = visible; -} - -// executes action for each client within frame and its subframes -// if action fails (i.e. returns something != 0), then it aborts with this code -int frame_foreach_client(HSFrame* frame, ClientAction action, void* data) { - int status; - if (frame->type == TYPE_FRAMES) { - status = frame_foreach_client(frame->content.layout.a, action, data); - if (0 != status) { - return status; - } - status = frame_foreach_client(frame->content.layout.b, action, data); - if (0 != status) { - return status; - } - } else { - // frame->type == TYPE_CLIENTS - HSClient** buf = frame->content.clients.buf; - size_t count = frame->content.clients.count; - HSClient* client; - for (int i = 0; i < count; i++) { - client = buf[i]; - // do action for each client - status = action(client, data); - if (0 != status) { - return status; - } - } - } - return 0; -} - -void frame_update_border(Window window, unsigned long color) { - if (*g_frame_border_inner_width > 0 && *g_frame_border_inner_width < *g_frame_border_width) { - set_window_double_border(g_display, window, *g_frame_border_inner_width, g_frame_border_inner_color, color); - } else { - XSetWindowBorder(g_display, window, color); - } -} - -int frame_focus_edge(int argc, char** argv, GString* output) { - // Puts the focus to the edge in the specified direction - char* args[] = { "" }; - monitors_lock_command(LENGTH(args), args); - int oldval = *g_focus_crosses_monitor_boundaries; - *g_focus_crosses_monitor_boundaries = 0; - while (0 == frame_focus_command(argc,argv,output)) - ; - *g_focus_crosses_monitor_boundaries = oldval; - monitors_unlock_command(LENGTH(args), args); - return 0; -} - -int frame_move_window_edge(int argc, char** argv, GString* output) { - // Moves a window to the edge in the specified direction - char* args[] = { "" }; - monitors_lock_command(LENGTH(args), args); - int oldval = *g_focus_crosses_monitor_boundaries; - *g_focus_crosses_monitor_boundaries = 0; - while (0 == frame_move_window_command(argc,argv,output)) - ; - *g_focus_crosses_monitor_boundaries = oldval; - monitors_unlock_command(LENGTH(args), args); - return 0; -} - -bool smart_window_surroundings_active(HSFrame* frame) { - return *g_smart_window_surroundings - && (frame->content.clients.count == 1 - || frame->content.clients.layout == LAYOUT_MAX); -} - diff -Nru herbstluftwm-0.6.2/src/layout.cpp herbstluftwm-0.7.0/src/layout.cpp --- herbstluftwm-0.6.2/src/layout.cpp 1970-01-01 00:00:00.000000000 +0000 +++ herbstluftwm-0.7.0/src/layout.cpp 2015-10-14 13:27:40.000000000 +0000 @@ -0,0 +1,2022 @@ +/** Copyright 2011-2013 Thorsten Wißmann. All rights reserved. + * + * This software is licensed under the "Simplified BSD License". + * See LICENSE for details */ + +#include "clientlist.h" +#include "globals.h" +#include "utils.h" +#include "x11-utils.h" +#include "hook.h" +#include "ewmh.h" +#include "ipc-protocol.h" +#include "settings.h" +#include "layout.h" +#include "stack.h" +#include "monitor.h" +#include "floating.h" + +#include +#include "glib-backports.h" +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +static int* g_frame_border_width; +static int* g_frame_border_inner_width; +static int* g_always_show_frame; +static int* g_default_frame_layout; +static int* g_frame_bg_transparent; +static int* g_frame_transparent_width; +static int* g_direction_external_only; +static int* g_gapless_grid; +static int* g_smart_frame_surroundings; +static int* g_smart_window_surroundings; +static int* g_focus_crosses_monitor_boundaries; +static int* g_frame_padding; +static unsigned long g_frame_border_active_color; +static unsigned long g_frame_border_normal_color; +static unsigned long g_frame_border_inner_color; +static unsigned long g_frame_bg_active_color; +static unsigned long g_frame_bg_normal_color; +static unsigned long g_frame_active_opacity; +static unsigned long g_frame_normal_opacity; + +char* g_tree_style = NULL; // used by utils.c +HSFrame* g_cur_frame; // currently selected frame +int* g_frame_gap; +int* g_window_gap; + +const char* g_align_names[] = { + "vertical", + "horizontal", +}; + +const char* g_layout_names[] = { + "vertical", + "horizontal", + "max", + "grid", + NULL, +}; + +static void fetch_frame_colors() { + // load settings + g_frame_gap = &(settings_find("frame_gap")->value.i); + g_frame_padding = &(settings_find("frame_padding")->value.i); + g_window_gap = &(settings_find("window_gap")->value.i); + g_frame_border_width = &(settings_find("frame_border_width")->value.i); + g_frame_border_inner_width = &(settings_find("frame_border_inner_width")->value.i); + g_always_show_frame = &(settings_find("always_show_frame")->value.i); + g_frame_bg_transparent = &(settings_find("frame_bg_transparent")->value.i); + g_frame_transparent_width = &(settings_find("frame_transparent_width")->value.i); + g_default_frame_layout = &(settings_find("default_frame_layout")->value.i); + g_direction_external_only = &(settings_find("default_direction_external_only")->value.i); + g_gapless_grid = &(settings_find("gapless_grid")->value.i); + g_smart_frame_surroundings = &(settings_find("smart_frame_surroundings")->value.i); + g_smart_window_surroundings = &(settings_find("smart_window_surroundings")->value.i); + g_focus_crosses_monitor_boundaries = &(settings_find("focus_crosses_monitor_boundaries")->value.i); + *g_default_frame_layout = CLAMP(*g_default_frame_layout, 0, LAYOUT_COUNT - 1); + char* str = settings_find_string("frame_border_normal_color"); + g_frame_border_normal_color = getcolor(str); + str = settings_find_string("frame_border_active_color"); + g_frame_border_active_color = getcolor(str); + str = settings_find_string("frame_border_inner_color"); + g_frame_border_inner_color = getcolor(str); + // background color + str = settings_find_string("frame_bg_normal_color"); + g_frame_bg_normal_color = getcolor(str); + str = settings_find_string("frame_bg_active_color"); + g_frame_bg_active_color = getcolor(str); + g_frame_active_opacity = CLAMP(settings_find("frame_active_opacity")->value.i, 0, 100); + g_frame_normal_opacity = CLAMP(settings_find("frame_normal_opacity")->value.i, 0, 100); + + // tree style + g_tree_style = settings_find_string("tree_style"); + if (g_utf8_strlen(g_tree_style, -1) < 8) { + g_warning("too few characters in setting tree_style\n"); + // ensure that it is long enough + const char* argv[] = { "set", "tree_style", "01234567" }; + settings_set_command(LENGTH(argv), argv, NULL); + } +} + +void layout_init() { + fetch_frame_colors(); +} +void reset_frame_colors() { + fetch_frame_colors(); + all_monitors_apply_layout(); +} + +void layout_destroy() { +} + + +/* create a new frame + * you can either specify a frame or a tag as its parent + */ +HSFrame* frame_create_empty(HSFrame* parent, HSTag* parenttag) { + HSFrame* frame = g_new0(HSFrame, 1); + frame->type = TYPE_CLIENTS; + frame->window_visible = false; + frame->content.clients.layout = *g_default_frame_layout; + frame->parent = parent; + frame->tag = parent ? parent->tag : parenttag; + // set window attributes + XSetWindowAttributes at; + at.background_pixel = getcolor("red"); + at.background_pixmap = ParentRelative; + at.override_redirect = True; + at.bit_gravity = StaticGravity; + at.event_mask = SubstructureRedirectMask|SubstructureNotifyMask + |ExposureMask|VisibilityChangeMask + |EnterWindowMask|LeaveWindowMask|FocusChangeMask; + frame->window = XCreateWindow(g_display, g_root, + 42, 42, 42, 42, *g_frame_border_width, + DefaultDepth(g_display, DefaultScreen(g_display)), + CopyFromParent, + DefaultVisual(g_display, DefaultScreen(g_display)), + CWOverrideRedirect | CWBackPixmap | CWEventMask, &at); + // insert it to the stack + frame->slice = slice_create_frame(frame->window); + stack_insert_slice(frame->tag->stack, frame->slice); + // set wm_class for window + XClassHint *hint = XAllocClassHint(); + hint->res_name = (char*)HERBST_FRAME_CLASS; + hint->res_class = (char*)HERBST_FRAME_CLASS; + XSetClassHint(g_display, frame->window, hint); + XFree(hint); + return frame; +} + +void frame_insert_client(HSFrame* frame, struct HSClient* client) { + if (frame->type == TYPE_CLIENTS) { + // insert it here + HSClient** buf = frame->content.clients.buf; + // append it to buf + size_t count = frame->content.clients.count; + count++; + // insert it after the selection + int index = frame->content.clients.selection + 1; + index = CLAMP(index, 0, count - 1); + buf = g_renew(HSClient*, buf, count); + // shift other windows to the back to insert the new one at index + memmove(buf + index + 1, buf + index, sizeof(*buf) * (count - index - 1)); + buf[index] = client; + // write results back + frame->content.clients.count = count; + frame->content.clients.buf = buf; + // check for focus + if (g_cur_frame == frame + && frame->content.clients.selection >= (count-1)) { + frame->content.clients.selection = count - 1; + client_window_focus(client); + } + } else { /* frame->type == TYPE_FRAMES */ + HSLayout* layout = &frame->content.layout; + frame_insert_client((layout->selection == 0)? layout->a : layout->b, client); + } +} + +HSFrame* lookup_frame(HSFrame* root, const char *index) { + if (index == NULL || index[0] == '\0') return root; + if (root->type == TYPE_CLIENTS) return root; + + HSFrame* new_root; + const char *new_index = index + 1; + HSLayout* layout = &root->content.layout; + + switch (index[0]) { + case '0': new_root = layout->a; break; + case '1': new_root = layout->b; break; + /* opposite selection */ + case '/': new_root = (layout->selection == 0) + ? layout->b + : layout->a; + break; + /* else just follow selection */ + case '@': new_index = index; + case '.': + default: new_root = (layout->selection == 0) + ? layout->a + : layout->b; break; + } + return lookup_frame(new_root, new_index); +} + +HSFrame* find_frame_with_client(HSFrame* frame, struct HSClient* client) { + if (frame->type == TYPE_CLIENTS) { + HSClient** buf = frame->content.clients.buf; + size_t count = frame->content.clients.count; + for (int i = 0; i < count; i++) { + if (buf[i] == client) { + return frame; + } + } + return NULL; + } else { /* frame->type == TYPE_FRAMES */ + HSFrame* found = find_frame_with_client(frame->content.layout.a, client); + if (!found) { + found = find_frame_with_client(frame->content.layout.b, client); + } + return found; + } +} + +bool frame_remove_client(HSFrame* frame, HSClient* client) { + if (frame->type == TYPE_CLIENTS) { + HSClient** buf = frame->content.clients.buf; + size_t count = frame->content.clients.count; + int i; + for (i = 0; i < count; i++) { + if (buf[i] == client) { + // if window was found + // them remove it + memmove(buf+i, buf+i+1, sizeof(buf[0])*(count - i - 1)); + count--; + buf = g_renew(HSClient*, buf, count); + frame->content.clients.buf = buf; + frame->content.clients.count = count; + // find out new selection + int selection = frame->content.clients.selection; + // if selection was before removed window + // then do nothing + // else shift it by 1 + selection -= (selection < i) ? 0 : 1; + // ensure, that it's a valid index + selection = count ? CLAMP(selection, 0, count-1) : 0; + frame->content.clients.selection = selection; + return true; + } + } + return false; + } else { /* frame->type == TYPE_FRAMES */ + bool found = frame_remove_client(frame->content.layout.a, client); + found = found || frame_remove_client(frame->content.layout.b, client); + return found; + } +} + +void frame_destroy(HSFrame* frame, HSClient*** buf, size_t* count) { + if (frame->type == TYPE_CLIENTS) { + *buf = frame->content.clients.buf; + *count = frame->content.clients.count; + } else { /* frame->type == TYPE_FRAMES */ + size_t c1, c2; + HSClient **buf1, **buf2; + frame_destroy(frame->content.layout.a, &buf1, &c1); + frame_destroy(frame->content.layout.b, &buf2, &c2); + // append buf2 to buf1 + buf1 = g_renew(HSClient*, buf1, c1 + c2); + memcpy(buf1+c1, buf2, sizeof(buf1[0]) * c2); + // free unused things + g_free(buf2); + // return; + *buf = buf1; + *count = c1 + c2; + } + stack_remove_slice(frame->tag->stack, frame->slice); + slice_destroy(frame->slice); + // free other things + XDestroyWindow(g_display, frame->window); + g_free(frame); +} + +void dump_frame_tree(HSFrame* frame, GString* output) { + if (frame->type == TYPE_CLIENTS) { + g_string_append_printf(output, "%cclients%c%s:%d", + LAYOUT_DUMP_BRACKETS[0], + LAYOUT_DUMP_WHITESPACES[0], + g_layout_names[frame->content.clients.layout], + frame->content.clients.selection); + HSClient** buf = frame->content.clients.buf; + size_t i, count = frame->content.clients.count; + for (i = 0; i < count; i++) { + g_string_append_printf(output, "%c0x%lx", + LAYOUT_DUMP_WHITESPACES[0], + buf[i]->window); + } + g_string_append_c(output, LAYOUT_DUMP_BRACKETS[1]); + } else { + /* type == TYPE_FRAMES */ + g_string_append_printf(output, "%csplit%c%s%c%lf%c%d%c", + LAYOUT_DUMP_BRACKETS[0], + LAYOUT_DUMP_WHITESPACES[0], + g_align_names[frame->content.layout.align], + LAYOUT_DUMP_SEPARATOR, + ((double)frame->content.layout.fraction) / (double)FRACTION_UNIT, + LAYOUT_DUMP_SEPARATOR, + frame->content.layout.selection, + LAYOUT_DUMP_WHITESPACES[0]); + dump_frame_tree(frame->content.layout.a, output); + g_string_append_c(output, LAYOUT_DUMP_WHITESPACES[0]); + dump_frame_tree(frame->content.layout.b, output); + g_string_append_c(output, LAYOUT_DUMP_BRACKETS[1]); + } +} + +char* load_frame_tree(HSFrame* frame, char* description, GString* errormsg) { + // find next ( + description = strchr(description, LAYOUT_DUMP_BRACKETS[0]); + if (!description) { + g_string_append_printf(errormsg, "Missing %c\n", + LAYOUT_DUMP_BRACKETS[0]); + return NULL; + } + description++; // jump over ( + + // goto frame type + description += strspn(description, LAYOUT_DUMP_WHITESPACES); + int type = TYPE_CLIENTS; + if (description[0] == 's') { + // if it could be "split" + type = TYPE_FRAMES; + } + + // get substring with frame args + // jump to whitespaces and over them + description += strcspn(description, LAYOUT_DUMP_WHITESPACES); + description += strspn(description, LAYOUT_DUMP_WHITESPACES); + // jump to whitespaces or brackets + size_t args_len = strcspn(description, LAYOUT_DUMP_WHITESPACES LAYOUT_DUMP_BRACKETS); + char* args = new char[args_len + 1]; + std::unique_ptr free_args_correctly (args); + strncpy(args, description, args_len); + args[args_len] = '\0'; + // jump over args substring + description += args_len; + if (!*description) { + g_string_append_printf(errormsg, "Missing %c or arguments\n", LAYOUT_DUMP_BRACKETS[1]); + return NULL; + } + description += strspn(description, LAYOUT_DUMP_WHITESPACES); + if (!*description) { + g_string_append_printf(errormsg, "Missing %c or arguments\n", LAYOUT_DUMP_BRACKETS[1]); + return NULL; + } + + // apply type to frame + if (type == TYPE_FRAMES) { + // parse args + char* align_name = g_new(char, strlen(args)+1); + int selection; + double fraction_double; +#define SEP LAYOUT_DUMP_SEPARATOR_STR + if (3 != sscanf(args, "%[^" SEP "]" SEP "%lf" SEP "%d", + align_name, &fraction_double, &selection)) { + g_string_append_printf(errormsg, + "Can not parse frame args \"%s\"\n", args); + return NULL; + } +#undef SEP + int align = find_align_by_name(align_name); + g_free(align_name); + if (align < 0) { + g_string_append_printf(errormsg, + "Invalid alignment name in args \"%s\"\n", args); + return NULL; + } + selection = !!selection; // CLAMP it to [0;1] + int fraction = (int)(fraction_double * (double)FRACTION_UNIT); + + // ensure that it is split + if (frame->type == TYPE_FRAMES) { + // nothing to do + frame->content.layout.align = align; + frame->content.layout.fraction = fraction; + } else { + frame_split(frame, align, fraction); + if (frame->type != TYPE_FRAMES) { + g_string_append_printf(errormsg, + "Can not split frame\n"); + return NULL; + } + } + frame->content.layout.selection = selection; + + // now parse subframes + description = load_frame_tree(frame->content.layout.a, + description, errormsg); + if (!description) return NULL; + description = load_frame_tree(frame->content.layout.b, + description, errormsg); + if (!description) return NULL; + } else { + // parse args + char* layout_name = g_new(char, strlen(args)+1); + int selection; +#define SEP LAYOUT_DUMP_SEPARATOR_STR + if (2 != sscanf(args, "%[^" SEP "]" SEP "%d", + layout_name, &selection)) { + g_string_append_printf(errormsg, + "Can not parse frame args \"%s\"\n", args); + return NULL; + } +#undef SEP + int layout = find_layout_by_name(layout_name); + g_free(layout_name); + if (layout < 0) { + g_string_append_printf(errormsg, + "Can not parse layout from args \"%s\"\n", args); + return NULL; + } + + // ensure that it is a client frame + if (frame->type == TYPE_FRAMES) { + // remove childs + HSClient **buf1, **buf2; + size_t count1, count2; + frame_destroy(frame->content.layout.a, &buf1, &count1); + frame_destroy(frame->content.layout.b, &buf2, &count2); + + // merge bufs + size_t count = count1 + count2; + HSClient** buf = g_new(HSClient*, count); + memcpy(buf, buf1, sizeof(buf[0]) * count1); + memcpy(buf + count1, buf2, sizeof(buf[0]) * count2); + g_free(buf1); + g_free(buf2); + + // setup frame + frame->type = TYPE_CLIENTS; + frame->content.clients.buf = buf; + frame->content.clients.count = count; + frame->content.clients.selection = 0; // only some sane defaults + frame->content.clients.layout = 0; // only some sane defaults + } + + // bring child wins + // jump over whitespaces + description += strspn(description, LAYOUT_DUMP_WHITESPACES); + int index = 0; + HSTag* tag = find_tag_with_toplevel_frame(get_toplevel_frame(frame)); + while (*description != LAYOUT_DUMP_BRACKETS[1]) { + Window win; + if (1 != sscanf(description, "0x%lx\n", &win)) { + g_string_append_printf(errormsg, + "Can not parse window id from \"%s\"\n", description); + return NULL; + } + // jump over window id and over whitespaces + description += strspn(description, "0x123456789abcdef"); + description += strspn(description, LAYOUT_DUMP_WHITESPACES); + + // bring window here + HSClient* client = get_client_from_window(win); + if (!client) { + // client not managed... ignore it + continue; + } + + // remove window from old tag + HSMonitor* clientmonitor = find_monitor_with_tag(client->tag); + if (!frame_remove_client(client->tag->frame, client)) { + g_warning("window %lx was not found on tag %s\n", + win, client->tag->name->str); + } + if (clientmonitor) { + monitor_apply_layout(clientmonitor); + } + stack_remove_slice(client->tag->stack, client->slice); + + // insert it to buf + HSClient** buf = frame->content.clients.buf; + size_t count = frame->content.clients.count; + count++; + index = CLAMP(index, 0, count - 1); + buf = g_renew(HSClient*, buf, count); + memmove(buf + index + 1, buf + index, + sizeof(buf[0]) * (count - index - 1)); + buf[index] = client; + frame->content.clients.buf = buf; + frame->content.clients.count = count; + + client->tag = tag; + stack_insert_slice(client->tag->stack, client->slice); + ewmh_window_update_tag(client->window, client->tag); + + index++; + } + // apply layout and selection + selection = (selection < frame->content.clients.count) ? selection : 0; + selection = (selection >= 0) ? selection : 0; + frame->content.clients.layout = layout; + frame->content.clients.selection = selection; + } + // jump over closing bracket + if (*description == LAYOUT_DUMP_BRACKETS[1]) { + description++; + } else { + g_string_append_printf(errormsg, "warning: missing closing bracket %c\n", LAYOUT_DUMP_BRACKETS[1]); + } + // and over whitespaces + description += strspn(description, LAYOUT_DUMP_WHITESPACES); + return description; +} + +int find_layout_by_name(char* name) { + for (int i = 0; i < LENGTH(g_layout_names); i++) { + if (!g_layout_names[i]) { + break; + } + if (!strcmp(name, g_layout_names[i])) { + return i; + } + } + return -1; +} + +int find_align_by_name(char* name) { + for (int i = 0; i < LENGTH(g_align_names); i++) { + if (!strcmp(name, g_align_names[i])) { + return i; + } + } + return -1; +} + +HSFrame* get_toplevel_frame(HSFrame* frame) { + if (!frame) return NULL; + while (frame->parent) { + frame = frame->parent; + } + return frame; +} + +static void frame_append_caption(HSTree tree, GString* output) { + HSFrame* frame = (HSFrame*) tree; + if (frame->type == TYPE_CLIENTS) { + // list of clients + g_string_append_printf(output, "%s:", + g_layout_names[frame->content.clients.layout]); + HSClient** buf = frame->content.clients.buf; + size_t i, count = frame->content.clients.count; + for (i = 0; i < count; i++) { + g_string_append_printf(output, " 0x%lx", buf[i]->window); + } + if (g_cur_frame == frame) { + g_string_append(output, " [FOCUS]"); + } + } else { + /* type == TYPE_FRAMES */ + g_string_append_printf(output, "%s %d%% selection=%d", + g_layout_names[frame->content.layout.align], + frame->content.layout.fraction * 100 / FRACTION_UNIT, + frame->content.layout.selection); + } +} + +static size_t frame_child_count(HSTree tree) { + HSFrame* frame = (HSFrame*) tree; + return (frame->type == TYPE_CLIENTS) ? 0 : 2; +} + +static HSTreeInterface frame_nth_child(HSTree tree, size_t idx) { + HSFrame* frame = (HSFrame*) tree; + assert(frame->type != TYPE_CLIENTS); + HSTreeInterface intf = { + /* .nth_child = */ frame_nth_child, + /* .child_count = */ frame_child_count, + /* .append_caption = */ frame_append_caption, + /* .data = */ (idx == 0) + ? frame->content.layout.a + : frame->content.layout.b, + /* .destructor = */ NULL, + }; + return intf; +} + +void print_frame_tree(HSFrame* frame, GString* output) { + HSTreeInterface frameintf = { + /* .nth_child = */ frame_nth_child, + /* .child_count = */ frame_child_count, + /* .append_caption = */ frame_append_caption, + /* .data = */ (HSTree) frame, + /* .destructor = */ NULL, + }; + tree_print_to(&frameintf, output); +} + +void frame_apply_floating_layout(HSFrame* frame, HSMonitor* m) { + if (!frame) return; + if (frame->type == TYPE_FRAMES) { + frame_apply_floating_layout(frame->content.layout.a, m); + frame_apply_floating_layout(frame->content.layout.b, m); + } else { + /* if(frame->type == TYPE_CLIENTS) */ + frame_set_visible(frame, false); + HSClient** buf = frame->content.clients.buf; + size_t count = frame->content.clients.count; + size_t selection = frame->content.clients.selection; + /* border color */ + for (int i = 0; i < count; i++) { + HSClient* client = buf[i]; + client_setup_border(client, (g_cur_frame == frame) && (i == selection)); + client_resize_floating(client, m); + } + } +} + +int frame_current_cycle_client_layout(int argc, char** argv, GString* output) { + char* cmd_name = argv[0]; // save this before shifting + int delta = 1; + if (argc >= 2) { + delta = atoi(argv[1]); + } + assert(g_cur_frame && g_cur_frame->type == TYPE_CLIENTS); + (void)SHIFT(argc, argv); + (void)SHIFT(argc, argv); + int layout_index; + if (argc > 0) { + /* cycle through a given list of layouts */ + const char* curname = g_layout_names[g_cur_frame->content.clients.layout]; + char** pcurrent = (char**)table_find(argv, sizeof(*argv), argc, 0, + memberequals_string, curname); + int idx = pcurrent ? (INDEX_OF(argv, pcurrent) + delta) : 0; + idx %= argc; + idx += argc; + idx %= argc; + layout_index = find_layout_by_name(argv[idx]); + if (layout_index < 0) { + g_string_append_printf(output, + "%s: Invalid layout name \"%s\"\n", cmd_name, argv[idx]); + return HERBST_INVALID_ARGUMENT; + } + } else { + /* cycle through the default list of layouts */ + layout_index = g_cur_frame->content.clients.layout + delta; + layout_index %= LAYOUT_COUNT; + layout_index += LAYOUT_COUNT; + layout_index %= LAYOUT_COUNT; + } + g_cur_frame->content.clients.layout = layout_index; + monitor_apply_layout(get_current_monitor()); + return 0; +} + +int frame_current_set_client_layout(int argc, char** argv, GString* output) { + int layout = 0; + if (argc <= 1) { + return HERBST_NEED_MORE_ARGS; + } + layout = find_layout_by_name(argv[1]); + if (layout < 0) { + g_string_append_printf(output, + "%s: Invalid layout name \"%s\"\n", argv[0], argv[1]); + return HERBST_INVALID_ARGUMENT; + } + if (g_cur_frame && g_cur_frame->type == TYPE_CLIENTS) { + g_cur_frame->content.clients.layout = layout; + monitor_apply_layout(get_current_monitor()); + } + return 0; +} + +void frame_apply_client_layout_linear(HSFrame* frame, Rectangle rect, bool vertical) { + HSClient** buf = frame->content.clients.buf; + size_t count = frame->content.clients.count; + Rectangle cur = rect; + int last_step_y; + int last_step_x; + int step_y; + int step_x; + if (vertical) { + // only do steps in y direction + last_step_y = cur.height % count; // get the space on bottom + last_step_x = 0; + cur.height /= count; + step_y = cur.height; + step_x = 0; + } else { + // only do steps in x direction + last_step_y = 0; + last_step_x = cur.width % count; // get the space on the right + cur.width /= count; + step_y = 0; + step_x = cur.width; + } + for (int i = 0; i < count; i++) { + HSClient* client = buf[i]; + // add the space, if count does not divide frameheight without remainder + cur.height += (i == count-1) ? last_step_y : 0; + cur.width += (i == count-1) ? last_step_x : 0; + client_resize_tiling(client, cur, frame); + cur.y += step_y; + cur.x += step_x; + } +} + +void frame_apply_client_layout_horizontal(HSFrame* frame, Rectangle rect) { + frame_apply_client_layout_linear(frame, rect, false); +} + +void frame_apply_client_layout_vertical(HSFrame* frame, Rectangle rect) { + frame_apply_client_layout_linear(frame, rect, true); +} + +void frame_apply_client_layout_max(HSFrame* frame, Rectangle rect) { + HSClient** buf = frame->content.clients.buf; + size_t count = frame->content.clients.count; + int selection = frame->content.clients.selection; + for (int i = 0; i < count; i++) { + HSClient* client = buf[i]; + client_setup_border(client, (g_cur_frame == frame) && (i == selection)); + client_resize_tiling(client, rect, frame); + if (i == selection) { + client_raise(client); + } + } +} + +void frame_layout_grid_get_size(size_t count, int* res_rows, int* res_cols) { + int cols = 0; + while (cols * cols < count) { + cols++; + } + *res_cols = cols; + if (*res_cols != 0) { + *res_rows = (count / cols) + (count % cols ? 1 : 0); + } else { + *res_rows = 0; + } +} + +void frame_apply_client_layout_grid(HSFrame* frame, Rectangle rect) { + HSClient** buf = frame->content.clients.buf; + size_t count = frame->content.clients.count; + int selection = frame->content.clients.selection; + if (count == 0) { + return; + } + + int rows, cols; + frame_layout_grid_get_size(count, &rows, &cols); + int width = rect.width / cols; + int height = rect.height / rows; + int i = 0; + Rectangle cur = rect; // current rectangle + for (int r = 0; r < rows; r++) { + // reset to left + cur.x = rect.x; + cur.width = width; + cur.height = height; + if (r == rows -1) { + // fill small pixel gap below last row + cur.height += rect.height % rows; + } + for (int c = 0; c < cols && i < count; c++) { + if (*g_gapless_grid && (i == count - 1) // if last client + && (count % cols != 0)) { // if cols remain + // fill remaining cols with client + cur.width = rect.x + rect.width - cur.x; + } else if (c == cols - 1) { + // fill small pixel gap in last col + cur.width += rect.width % cols; + } + + // apply size + HSClient* client = buf[i]; + client_setup_border(client, (g_cur_frame == frame) && (i == selection)); + client_resize_tiling(client, cur, frame); + cur.x += width; + i++; + } + cur.y += height; + } + +} + +void frame_apply_layout(HSFrame* frame, Rectangle rect) { + frame->last_rect = rect; + if (frame->type == TYPE_CLIENTS) { + size_t count = frame->content.clients.count; + if (!*g_smart_frame_surroundings || frame->parent) { + // apply frame gap + rect.height -= *g_frame_gap; + rect.width -= *g_frame_gap; + // apply frame border + rect.x += *g_frame_border_width; + rect.y += *g_frame_border_width; + rect.height -= *g_frame_border_width * 2; + rect.width -= *g_frame_border_width * 2; + } + + rect.width = MAX(WINDOW_MIN_WIDTH, rect.width); + rect.height = MAX(WINDOW_MIN_HEIGHT, rect.height); + + unsigned long border_color = g_frame_border_normal_color; + unsigned long bg_color = g_frame_bg_normal_color; + int bw = *g_frame_border_width; + if (g_cur_frame == frame) { + border_color = g_frame_border_active_color; + bg_color = g_frame_bg_active_color; + } + if (*g_smart_frame_surroundings && !frame->parent) { + bw = 0; + } + XSetWindowBorderWidth(g_display, frame->window, bw); + XMoveResizeWindow(g_display, frame->window, + rect.x - bw, + rect.y - bw, + rect.width, rect.height); + + frame_update_border(frame->window, border_color); + + XSetWindowBackground(g_display, frame->window, bg_color); + if (*g_frame_bg_transparent) { // != ) { + window_cut_rect_hole(frame->window, rect.width, rect.height, + *g_frame_transparent_width); + } else if (frame->window_transparent) { + window_make_intransparent(frame->window, rect.width, rect.height); + } + frame->window_transparent = *g_frame_bg_transparent; + if (g_cur_frame == frame) { + ewmh_set_window_opacity(frame->window, g_frame_active_opacity/100.0); + } else { + ewmh_set_window_opacity(frame->window, g_frame_normal_opacity/100.0); + } + XClearWindow(g_display, frame->window); + // move windows + if (count == 0) { + return; + } + + if (!smart_window_surroundings_active(frame)) { + // apply window gap + rect.x += *g_window_gap; + rect.y += *g_window_gap; + rect.width -= *g_window_gap; + rect.height -= *g_window_gap; + + // apply frame padding + rect.x += *g_frame_padding; + rect.y += *g_frame_padding; + rect.width -= *g_frame_padding * 2; + rect.height -= *g_frame_padding * 2; + } + + if (frame->content.clients.layout == LAYOUT_MAX) { + frame_apply_client_layout_max(frame, rect); + } else if (frame->content.clients.layout == LAYOUT_GRID) { + frame_apply_client_layout_grid(frame, rect); + } else { + frame_apply_client_layout_linear(frame, rect, + (frame->content.clients.layout == LAYOUT_VERTICAL)); + } + } else { /* frame->type == TYPE_FRAMES */ + HSLayout* layout = &frame->content.layout; + Rectangle first = rect; + Rectangle second = rect; + if (layout->align == ALIGN_VERTICAL) { + first.height = (rect.height * layout->fraction) / FRACTION_UNIT; + second.y += first.height; + second.height -= first.height; + } else { // (layout->align == ALIGN_HORIZONTAL) + first.width = (rect.width * layout->fraction) / FRACTION_UNIT; + second.x += first.width; + second.width -= first.width; + } + frame_apply_layout(layout->a, first); + frame_apply_layout(layout->b, second); + } +} + +static void frame_update_frame_window_visibility_helper(HSFrame* frame) { + if (frame->type == TYPE_CLIENTS) { + int count = frame->content.clients.count; + frame_set_visible(frame, *g_always_show_frame + || (count != 0) || (g_cur_frame == frame)); + } else { + frame_set_visible(frame, false); + } +} + +void frame_update_frame_window_visibility(HSFrame* frame) { + frame_do_recursive(frame, frame_update_frame_window_visibility_helper, 2); +} + +HSFrame* frame_current_selection_below(HSFrame* frame) { + while (frame->type == TYPE_FRAMES) { + frame = (frame->content.layout.selection == 0) ? + frame->content.layout.a : + frame->content.layout.b; + } + return frame; +} + +HSFrame* frame_current_selection() { + HSMonitor* m = get_current_monitor(); + if (!m->tag) return NULL; + return frame_current_selection_below(m->tag->frame); +} + +int frame_current_bring(int argc, char** argv, GString* output) { + HSClient* client = NULL; + + if (argc < 2) { + return HERBST_NEED_MORE_ARGS; + } + string_to_client(argv[1], &client); + if (!client) { + g_string_append_printf(output, + "%s: Could not find client", argv[0]); + if (argc > 1) { + g_string_append_printf(output, " \"%s\".\n", argv[1]); + } else { + g_string_append(output, ".\n"); + } + return HERBST_INVALID_ARGUMENT; + } + HSTag* tag = get_current_monitor()->tag; + tag_move_client(client, tag); + HSFrame* frame = find_frame_with_client(tag->frame, client); + if (frame != g_cur_frame) { + frame_remove_client(frame, client); + frame_insert_client(g_cur_frame, client); + } + focus_client(client, false, false); + return 0; +} + +int frame_current_set_selection(int argc, char** argv) { + int index = 0; + if (argc >= 2) { + index = atoi(argv[1]); + } else { + return HERBST_NEED_MORE_ARGS; + } + // find current selection + HSFrame* frame = frame_current_selection(); + if (frame->content.clients.count == 0) { + // nothing to do + return 0; + } + if (index < 0 || index >= frame->content.clients.count) { + index = frame->content.clients.count - 1; + } + frame->content.clients.selection = index; + HSClient* client = frame->content.clients.buf[index]; + client_window_focus(client); + return 0; +} + +int frame_current_cycle_selection(int argc, char** argv) { + int delta = 1; + if (argc >= 2) { + delta = atoi(argv[1]); + } + // find current selection + HSFrame* frame = frame_current_selection(); + if (frame->content.clients.count == 0) { + // nothing to do + return 0; + } + int index = frame->content.clients.selection; + // use an integer variable to avoid strange happenings when computing + // (-1) % (size_t)6 + int count = (int) frame->content.clients.count; + index += delta; + index %= count; + index += count; + index %= count; + frame->content.clients.selection = index; + monitor_apply_layout(get_current_monitor()); + return 0; +} + +int cycle_all_command(int argc, char** argv) { + int delta = 1; + bool skip_invisible = false; + if (argc >= 2) { + if (!strcmp(argv[1], "--skip-invisible")) { + skip_invisible = true; + (void) SHIFT(argc, argv); + } + } + if (argc >= 2) { + delta = atoi(argv[1]); + } + delta = CLAMP(delta, -1, 1); // only delta -1, 0, 1 is allowed + if (delta == 0) { + // nothing to do + return 0; + } + // find current selection + HSFrame* frame = frame_current_selection(); + int index = frame->content.clients.selection; + bool change_frame = false; + int direction; + int new_window_index; // tells where to start in a new frame + if (delta > 0 && (index + 1) >= frame->content.clients.count) { + // change selection from 0 to 1 + direction = 1; + change_frame = true; + new_window_index = 0; // focus first window in in a frame + } + if (delta < 0 && index == 0) { + // change selection from 1 to 0 + direction = 0; + change_frame = true; + new_window_index = -1; // focus last window in in a frame + } + if (skip_invisible && frame->content.clients.layout == LAYOUT_MAX) { + direction = (delta > 0) ? 1 : 0; + change_frame = true; + } + if (change_frame) { + cycle_frame(direction, new_window_index, skip_invisible); + } else { + // only change the selection within one frame + index += delta; + // ensure it is a valid index + index %= frame->content.clients.count; + index += frame->content.clients.count; + index %= frame->content.clients.count; + frame->content.clients.selection = index; + } + HSClient* c = frame_focused_client(g_cur_frame); + if (c) { + client_raise(c); + } + monitor_apply_layout(get_current_monitor()); + return 0; +} + +int cycle_frame_command(int argc, char** argv) { + int delta = 1; + if (argc >= 2) { + delta = atoi(argv[1]); + } + if (delta == 0) return 0; + int direction = (delta > 0) ? 1 : 0; + cycle_frame(direction, -2, false); + return 0; +} + +// cycle_frame: +// direction: 1 for forward cycling, 0 for backwards cycling +// new_window_index: which index in the new frame should be focused, -2 does +// not change the window selection in the new frame +// skip_invisible: if set, don't touch the selection in frames in max layout +void cycle_frame(int direction, int new_window_index, bool skip_invisible) { + HSFrame* frame = frame_current_selection(); + int other_direction = 1 - direction; + if (true) { + HSFrame* top_frame; + // these things can be visualized easily for direction = 1 + /* + * . + * / \ + * . \ + * / \ ... + * / \ + * . . + * / \ / \ + * . * . . + * the star shows the current focus + */ + // go to next frame in tree + // find first frame, where we can change the selection from 0 to 1 + // i.e. from other_direction to direction we want to use + while (frame->parent && frame->parent->content.layout.selection == direction) { + frame = frame->parent; + } + /* + * . + * / \ + * . \ + * / \ ... + * / \ + * * . + * / \ / \ + * . . . . + */ + if (frame->parent) { + // go to the top + frame = frame->parent; + } else { + // if we reached the top, do nothing.. + } + top_frame = frame; + /* \ . + * \ / \ + * `-> * \ + * / \ ... + * / \ + * . . + * / \ / \ + * . . . . + */ + // go one step to the right (i.e. in desired direction + if (frame->type == TYPE_FRAMES) { + int oldselection = frame->content.layout.selection; + if (oldselection == direction) { + // if we already reached the end, + // i.e. if we cannot go in the desired direction + // then wrap around + frame->content.layout.selection = other_direction; + frame = frame->content.layout.a; + } else { + frame->content.layout.selection = direction; + frame = frame->content.layout.b; + } + } + /* + * . + * / \ + * . \ + * / \ ... + * / \ + * . * + * / \ / \ + * . . . . + */ + // and then to the left (i.e. find first leaf) + while (frame->type == TYPE_FRAMES) { + // then go deeper, with the other direction + frame->content.layout.selection = other_direction; + frame = frame->content.layout.a; + } + /* . + * / \ + * . \ + * / \ ... + * / \ + * . . + * / \ / \ + * . . * . + */ + // now we reached the next client containing frame + + if (new_window_index != -2 && frame->content.clients.count > 0) { + if (!skip_invisible + || frame->content.clients.layout != LAYOUT_MAX) + { + frame->content.clients.selection = new_window_index; + // ensure it is a valid index + size_t count = frame->content.clients.count; + frame->content.clients.selection += count; + frame->content.clients.selection %= count; + } + } + + // reset focus and g_cur_frame + // all changes were made below top_frame + frame_focus_recursive(top_frame); + + } + monitor_apply_layout(get_current_monitor()); + return; +} + + +// counts the splits of a kind align to root window +int frame_split_count_to_root(HSFrame* frame, int align) { + int height = 0; + // count steps until root node + // root node has no parent + while (frame->parent) { + frame = frame->parent; + if (frame->content.layout.align == align) { + height++; + } + } + return height; +} + +bool frame_split(HSFrame* frame, int align, int fraction) { + if (frame_split_count_to_root(frame, align) > HERBST_MAX_TREE_HEIGHT) { + // do nothing if tree would be to large + return false; + } + assert(frame->type == TYPE_CLIENTS); + // ensure fraction is allowed + fraction = CLAMP(fraction, + FRACTION_UNIT * (0.0 + FRAME_MIN_FRACTION), + FRACTION_UNIT * (1.0 - FRAME_MIN_FRACTION)); + + HSFrame* first = frame_create_empty(frame, NULL); + HSFrame* second = frame_create_empty(frame, NULL); + first->content = frame->content; + first->type = frame->type; + second->type = TYPE_CLIENTS; + frame->type = TYPE_FRAMES; + frame->content.layout.align = align; + frame->content.layout.a = first; + frame->content.layout.b = second; + frame->content.layout.selection = 0; + frame->content.layout.fraction = fraction; + return true; +} + +int frame_split_command(int argc, char** argv, GString* output) { + // usage: split t|b|l|r|h|v FRACTION + if (argc < 2) { + return HERBST_NEED_MORE_ARGS; + } + int align = -1; + bool userDefinedFraction = true; + const char* strFraction = "0.5"; + if (argc < 3) { + userDefinedFraction = false; + } else { + strFraction = argv[2]; + } + bool frameToFirst = true; + int fraction = FRACTION_UNIT* CLAMP(atof(strFraction), + 0.0 + FRAME_MIN_FRACTION, + 1.0 - FRAME_MIN_FRACTION); + int selection = 0; + int lh = g_cur_frame->last_rect.height; + int lw = g_cur_frame->last_rect.width; + int align_auto = (lw > lh) ? ALIGN_HORIZONTAL : ALIGN_VERTICAL; + struct { + const char* name; + int align; + bool frameToFirst; // if former frame moves to first child + int selection; // which child to select after the split + } splitModes[] = { + { "top", ALIGN_VERTICAL, false, 1 }, + { "bottom", ALIGN_VERTICAL, true, 0 }, + { "vertical", ALIGN_VERTICAL, true, 0 }, + { "right", ALIGN_HORIZONTAL, true, 0 }, + { "horizontal", ALIGN_HORIZONTAL, true, 0 }, + { "left", ALIGN_HORIZONTAL, false, 1 }, + { "explode", ALIGN_EXPLODE, true, 0 }, + { "auto", align_auto, true, 0 }, + }; + for (int i = 0; i < LENGTH(splitModes); i++) { + if (splitModes[i].name[0] == argv[1][0]) { + align = splitModes[i].align; + frameToFirst = splitModes[i].frameToFirst; + selection = splitModes[i].selection; + break; + } + } + if (align < 0) { + g_string_append_printf(output, + "%s: Invalid alignment \"%s\"\n", argv[0], argv[1]); + return HERBST_INVALID_ARGUMENT; + } + HSFrame* frame = frame_current_selection(); + if (!frame) return 0; // nothing to do + bool exploding = align == ALIGN_EXPLODE; + int layout = frame->content.clients.layout; + int windowcount = frame->content.clients.count; + if (exploding) { + if (windowcount <= 1) { + align = align_auto; + } else if (layout == LAYOUT_MAX) { + align = align_auto; + } else if (layout == LAYOUT_GRID && windowcount == 2) { + align = ALIGN_HORIZONTAL; + } else if (layout == LAYOUT_HORIZONTAL) { + align = ALIGN_HORIZONTAL; + } else { + align = ALIGN_VERTICAL; + } + int layout = frame->content.clients.layout; + size_t count1 = frame->content.clients.count; + size_t nc1 = (count1 + 1) / 2; // new count for the first frame + if ((layout == LAYOUT_HORIZONTAL || layout == LAYOUT_VERTICAL) + && !userDefinedFraction && count1 > 1) { + fraction = (nc1 * FRACTION_UNIT) / count1; + } + } + if (!frame_split(frame, align, fraction)) { + return 0; + } + if (exploding) { + // move second half of the window buf to second frame + size_t count1 = frame->content.layout.a->content.clients.count; + size_t count2 = frame->content.layout.b->content.clients.count; + // assert: count2 == 0 + size_t nc1 = (count1 + 1) / 2; // new count for the first frame + size_t nc2 = count1 - nc1 + count2; // new count for the 2nd frame + HSFrame* child1 = frame->content.layout.a; + HSFrame* child2 = frame->content.layout.b; + HSClient*** buf1 = &child1->content.clients.buf; + HSClient*** buf2 = &child2->content.clients.buf; + *buf2 = g_renew(HSClient*, *buf2, nc2); + memcpy(*buf2 + count2, *buf1 + nc1, (nc2 - count2) * sizeof(**buf2)); + *buf1 = g_renew(HSClient*, *buf1, nc1); + child1->content.clients.count = nc1; + child2->content.clients.count = nc2; + child2->content.clients.layout = child1->content.clients.layout; + if (child1->content.clients.selection >= nc1 && nc1 > 0) { + child2->content.clients.selection = + child1->content.clients.selection - nc1 + count2; + child1->content.clients.selection = nc1 - 1; + selection = 1; + } else { + selection = 0; + } + + } else if (!frameToFirst) { + HSFrame* emptyFrame = frame->content.layout.b; + frame->content.layout.b = frame->content.layout.a; + frame->content.layout.a = emptyFrame; + } + frame->content.layout.selection = selection; + // reset focus + g_cur_frame = frame_current_selection(); + // redraw monitor + monitor_apply_layout(get_current_monitor()); + return 0; +} + +int frame_change_fraction_command(int argc, char** argv, GString* output) { + // usage: fraction DIRECTION DELTA + if (argc < 3) { + return HERBST_NEED_MORE_ARGS; + } + char direction = argv[1][0]; + double delta_double = atof(argv[2]); + delta_double = CLAMP(delta_double, -1.0, 1.0); + int delta = FRACTION_UNIT * delta_double; + // if direction is left or up we have to flip delta + // because e.g. resize up by 0.1 actually means: + // reduce fraction by 0.1, i.e. delta = -0.1 + switch (direction) { + case 'l': delta *= -1; break; + case 'r': break; + case 'u': delta *= -1; break; + case 'd': break; + default: + g_string_append_printf(output, + "%s: Invalid direction \"%s\"\n", argv[0], argv[1]); + return HERBST_INVALID_ARGUMENT; + } + HSFrame* neighbour = frame_neighbour(g_cur_frame, direction); + if (!neighbour) { + // then try opposite direction + switch (direction) { + case 'l': direction = 'r'; break; + case 'r': direction = 'l'; break; + case 'u': direction = 'd'; break; + case 'd': direction = 'u'; break; + default: assert(false); break; + } + neighbour = frame_neighbour(g_cur_frame, direction); + if (!neighbour) { + g_string_append_printf(output, + "%s: No neighbour found\n", argv[0]); + return HERBST_FORBIDDEN; + } + } + HSFrame* parent = neighbour->parent; + assert(parent != NULL); // if has neighbour, it also must have a parent + assert(parent->type == TYPE_FRAMES); + int fraction = parent->content.layout.fraction; + fraction += delta; + fraction = CLAMP(fraction, (int)(FRAME_MIN_FRACTION * FRACTION_UNIT), (int)((1.0 - FRAME_MIN_FRACTION) * FRACTION_UNIT)); + parent->content.layout.fraction = fraction; + // arrange monitor + monitor_apply_layout(get_current_monitor()); + return 0; +} + +HSFrame* frame_neighbour(HSFrame* frame, char direction) { + HSFrame* other; + bool found = false; + while (frame->parent) { + // find frame, where we can change the + // selection in the desired direction + HSLayout* layout = &frame->parent->content.layout; + switch(direction) { + case 'r': + if (layout->align == ALIGN_HORIZONTAL + && layout->a == frame) { + found = true; + other = layout->b; + } + break; + case 'l': + if (layout->align == ALIGN_HORIZONTAL + && layout->b == frame) { + found = true; + other = layout->a; + } + break; + case 'd': + if (layout->align == ALIGN_VERTICAL + && layout->a == frame) { + found = true; + other = layout->b; + } + break; + case 'u': + if (layout->align == ALIGN_VERTICAL + && layout->b == frame) { + found = true; + other = layout->a; + } + break; + default: + return NULL; + break; + } + if (found) { + break; + } + // else: go one step closer to root + frame = frame->parent; + } + if (!found) { + return NULL; + } + return other; +} + +// finds a neighbour within frame in the specified direction +// returns its index or -1 if there is none +int frame_inner_neighbour_index(HSFrame* frame, char direction) { + int index = -1; + if (frame->type != TYPE_CLIENTS) { + fprintf(stderr, "warning: frame has invalid type\n"); + return -1; + } + int selection = frame->content.clients.selection; + int count = frame->content.clients.count; + int rows, cols; + switch (frame->content.clients.layout) { + case LAYOUT_VERTICAL: + if (direction == 'd') index = selection + 1; + if (direction == 'u') index = selection - 1; + break; + case LAYOUT_HORIZONTAL: + if (direction == 'r') index = selection + 1; + if (direction == 'l') index = selection - 1; + break; + case LAYOUT_MAX: + break; + case LAYOUT_GRID: { + frame_layout_grid_get_size(count, &rows, &cols); + if (cols == 0) break; + int r = selection / cols; + int c = selection % cols; + switch (direction) { + case 'd': + index = selection + cols; + if (*g_gapless_grid && index >= count && r == (rows - 2)) { + // if grid is gapless and we're in the second-last row + // then it means last client is below us + index = count - 1; + } + break; + case 'u': index = selection - cols; break; + case 'r': if (c < cols-1) index = selection + 1; break; + case 'l': if (c > 0) index = selection - 1; break; + } + break; + } + default: + break; + } + // check that index is valid + if (index < 0 || index >= count) { + index = -1; + } + return index; +} + +int frame_focus_command(int argc, char** argv, GString* output) { + // usage: focus [-e|-i] left|right|up|down + if (argc < 2) return HERBST_NEED_MORE_ARGS; + if (!g_cur_frame) { + fprintf(stderr, "warning: no frame is selected\n"); + return HERBST_UNKNOWN_ERROR; + } + int external_only = *g_direction_external_only; + char direction = argv[1][0]; + if (argc > 2 && !strcmp(argv[1], "-i")) { + external_only = false; + direction = argv[2][0]; + } + if (argc > 2 && !strcmp(argv[1], "-e")) { + external_only = true; + direction = argv[2][0]; + } + //HSFrame* frame = g_cur_frame; + int index; + bool neighbour_found = true; + if (g_cur_frame->tag->floating) { + enum HSDirection dir = char_to_direction(direction); + if (dir < 0) return HERBST_INVALID_ARGUMENT; + neighbour_found = floating_focus_direction(dir); + } else if (!external_only && + (index = frame_inner_neighbour_index(g_cur_frame, direction)) != -1) { + g_cur_frame->content.clients.selection = index; + frame_focus_recursive(g_cur_frame); + monitor_apply_layout(get_current_monitor()); + } else { + HSFrame* neighbour = frame_neighbour(g_cur_frame, direction); + if (neighbour != NULL) { // if neighbour was found + HSFrame* parent = neighbour->parent; + // alter focus (from 0 to 1, from 1 to 0) + int selection = parent->content.layout.selection; + selection = (selection == 1) ? 0 : 1; + parent->content.layout.selection = selection; + // change focus if possible + frame_focus_recursive(parent); + monitor_apply_layout(get_current_monitor()); + } else { + neighbour_found = false; + } + } + if (!neighbour_found && !*g_focus_crosses_monitor_boundaries) { + g_string_append_printf(output, + "%s: No neighbour found\n", argv[0]); + return HERBST_FORBIDDEN; + } + if (!neighbour_found && *g_focus_crosses_monitor_boundaries) { + // find monitor in the specified direction + enum HSDirection dir = char_to_direction(direction); + if (dir < 0) return HERBST_INVALID_ARGUMENT; + int idx = monitor_index_in_direction(get_current_monitor(), dir); + if (idx < 0) { + g_string_append_printf(output, + "%s: No neighbour found\n", argv[0]); + return HERBST_FORBIDDEN; + } + monitor_focus_by_index(idx); + } + return 0; +} + +int frame_move_window_command(int argc, char** argv, GString* output) { + // usage: move left|right|up|down + if (argc < 2) return HERBST_NEED_MORE_ARGS; + if (!g_cur_frame) { + fprintf(stderr, "error: no frame is selected\n"); + return HERBST_UNKNOWN_ERROR; + } + char direction = argv[1][0]; + int external_only = *g_direction_external_only; + if (argc > 2 && !strcmp(argv[1], "-i")) { + external_only = false; + direction = argv[2][0]; + } + if (argc > 2 && !strcmp(argv[1], "-e")) { + external_only = true; + direction = argv[2][0]; + } + if (!frame_focused_client(g_cur_frame)) { + return HERBST_FORBIDDEN; + } + if (is_client_floated(get_current_client())) { + // try to move the floating window + enum HSDirection dir = char_to_direction(direction); + if (dir < 0) return HERBST_INVALID_ARGUMENT; + bool success = floating_shift_direction(dir); + return success ? 0 : HERBST_FORBIDDEN; + } + int index; + if (!external_only && + (index = frame_inner_neighbour_index(g_cur_frame, direction)) != -1) { + int selection = g_cur_frame->content.clients.selection; + HSClient** buf = g_cur_frame->content.clients.buf; + // if internal neighbour was found, then swap + HSClient* tmp = buf[selection]; + buf[selection] = buf[index]; + buf[index] = tmp; + + g_cur_frame->content.clients.selection = index; + frame_focus_recursive(g_cur_frame); + monitor_apply_layout(get_current_monitor()); + } else { + HSFrame* neighbour = frame_neighbour(g_cur_frame, direction); + HSClient* client = frame_focused_client(g_cur_frame); + if (client && neighbour != NULL) { // if neighbour was found + // move window to neighbour + frame_remove_client(g_cur_frame, client); + frame_insert_client(neighbour, client); + + // change selection in parent + HSFrame* parent = neighbour->parent; + assert(parent); + parent->content.layout.selection = ! parent->content.layout.selection; + frame_focus_recursive(parent); + // focus right window in frame + HSFrame* frame = g_cur_frame; + assert(frame); + int i; + HSClient** buf = frame->content.clients.buf; + size_t count = frame->content.clients.count; + for (i = 0; i < count; i++) { + if (buf[i] == client) { + frame->content.clients.selection = i; + client_window_focus(buf[i]); + break; + } + } + + // layout was changed, so update it + monitor_apply_layout(get_current_monitor()); + } else { + g_string_append_printf(output, + "%s: No neighbour found\n", argv[0]); + return HERBST_FORBIDDEN; + } + } + return 0; +} + +void frame_unfocus() { + //XSetInputFocus(g_display, g_root, RevertToPointerRoot, CurrentTime); +} + +HSClient* frame_focused_client(HSFrame* frame) { + if (!frame) { + return NULL; + } + // follow the selection to a leaf + while (frame->type == TYPE_FRAMES) { + frame = (frame->content.layout.selection == 0) ? + frame->content.layout.a : + frame->content.layout.b; + } + if (frame->content.clients.count) { + int selection = frame->content.clients.selection; + return frame->content.clients.buf[selection]; + } // else, if there are no windows + return NULL; +} + +// try to focus window in frame +// it does not require anything from the frame. it may be infocused or even +// hidden. +// returns true if win was found and focused, else returns false +bool frame_focus_client(HSFrame* frame, HSClient* client) { + if (!frame) { + return false; + } + if (frame->type == TYPE_CLIENTS) { + int i; + size_t count = frame->content.clients.count; + HSClient** buf = frame->content.clients.buf; + // search for win in buf + for (i = 0; i < count; i++) { + if (buf[i] == client) { + // if found, set focus to it + frame->content.clients.selection = i; + return true; + } + } + return false; + } else { + // type == TYPE_FRAMES + // search in subframes + bool found = frame_focus_client(frame->content.layout.a, client); + if (found) { + // set selection to first frame + frame->content.layout.selection = 0; + return true; + } + found = frame_focus_client(frame->content.layout.b, client); + if (found) { + // set selection to second frame + frame->content.layout.selection = 1; + return true; + } + return false; + } +} + +// focus a window +// switch_tag tells, whether to switch tag to focus to window +// switch_monitor tells, whether to switch monitor to focus to window +// returns if window was focused or not +bool focus_client(struct HSClient* client, bool switch_tag, bool switch_monitor) { + if (!client) { + // client is not managed + return false; + } + HSTag* tag = client->tag; + assert(client->tag); + HSMonitor* monitor = find_monitor_with_tag(tag); + HSMonitor* cur_mon = get_current_monitor(); + if (monitor != cur_mon && !switch_monitor) { + // if we are not allowed to switch tag + // and tag is not on current monitor (or on no monitor) + // than we cannot focus the window + return false; + } + if (monitor == NULL && !switch_tag) { + return false; + } + if (monitor != cur_mon && monitor != NULL) { + if (!switch_monitor) { + return false; + } else { + // switch monitor + monitor_focus_by_index(monitor_index_of(monitor)); + cur_mon = get_current_monitor(); + assert(cur_mon == monitor); + } + } + monitors_lock(); + monitor_set_tag(cur_mon, tag); + cur_mon = get_current_monitor(); + if (cur_mon->tag != tag) { + // could not set tag on monitor + monitors_unlock(); + return false; + } + // now the right tag is visible + // now focus it + bool found = frame_focus_client(tag->frame, client); + frame_focus_recursive(tag->frame); + monitor_apply_layout(cur_mon); + monitors_unlock(); + return found; +} + +int frame_focus_recursive(HSFrame* frame) { + // follow the selection to a leaf + while (frame->type == TYPE_FRAMES) { + frame = (frame->content.layout.selection == 0) ? + frame->content.layout.a : + frame->content.layout.b; + } + g_cur_frame = frame; + frame_unfocus(); + if (frame->content.clients.count) { + int selection = frame->content.clients.selection; + client_window_focus(frame->content.clients.buf[selection]); + } else { + client_window_unfocus_last(); + } + return 0; +} + +// do recursive for each element of the (binary) frame tree +// if order <= 0 -> action(node); action(left); action(right); +// if order == 1 -> action(left); action(node); action(right); +// if order >= 2 -> action(left); action(right); action(node); +void frame_do_recursive(HSFrame* frame, void (*action)(HSFrame*), int order) { + if (!frame) { + return; + } + if (frame->type == TYPE_FRAMES) { + // clients and subframes + HSLayout* layout = &(frame->content.layout); + if (order <= 0) action(frame); + frame_do_recursive(layout->a, action, order); + if (order == 1) action(frame); + frame_do_recursive(layout->b, action, order); + if (order >= 2) action(frame); + } else { + // action only + action(frame); + } +} + +void frame_do_recursive_data(HSFrame* frame, void (*action)(HSFrame*,void*), + int order, void* data) { + if (frame->type == TYPE_FRAMES) { + // clients and subframes + HSLayout* layout = &(frame->content.layout); + if (order <= 0) action(frame, data); + frame_do_recursive_data(layout->a, action, order, data); + if (order == 1) action(frame, data); + frame_do_recursive_data(layout->b, action, order, data); + if (order >= 2) action(frame, data); + } else { + // action only + action(frame, data); + } +} + +static void frame_hide(HSFrame* frame) { + frame_set_visible(frame, false); + if (frame->type == TYPE_CLIENTS) { + int i; + HSClient** buf = frame->content.clients.buf; + size_t count = frame->content.clients.count; + for (i = 0; i < count; i++) { + client_set_visible(buf[i], false); + } + } +} + +void frame_hide_recursive(HSFrame* frame) { + // first hide children => order = 2 + frame_do_recursive(frame, frame_hide, 2); +} + +static void frame_show_clients(HSFrame* frame) { + if (frame->type == TYPE_CLIENTS) { + int i; + HSClient** buf = frame->content.clients.buf; + size_t count = frame->content.clients.count; + for (i = 0; i < count; i++) { + client_set_visible(buf[i], true); + } + } +} + +void frame_show_recursive(HSFrame* frame) { + // first show parents, then children => order = 0 + frame_do_recursive(frame, frame_show_clients, 2); +} + +static void frame_rotate(HSFrame* frame) { + if (frame && frame->type == TYPE_FRAMES) { + HSLayout* l = &frame->content.layout; + switch (l->align) { + case ALIGN_VERTICAL: + l->align = ALIGN_HORIZONTAL; + break; + case ALIGN_HORIZONTAL: + l->align = ALIGN_VERTICAL; + l->selection = l->selection ? 0 : 1; + HSFrame* temp = l->a; + l->a = l->b; + l->b = temp; + l->fraction = FRACTION_UNIT - l->fraction; + break; + } + } +} + +int layout_rotate_command() { + frame_do_recursive(get_current_monitor()->tag->frame, frame_rotate, -1); + monitor_apply_layout(get_current_monitor()); + return 0; +} + +int frame_remove_command(int argc, char** argv) { + if (!g_cur_frame->parent) { + // do nothing if is toplevel frame + return 0; + } + assert(g_cur_frame->type == TYPE_CLIENTS); + HSFrame* parent = g_cur_frame->parent; + HSFrame* first = g_cur_frame; + HSFrame* second; + if (first == parent->content.layout.a) { + second = parent->content.layout.b; + } else { + assert(first == parent->content.layout.b); + second = parent->content.layout.a; + } + size_t count; + HSClient** wins; + // get all wins from first child + frame_destroy(first, &wins, &count); + // and insert them to other child.. inefficiently + int i; + for (i = 0; i < count; i++) { + frame_insert_client(second, wins[i]); + } + g_free(wins); + XDestroyWindow(g_display, parent->window); + // now do tree magic + // and make second child the new parent + // set parent + second->parent = parent->parent; + // TODO: call frame destructor here + stack_remove_slice(parent->tag->stack, parent->slice); + slice_destroy(parent->slice); + // copy all other elements + *parent = *second; + // fix childs' parent-pointer + if (parent->type == TYPE_FRAMES) { + parent->content.layout.a->parent = parent; + parent->content.layout.b->parent = parent; + } + g_free(second); + // re-layout + frame_focus_recursive(parent); + monitor_apply_layout(get_current_monitor()); + return 0; +} + +int close_or_remove_command(int argc, char** argv) { + HSClient* client = frame_focused_client(g_cur_frame); + if (client) { + window_close(client->window); + return 0; + } else { + return frame_remove_command(argc, argv); + } +} + +// ET: same as close or remove but remove after last client +int close_and_remove_command(int argc, char** argv) { + bool remove_after_close = false; + HSClient* client = frame_focused_client(g_cur_frame); + if (client) { + if (g_cur_frame->content.clients.count == 1 ) { + remove_after_close = true; + } + + window_close(client->window); + + if (remove_after_close) { + frame_remove_command(argc, argv); + } + + return 0; + + } else { + return frame_remove_command(argc, argv); + } +} + +void frame_set_visible(HSFrame* frame, bool visible) { + if (!frame) { + return; + } + if (frame->window_visible == visible) { + return; + } + window_set_visible(frame->window, visible); + frame->window_visible = visible; +} + +// executes action for each client within frame and its subframes +// if action fails (i.e. returns something != 0), then it aborts with this code +int frame_foreach_client(HSFrame* frame, ClientAction action, void* data) { + int status; + if (frame->type == TYPE_FRAMES) { + status = frame_foreach_client(frame->content.layout.a, action, data); + if (0 != status) { + return status; + } + status = frame_foreach_client(frame->content.layout.b, action, data); + if (0 != status) { + return status; + } + } else { + // frame->type == TYPE_CLIENTS + HSClient** buf = frame->content.clients.buf; + size_t count = frame->content.clients.count; + HSClient* client; + for (int i = 0; i < count; i++) { + client = buf[i]; + // do action for each client + status = action(client, data); + if (0 != status) { + return status; + } + } + } + return 0; +} + +void frame_update_border(Window window, unsigned long color) { + if (*g_frame_border_inner_width > 0 && *g_frame_border_inner_width < *g_frame_border_width) { + set_window_double_border(g_display, window, *g_frame_border_inner_width, g_frame_border_inner_color, color); + } else { + XSetWindowBorder(g_display, window, color); + } +} + +int frame_focus_edge(int argc, char** argv, GString* output) { + // Puts the focus to the edge in the specified direction + const char* args[] = { "" }; + monitors_lock_command(LENGTH(args), args); + int oldval = *g_focus_crosses_monitor_boundaries; + *g_focus_crosses_monitor_boundaries = 0; + while (0 == frame_focus_command(argc,argv,output)) + ; + *g_focus_crosses_monitor_boundaries = oldval; + monitors_unlock_command(LENGTH(args), args); + return 0; +} + +int frame_move_window_edge(int argc, char** argv, GString* output) { + // Moves a window to the edge in the specified direction + const char* args[] = { "" }; + monitors_lock_command(LENGTH(args), args); + int oldval = *g_focus_crosses_monitor_boundaries; + *g_focus_crosses_monitor_boundaries = 0; + while (0 == frame_move_window_command(argc,argv,output)) + ; + *g_focus_crosses_monitor_boundaries = oldval; + monitors_unlock_command(LENGTH(args), args); + return 0; +} + +bool smart_window_surroundings_active(HSFrame* frame) { + return *g_smart_window_surroundings + && (frame->content.clients.count == 1 + || frame->content.clients.layout == LAYOUT_MAX); +} + diff -Nru herbstluftwm-0.6.2/src/layout.h herbstluftwm-0.7.0/src/layout.h --- herbstluftwm-0.6.2/src/layout.h 2014-03-26 23:54:22.000000000 +0000 +++ herbstluftwm-0.7.0/src/layout.h 2015-10-14 13:27:40.000000000 +0000 @@ -11,7 +11,7 @@ #include #include #include -#include +#include "monitor.h" #include "tag.h" #include "floating.h" @@ -43,8 +43,8 @@ LAYOUT_COUNT, }; -extern char* g_align_names[]; -extern char* g_layout_names[]; +extern const char* g_align_names[]; +extern const char* g_layout_names[]; enum { TYPE_CLIENTS = 0, @@ -94,9 +94,10 @@ // globals -HSFrame* g_cur_frame; // currently selected frame -int* g_frame_gap; -int* g_window_gap; +extern HSFrame* g_cur_frame; // currently selected frame +extern int* g_frame_gap; +extern int* g_window_gap; +extern char* g_tree_style; // functions void layout_init(); @@ -104,7 +105,7 @@ // for frames HSFrame* frame_create_empty(HSFrame* parent, HSTag* parenttag); void frame_insert_client(HSFrame* frame, struct HSClient* client); -HSFrame* lookup_frame(HSFrame* root, char* path); +HSFrame* lookup_frame(HSFrame* root, const char* path); HSFrame* frame_current_selection(); HSFrame* frame_current_selection_below(HSFrame* frame); // finds the subframe of frame that contains the window @@ -179,6 +180,7 @@ /// removes the current frame int frame_remove_command(int argc, char** argv); int close_or_remove_command(int argc, char** argv); +int close_and_remove_command(int argc, char** argv); void frame_set_visible(HSFrame* frame, bool visible); void frame_update_border(Window window, unsigned long color); diff -Nru herbstluftwm-0.6.2/src/main.c herbstluftwm-0.7.0/src/main.c --- herbstluftwm-0.6.2/src/main.c 2014-03-26 23:54:22.000000000 +0000 +++ herbstluftwm-0.7.0/src/main.c 1970-01-01 00:00:00.000000000 +0000 @@ -1,1036 +0,0 @@ -/** Copyright 2011-2013 Thorsten Wißmann. All rights reserved. - * - * This software is licensed under the "Simplified BSD License". - * See LICENSE for details */ - -// herbstluftwm -#include "clientlist.h" -#include "utils.h" -#include "key.h" -#include "layout.h" -#include "globals.h" -#include "ipc-server.h" -#include "ipc-protocol.h" -#include "command.h" -#include "settings.h" -#include "hook.h" -#include "mouse.h" -#include "rules.h" -#include "ewmh.h" -#include "stack.h" -#include "object.h" -#include "decoration.h" -// standard -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -// gui -#include -#include -#include -#include -#include - -static Bool g_otherwm; -int g_verbose = 0; -static int (*g_xerrorxlib)(Display *, XErrorEvent *); -static char* g_autostart_path = NULL; // if not set, then find it in $HOME or $XDG_CONFIG_HOME -static int* g_focus_follows_mouse = NULL; -static bool g_exec_before_quit = false; -static char** g_exec_args = NULL; -static int* g_raise_on_click = NULL; - -typedef void (*HandlerTable[LASTEvent]) (XEvent*); - -int quit(); -int reload(); -int version(int argc, char* argv[], GString* output); -int echo(int argc, char* argv[], GString* output); -int true_command(); -int false_command(); -int try_command(int argc, char* argv[], GString* output); -int silent_command(int argc, char* argv[]); -int print_layout_command(int argc, char** argv, GString* output); -int load_command(int argc, char** argv, GString* output); -int print_tag_status_command(int argc, char** argv, GString* output); -void execute_autostart_file(); -int raise_command(int argc, char** argv, GString* output); -int spawn(int argc, char** argv); -int wmexec(int argc, char** argv); -static void remove_zombies(int signal); -int custom_hook_emit(int argc, char** argv); -int jumpto_command(int argc, char** argv, GString* output); -int getenv_command(int argc, char** argv, GString* output); -int setenv_command(int argc, char** argv, GString* output); -int unsetenv_command(int argc, char** argv, GString* output); - -// handler for X-Events -void buttonpress(XEvent* event); -void buttonrelease(XEvent* event); -void createnotify(XEvent* event); -void configurerequest(XEvent* event); -void configurenotify(XEvent* event); -void destroynotify(XEvent* event); -void enternotify(XEvent* event); -void expose(XEvent* event); -void focusin(XEvent* event); -void keypress(XEvent* event); -void mappingnotify(XEvent* event); -void motionnotify(XEvent* event); -void mapnotify(XEvent* event); -void maprequest(XEvent* event); -void propertynotify(XEvent* event); -void unmapnotify(XEvent* event); - -CommandBinding g_commands[] = { - CMD_BIND_NO_OUTPUT( "quit", quit), - CMD_BIND( "echo", echo), - CMD_BIND_NO_OUTPUT( "true", true_command), - CMD_BIND_NO_OUTPUT( "false", false_command), - CMD_BIND( "try", try_command), - CMD_BIND_NO_OUTPUT( "silent", silent_command), - CMD_BIND_NO_OUTPUT( "reload", reload), - CMD_BIND( "version", version), - CMD_BIND( "list_commands", list_commands), - CMD_BIND( "list_monitors", list_monitors), - CMD_BIND( "set_monitors", set_monitor_rects_command), - CMD_BIND( "disjoin_rects", disjoin_rects_command), - CMD_BIND( "list_keybinds", key_list_binds), - CMD_BIND( "list_padding", list_padding), - CMD_BIND( "keybind", keybind), - CMD_BIND( "keyunbind", keyunbind), - CMD_BIND( "mousebind", mouse_bind_command), - CMD_BIND_NO_OUTPUT( "mouseunbind", mouse_unbind_all), - CMD_BIND_NO_OUTPUT( "spawn", spawn), - CMD_BIND_NO_OUTPUT( "wmexec", wmexec), - CMD_BIND_NO_OUTPUT( "emit_hook", custom_hook_emit), - CMD_BIND( "bring", frame_current_bring), - CMD_BIND_NO_OUTPUT( "focus_nth", frame_current_set_selection), - CMD_BIND_NO_OUTPUT( "cycle", frame_current_cycle_selection), - CMD_BIND_NO_OUTPUT( "cycle_all", cycle_all_command), - CMD_BIND( "cycle_layout", frame_current_cycle_client_layout), - CMD_BIND_NO_OUTPUT( "cycle_frame", cycle_frame_command), - CMD_BIND( "close", close_command), - CMD_BIND_NO_OUTPUT( "close_or_remove",close_or_remove_command), - CMD_BIND( "split", frame_split_command), - CMD_BIND( "resize", frame_change_fraction_command), - CMD_BIND( "focus_edge", frame_focus_edge), - CMD_BIND( "focus", frame_focus_command), - CMD_BIND( "shift_edge", frame_move_window_edge), - CMD_BIND( "shift", frame_move_window_command), - CMD_BIND( "shift_to_monitor",shift_to_monitor), - CMD_BIND_NO_OUTPUT( "remove", frame_remove_command), - CMD_BIND( "set", settings_set_command), - CMD_BIND( "toggle", settings_toggle), - CMD_BIND( "cycle_value", settings_cycle_value), - CMD_BIND_NO_OUTPUT( "cycle_monitor", monitor_cycle_command), - CMD_BIND( "focus_monitor", monitor_focus_command), - CMD_BIND( "get", settings_get), - CMD_BIND( "add", tag_add_command), - CMD_BIND( "use", monitor_set_tag_command), - CMD_BIND( "use_index", monitor_set_tag_by_index_command), - CMD_BIND( "use_previous", monitor_set_previous_tag_command), - CMD_BIND( "jumpto", jumpto_command), - CMD_BIND( "floating", tag_set_floating_command), - CMD_BIND_NO_OUTPUT( "fullscreen", client_set_property_command), - CMD_BIND_NO_OUTPUT( "pseudotile", client_set_property_command), - CMD_BIND( "tag_status", print_tag_status_command), - CMD_BIND( "merge_tag", tag_remove_command), - CMD_BIND( "rename", tag_rename_command), - CMD_BIND( "move", tag_move_window_command), - CMD_BIND_NO_OUTPUT( "rotate", layout_rotate_command), - CMD_BIND( "move_index", tag_move_window_by_index_command), - CMD_BIND( "add_monitor", add_monitor_command), - CMD_BIND( "raise_monitor", monitor_raise_command), - CMD_BIND( "remove_monitor", remove_monitor_command), - CMD_BIND( "move_monitor", move_monitor_command), - CMD_BIND( "rename_monitor", rename_monitor_command), - CMD_BIND( "monitor_rect", monitor_rect_command), - CMD_BIND( "pad", monitor_set_pad_command), - CMD_BIND( "raise", raise_command), - CMD_BIND( "rule", rule_add_command), - CMD_BIND( "unrule", rule_remove_command), - CMD_BIND( "list_rules", rule_print_all_command), - CMD_BIND( "layout", print_layout_command), - CMD_BIND( "stack", print_stack_command), - CMD_BIND( "dump", print_layout_command), - CMD_BIND( "load", load_command), - CMD_BIND( "complete", complete_command), - CMD_BIND( "complete_shell", complete_command), - CMD_BIND_NO_OUTPUT( "lock", monitors_lock_command), - CMD_BIND_NO_OUTPUT( "unlock", monitors_unlock_command), - CMD_BIND( "lock_tag", monitor_lock_tag_command), - CMD_BIND( "unlock_tag", monitor_unlock_tag_command), - CMD_BIND( "set_layout", frame_current_set_client_layout), - CMD_BIND( "detect_monitors",detect_monitors_command), - CMD_BIND( "chain", command_chain_command), - CMD_BIND( "and", command_chain_command), - CMD_BIND( "or", command_chain_command), - CMD_BIND( "!", negate_command), - CMD_BIND( "attr", attr_command), - CMD_BIND( "compare", compare_command), - CMD_BIND( "object_tree", print_object_tree_command), - CMD_BIND( "get_attr", hsattribute_get_command), - CMD_BIND( "set_attr", hsattribute_set_command), - CMD_BIND( "new_attr", userattribute_command), - CMD_BIND( "mktemp", tmpattribute_command), - CMD_BIND( "remove_attr", userattribute_remove_command), - CMD_BIND( "substitute", substitute_command), - CMD_BIND( "sprintf", sprintf_command), - CMD_BIND( "getenv", getenv_command), - CMD_BIND( "setenv", setenv_command), - CMD_BIND( "unsetenv", unsetenv_command), - {{ NULL }} -}; - -// core functions -int quit() { - g_aboutToQuit = true; - return 0; -} - -// reload config -int reload() { - execute_autostart_file(); - return 0; -} - -int version(int argc, char* argv[], GString* output) { - (void) argc; - (void) argv; - g_string_append(output, HERBSTLUFT_VERSION_STRING); - return 0; -} - -int echo(int argc, char* argv[], GString* output) { - if (SHIFT(argc, argv)) { - // if there still is an argument - g_string_append(output, argv[0]); - while (SHIFT(argc, argv)) { - g_string_append_c(output, ' '); - g_string_append(output, argv[0]); - } - } - g_string_append_c(output, '\n'); - return 0; -} - -int true_command() { - return 0; -} - -int false_command() { - return 1; -} - -int try_command(int argc, char* argv[], GString* output) { - if (argc <= 1) { - return HERBST_NEED_MORE_ARGS; - } - (void)SHIFT(argc, argv); - call_command(argc, argv, output); - return 0; -} - -int silent_command(int argc, char* argv[]) { - if (argc <= 1) { - return HERBST_NEED_MORE_ARGS; - } - (void)SHIFT(argc, argv); - return call_command_no_output(argc, argv); -} - -// prints or dumps the layout of an given tag -// first argument tells whether to print or to dump -int print_layout_command(int argc, char** argv, GString* output) { - HSTag* tag = NULL; - // an empty argv[1] means current focused tag - if (argc >= 2 && argv[1][0] != '\0') { - tag = find_tag(argv[1]); - if (!tag) { - g_string_append_printf(output, - "%s: Tag \"%s\" not found\n", argv[0], argv[1]); - return HERBST_INVALID_ARGUMENT; - } - } else { // use current tag - HSMonitor* m = get_current_monitor(); - tag = m->tag; - } - assert(tag != NULL); - - HSFrame* frame = lookup_frame(tag->frame, argc >= 3 ? argv[2] : ""); - if (argc > 0 && !strcmp(argv[0], "dump")) { - dump_frame_tree(frame, output); - } else { - print_frame_tree(frame, output); - } - return 0; -} - -int load_command(int argc, char** argv, GString* output) { - // usage: load TAG LAYOUT - HSTag* tag = NULL; - if (argc < 2) { - return HERBST_NEED_MORE_ARGS; - } - char* layout_string = argv[1]; - if (argc >= 3) { - tag = find_tag(argv[1]); - layout_string = argv[2]; - if (!tag) { - g_string_append_printf(output, - "%s: Tag \"%s\" not found\n", argv[0], argv[1]); - return HERBST_INVALID_ARGUMENT; - } - } else { // use current tag - HSMonitor* m = get_current_monitor(); - tag = m->tag; - } - assert(tag != NULL); - char* rest = load_frame_tree(tag->frame, layout_string, output); - if (output->len > 0) { - g_string_prepend(output, "load: "); - } - tag_set_flags_dirty(); // we probably changed some window positions - // arrange monitor - HSMonitor* m = find_monitor_with_tag(tag); - if (m) { - frame_show_recursive(tag->frame); - if (get_current_monitor() == m) { - frame_focus_recursive(tag->frame); - } - monitor_apply_layout(m); - } else { - frame_hide_recursive(tag->frame); - } - if (!rest) { - g_string_append_printf(output, - "%s: Error while parsing!\n", argv[0]); - return HERBST_INVALID_ARGUMENT; - } - if (rest[0] != '\0') { // if string was not parsed completely - g_string_append_printf(output, - "%s: Layout description was too long\n", argv[0]); - g_string_append_printf(output, - "%s: \"%s\" has not been parsed\n", argv[0], rest); - return HERBST_INVALID_ARGUMENT; - } - return 0; -} - -int print_tag_status_command(int argc, char** argv, GString* output) { - HSMonitor* monitor; - if (argc >= 2) { - monitor = string_to_monitor(argv[1]); - } else { - monitor = get_current_monitor(); - } - if (monitor == NULL) { - g_string_append_printf(output, - "%s: Monitor \"%s\" not found!\n", argv[0], argv[1]); - return HERBST_INVALID_ARGUMENT; - } - tag_update_flags(); - g_string_append_c(output, '\t'); - for (int i = 0; i < g_tags->len; i++) { - HSTag* tag = g_array_index(g_tags, HSTag*, i); - // print flags - char c = '.'; - if (tag->flags & TAG_FLAG_USED) { - c = ':'; - } - HSMonitor *tag_monitor = find_monitor_with_tag(tag); - if (tag_monitor == monitor) { - c = '+'; - if (monitor == get_current_monitor()) { - c = '#'; - } - } else if (tag_monitor) { - c = '-'; - if (get_current_monitor() == tag_monitor) { - c = '%'; - } - } - if (tag->flags & TAG_FLAG_URGENT) { - c = '!'; - } - g_string_append_c(output, c); - g_string_append(output, tag->name->str); - g_string_append_c(output, '\t'); - } - return 0; -} - -int custom_hook_emit(int argc, char** argv) { - hook_emit(argc - 1, argv + 1); - return 0; -} - -// spawn() heavily inspired by dwm.c -int spawn(int argc, char** argv) { - if (argc < 2) { - return HERBST_NEED_MORE_ARGS; - } - if (fork() == 0) { - // only look in child - if (g_display) { - close(ConnectionNumber(g_display)); - } - // shift all args in argv by 1 to the front - // so that we have space for a NULL entry at the end for execvp - char** execargs = argv_duplicate(argc, argv); - free(execargs[0]); - int i; - for (i = 0; i < argc-1; i++) { - execargs[i] = execargs[i+1]; - } - execargs[i] = NULL; - // do actual exec - setsid(); - execvp(execargs[0], execargs); - fprintf(stderr, "herbstluftwm: execvp \"%s\"", argv[1]); - perror(" failed"); - exit(0); - } - return 0; -} - -int wmexec(int argc, char** argv) { - if (argc >= 2) { - // shift all args in argv by 1 to the front - // so that we have space for a NULL entry at the end for execvp - char** execargs = argv_duplicate(argc, argv); - free(execargs[0]); - int i; - for (i = 0; i < argc-1; i++) { - execargs[i] = execargs[i+1]; - } - execargs[i] = NULL; - // quit and exec to new window manger - g_exec_args = execargs; - } else { - // exec into same command - g_exec_args = NULL; - } - g_exec_before_quit = true; - g_aboutToQuit = true; - return EXIT_SUCCESS; -} - -int raise_command(int argc, char** argv, GString* output) { - Window win; - HSClient* client = NULL; - win = string_to_client((argc > 1) ? argv[1] : "", &client); - if (client) { - client_raise(client); - } else { - XRaiseWindow(g_display, win); - } - return 0; -} - -int jumpto_command(int argc, char** argv, GString* output) { - if (argc < 2) { - return HERBST_NEED_MORE_ARGS; - } - HSClient* client = NULL; - string_to_client(argv[1], &client); - if (client) { - focus_client(client, true, true); - return 0; - } else { - g_string_append_printf(output, - "%s: Could not find client", argv[0]); - if (argc > 1) { - g_string_append_printf(output, " \"%s\".\n", argv[1]); - } else { - g_string_append(output, ".\n"); - } - return HERBST_INVALID_ARGUMENT; - } -} - -int getenv_command(int argc, char** argv, GString* output) { - if (argc < 2) { - return HERBST_NEED_MORE_ARGS; - } - char* envvar = getenv(argv[1]); - if (envvar == NULL) { - return HERBST_ENV_UNSET; - } - g_string_append_printf(output, "%s\n", envvar); - return 0; -} - -int setenv_command(int argc, char** argv, GString* output) { - if (argc < 3) { - return HERBST_NEED_MORE_ARGS; - } - if (setenv(argv[1], argv[2], 1) != 0) { - g_string_append_printf(output, - "%s: Could not set environment variable: %s\n", argv[0], strerror(errno)); - return HERBST_UNKNOWN_ERROR; - } - return 0; -} - -int unsetenv_command(int argc, char** argv, GString* output) { - if (argc < 2) { - return HERBST_NEED_MORE_ARGS; - } - if (unsetenv(argv[1]) != 0) { - g_string_append_printf(output, - "%s: Could not unset environment variable: %s\n", argv[0], strerror(errno)); - return HERBST_UNKNOWN_ERROR; - } - return 0; -} - -// handle x-events: - -void event_on_configure(XEvent event) { - XConfigureRequestEvent* cre = &event.xconfigurerequest; - HSClient* client = get_client_from_window(cre->window); - if (client) { - bool changes = false; - Rectangle newRect = client->float_size; - if (client->sizehints_floating && - (is_client_floated(client) || client->pseudotile)) - { - bool width_requested = 0 != (cre->value_mask & CWWidth); - bool height_requested = 0 != (cre->value_mask & CWHeight); - bool x_requested = 0 != (cre->value_mask & CWX); - bool y_requested = 0 != (cre->value_mask & CWY); - cre->width += 2*cre->border_width; - cre->height += 2*cre->border_width; - if (width_requested && newRect.width != cre->width) changes = true; - if (height_requested && newRect.height != cre->height) changes = true; - if (x_requested || y_requested) changes = true; - if (x_requested) newRect.x = cre->x; - if (y_requested) newRect.y = cre->y; - if (width_requested) newRect.width = cre->width; - if (height_requested) newRect.height = cre->height; - } - if (changes && is_client_floated(client)) { - client->float_size = newRect; - client_resize_floating(client, find_monitor_with_tag(client->tag)); - } else if (changes && client->pseudotile) { - client->float_size = newRect; - monitor_apply_layout(find_monitor_with_tag(client->tag)); - } else { - // FIXME: why send event and not XConfigureWindow or XMoveResizeWindow?? - client_send_configure(client); - } - } else { - // if client not known.. then allow configure. - // its probably a nice conky or dzen2 bar :) - XWindowChanges wc; - wc.x = cre->x; - wc.y = cre->y; - wc.width = cre->width; - wc.height = cre->height; - wc.border_width = cre->border_width; - wc.sibling = cre->above; - wc.stack_mode = cre->detail; - XConfigureWindow(g_display, cre->window, cre->value_mask, &wc); - } -} - -// from dwm.c -/* There's no way to check accesses to destroyed windows, thus those cases are - * ignored (especially on UnmapNotify's). Other types of errors call Xlibs - * default error handler, which may call exit. */ -int xerror(Display *dpy, XErrorEvent *ee) { - if(ee->error_code == BadWindow - || ee->error_code == BadGC - || ee->error_code == BadPixmap - || (ee->request_code == X_SetInputFocus && ee->error_code == BadMatch) - || (ee->request_code == X_PolyText8 && ee->error_code == BadDrawable) - || (ee->request_code == X_PolyFillRectangle && ee->error_code == BadDrawable) - || (ee->request_code == X_PolySegment && ee->error_code == BadDrawable) - || (ee->request_code == X_ConfigureWindow && ee->error_code == BadMatch) - || (ee->request_code == X_GrabButton && ee->error_code == BadAccess) - || (ee->request_code == X_GrabKey && ee->error_code == BadAccess) - || (ee->request_code == X_CopyArea && ee->error_code == BadDrawable)) { - return 0; - } - fprintf(stderr, "herbstluftwm: fatal error: request code=%d, error code=%d\n", - ee->request_code, ee->error_code); - if (ee->error_code == BadDrawable) { - HSDebug("Warning: ignoring X_BadDrawable"); - return 0; - } - return g_xerrorxlib(dpy, ee); /* may call exit */ -} - -int xerrordummy(Display *dpy, XErrorEvent *ee) { - return 0; -} - -// from dwm.c -/* Startup Error handler to check if another window manager - * is already running. */ -int xerrorstart(Display *dpy, XErrorEvent *ee) { - g_otherwm = True; - return -1; -} - -// from dwm.c -void checkotherwm(void) { - g_otherwm = False; - g_xerrorxlib = XSetErrorHandler(xerrorstart); - /* this causes an error if some other window manager is running */ - XSelectInput(g_display, DefaultRootWindow(g_display), SubstructureRedirectMask); - XSync(g_display, False); - if(g_otherwm) - die("herbstluftwm: another window manager is already running\n"); - XSetErrorHandler(xerror); - XSync(g_display, False); -} - -// scan for windows and add them to the list of managed clients -// from dwm.c -void scan(void) { - unsigned int num; - Window d1, d2, *cl, *wins = NULL; - unsigned long cl_count; - XWindowAttributes wa; - - ewmh_get_original_client_list(&cl, &cl_count); - if (XQueryTree(g_display, g_root, &d1, &d2, &wins, &num)) { - for (int i = 0; i < num; i++) { - if(!XGetWindowAttributes(g_display, wins[i], &wa) - || wa.override_redirect || XGetTransientForHint(g_display, wins[i], &d1)) - continue; - // only manage mapped windows.. no strange wins like: - // luakit/dbus/(ncurses-)vim - // but manage it if it was in the ewmh property _NET_CLIENT_LIST by - // the previous window manager - // TODO: what would dwm do? - if (is_window_mapped(g_display, wins[i]) - || 0 <= array_find(cl, cl_count, sizeof(Window), wins+i)) { - manage_client(wins[i]); - XMapWindow(g_display, wins[i]); - } - } - if(wins) - XFree(wins); - } - // ensure every original client is managed again - for (int i = 0; i < cl_count; i++) { - if (get_client_from_window(cl[i])) continue; - if (!XGetWindowAttributes(g_display, cl[i], &wa) - || wa.override_redirect - || XGetTransientForHint(g_display, cl[i], &d1)) - { - continue; - } - XReparentWindow(g_display, cl[i], g_root, 0,0); - manage_client(cl[i]); - } -} - -void execute_autostart_file() { - GString* path = NULL; - if (g_autostart_path) { - path = g_string_new(g_autostart_path); - } else { - // find right directory - char* xdg_config_home = getenv("XDG_CONFIG_HOME"); - if (xdg_config_home) { - path = g_string_new(xdg_config_home); - } else { - char* home = getenv("HOME"); - if (!home) { - g_warning("Will not run autostart file. " - "Neither $HOME or $XDG_CONFIG_HOME is set.\n"); - return; - } - path = g_string_new(home); - g_string_append_c(path, G_DIR_SEPARATOR); - g_string_append(path, ".config"); - } - g_string_append_c(path, G_DIR_SEPARATOR); - g_string_append(path, HERBSTLUFT_AUTOSTART); - } - if (0 == fork()) { - if (g_display) { - close(ConnectionNumber(g_display)); - } - setsid(); - execl(path->str, path->str, NULL); - - char* global_autostart = HERBSTLUFT_GLOBAL_AUTOSTART; - HSDebug("Can not execute %s, falling back to %s\n", path->str, global_autostart); - execl(global_autostart, global_autostart, NULL); - - fprintf(stderr, "herbstluftwm: execvp \"%s\"", global_autostart); - perror(" failed"); - exit(EXIT_FAILURE); - } - g_string_free(path, true); -} - -static void parse_arguments(int argc, char** argv) { - static struct option long_options[] = { - {"autostart", 1, 0, 'c'}, - {"version", 0, 0, 'v'}, - {"locked", 0, 0, 'l'}, - {"verbose", 0, &g_verbose, 1}, - {0, 0, 0, 0} - }; - // parse options - while (1) { - int option_index = 0; - int c = getopt_long(argc, argv, "+c:vl", long_options, &option_index); - if (c == -1) break; - switch (c) { - case 0: - /* ignore recognized long option */ - break; - case 'v': - printf("%s %s\n", argv[0], HERBSTLUFT_VERSION); - printf("Copyright (c) 2011-2013 Thorsten Wißmann\n"); - printf("Released under the Simplified BSD License\n"); - exit(0); - break; - case 'c': - g_autostart_path = optarg; - break; - case 'l': - g_initial_monitors_locked = 1; - break; - default: - exit(EXIT_FAILURE); - } - } -} - -static void remove_zombies(int signal) { - int bgstatus; - while (waitpid(-1, &bgstatus, WNOHANG) > 0); -} - -static void handle_signal(int signal) { - HSDebug("Interrupted by signal %d\n", signal); - g_aboutToQuit = true; - return; -} - -static void sigaction_signal(int signum, void (*handler)(int)) { - struct sigaction act; - act.sa_handler = handler; - sigemptyset(&act.sa_mask); - act.sa_flags = SA_NOCLDSTOP | SA_RESTART; - sigaction(signum, &act, NULL); -} - -static void fetch_settings() { - // fetch settings only for this main.c file from settings table - g_focus_follows_mouse = &(settings_find("focus_follows_mouse")->value.i); - g_raise_on_click = &(settings_find("raise_on_click")->value.i); -} - -static HandlerTable g_default_handler = { - [ ButtonPress ] = buttonpress, - [ ButtonRelease ] = buttonrelease, - [ ClientMessage ] = ewmh_handle_client_message, - [ CreateNotify ] = createnotify, - [ ConfigureRequest ] = configurerequest, - [ ConfigureNotify ] = configurenotify, - [ DestroyNotify ] = destroynotify, - [ EnterNotify ] = enternotify, - [ Expose ] = expose, - [ FocusIn ] = focusin, - [ KeyPress ] = keypress, - [ MappingNotify ] = mappingnotify, - [ MotionNotify ] = motionnotify, - [ MapNotify ] = mapnotify, - [ MapRequest ] = maprequest, - [ PropertyNotify ] = propertynotify, - [ UnmapNotify ] = unmapnotify, -}; - -static struct { - void (*init)(); - void (*destroy)(); -} g_modules[] = { - { ipc_init, ipc_destroy }, - { object_tree_init, object_tree_destroy }, - { key_init, key_destroy }, - { settings_init, settings_destroy }, - { floating_init, floating_destroy }, - { stacklist_init, stacklist_destroy }, - { layout_init, layout_destroy }, - { tag_init, tag_destroy }, - { clientlist_init, clientlist_destroy }, - { decorations_init, decorations_destroy }, - { monitor_init, monitor_destroy }, - { ewmh_init, ewmh_destroy }, - { mouse_init, mouse_destroy }, - { hook_init, hook_destroy }, - { rules_init, rules_destroy }, -}; - -/* ----------------------------- */ -/* event handler implementations */ -/* ----------------------------- */ - -void buttonpress(XEvent* event) { - XButtonEvent* be = &(event->xbutton); - HSDebug("name is: ButtonPress on sub %lx, win %lx\n", be->subwindow, be->window); - if (mouse_binding_find(be->state, be->button)) { - mouse_handle_event(event); - } else { - HSClient* client = get_client_from_window(be->window); - if (client) { - focus_client(client, false, true); - if (*g_raise_on_click) { - client_raise(client); - } - } - } - XAllowEvents(g_display, ReplayPointer, be->time); -} - -void buttonrelease(XEvent* event) { - HSDebug("name is: ButtonRelease\n"); - mouse_stop_drag(); -} - -void createnotify(XEvent* event) { - // printf("name is: CreateNotify\n"); - if (is_ipc_connectable(event->xcreatewindow.window)) { - ipc_add_connection(event->xcreatewindow.window); - } -} - -void configurerequest(XEvent* event) { - HSDebug("name is: ConfigureRequest\n"); - event_on_configure(*event); -} - -void configurenotify(XEvent* event) { - if (event->xconfigure.window == g_root && - settings_find("auto_detect_monitors")->value.i) { - char* args[] = { "detect_monitors" }; - detect_monitors_command(LENGTH(args), args, NULL); - } - // HSDebug("name is: ConfigureNotify\n"); -} - -void destroynotify(XEvent* event) { - // try to unmanage it - //HSDebug("name is: DestroyNotify for %lx\n", event->xdestroywindow.window); - unmanage_client(event->xdestroywindow.window); -} - -void enternotify(XEvent* event) { - XCrossingEvent *ce = &event->xcrossing; - //HSDebug("name is: EnterNotify, focus = %d\n", event->xcrossing.focus); - if (!mouse_is_dragging() - && *g_focus_follows_mouse - && ce->focus == false) { - HSClient* c = get_client_from_window(ce->window); - HSFrame* target; - if (c && c->tag->floating == false - && (target = find_frame_with_client(c->tag->frame, c)) - && target->content.clients.layout == LAYOUT_MAX - && frame_focused_client(target) != c) { - // don't allow focus_follows_mouse if another window would be - // hidden during that focus change (which only occurs in max layout) - } else if (c) { - focus_client(c, false, true); - } - } -} - -void expose(XEvent* event) { - //if (event->xexpose.count > 0) return; - //Window ewin = event->xexpose.window; - //HSDebug("name is: Expose for window %lx\n", ewin); -} - -void focusin(XEvent* event) { - //HSDebug("name is: FocusIn\n"); -} - -void keypress(XEvent* event) { - //HSDebug("name is: KeyPress\n"); - handle_key_press(event); -} - -void mappingnotify(XEvent* event) { - { - // regrab when keyboard map changes - XMappingEvent *ev = &event->xmapping; - XRefreshKeyboardMapping(ev); - if(ev->request == MappingKeyboard) { - regrab_keys(); - //TODO: mouse_regrab_all(); - } - } -} - -void motionnotify(XEvent* event) { - handle_motion_event(event); -} - -void mapnotify(XEvent* event) { - //HSDebug("name is: MapNotify\n"); - HSClient* c; - if ((c = get_client_from_window(event->xmap.window))) { - // reset focus. so a new window gets the focus if it shall have the - // input focus - frame_focus_recursive(g_cur_frame); - // also update the window title - just to be sure - client_update_title(c); - } -} - -void maprequest(XEvent* event) { - HSDebug("name is: MapRequest\n"); - XMapRequestEvent* mapreq = &event->xmaprequest; - if (is_herbstluft_window(g_display, mapreq->window)) { - // just map the window if it wants that - XWindowAttributes wa; - if (!XGetWindowAttributes(g_display, mapreq->window, &wa)) { - return; - } - XMapWindow(g_display, mapreq->window); - } else if (!get_client_from_window(mapreq->window)) { - // client should be managed (is not ignored) - // but is not managed yet - HSClient* client = manage_client(mapreq->window); - if (client && find_monitor_with_tag(client->tag)) { - XMapWindow(g_display, mapreq->window); - } - } - // else: ignore all other maprequests from windows - // that are managed already -} - -void propertynotify(XEvent* event) { - // printf("name is: PropertyNotify\n"); - XPropertyEvent *ev = &event->xproperty; - HSClient* client; - if (ev->state == PropertyNewValue) { - if (is_ipc_connectable(event->xproperty.window)) { - ipc_handle_connection(event->xproperty.window); - } else if((client = get_client_from_window(ev->window))) { - switch (ev->atom) { - case XA_WM_HINTS: - client_update_wm_hints(client); - break; - case XA_WM_NORMAL_HINTS: - updatesizehints(client); - HSMonitor* m = find_monitor_with_tag(client->tag); - if (m) monitor_apply_layout(m); - break; - case XA_WM_NAME: - client_update_title(client); - break; - default: - break; - } - } - } -} - -void unmapnotify(XEvent* event) { - HSDebug("name is: UnmapNotify for %lx\n", event->xunmap.window); - if (!clientlist_ignore_unmapnotify(event->xunmap.window)) { - unmanage_client(event->xunmap.window); - } -} - -/* ---- */ -/* main */ -/* ---- */ - -int main(int argc, char* argv[]) { - parse_arguments(argc, argv); - if(!(g_display = XOpenDisplay(NULL))) - die("herbstluftwm: cannot open display\n"); - checkotherwm(); - // remove zombies on SIGCHLD - sigaction_signal(SIGCHLD, remove_zombies); - sigaction_signal(SIGINT, handle_signal); - sigaction_signal(SIGQUIT, handle_signal); - sigaction_signal(SIGTERM, handle_signal); - // set some globals - g_screen = DefaultScreen(g_display); - g_screen_width = DisplayWidth(g_display, g_screen); - g_screen_height = DisplayHeight(g_display, g_screen); - g_root = RootWindow(g_display, g_screen); - XSelectInput(g_display, g_root, ROOT_EVENT_MASK); - - // initialize subsystems - for (int i = 0; i < LENGTH(g_modules); i++) { - g_modules[i].init(); - } - fetch_settings(); - - // setup - ensure_monitors_are_available(); - scan(); - tag_force_update_flags(); - all_monitors_apply_layout(); - ewmh_update_all(); - execute_autostart_file(); - clientlist_end_startup(); - - // main loop - XEvent event; - int x11_fd; - fd_set in_fds; - x11_fd = ConnectionNumber(g_display); - while (!g_aboutToQuit) { - FD_ZERO(&in_fds); - FD_SET(x11_fd, &in_fds); - // wait for an event or a signal - select(x11_fd + 1, &in_fds, 0, 0, NULL); - if (g_aboutToQuit) { - break; - } - while (XPending(g_display)) { - XNextEvent(g_display, &event); - void (*handler) (XEvent*) = g_default_handler[event.type]; - if (handler != NULL) { - handler(&event); - } - } - } - - // destroy all subsystems - for (int i = LENGTH(g_modules); i --> 0;) { - g_modules[i].destroy(); - } - XCloseDisplay(g_display); - // check if we shall restart an other window manager - if (g_exec_before_quit) { - if (g_exec_args) { - // do actual exec - HSDebug("==> Doing wmexec to %s\n", g_exec_args[0]); - execvp(g_exec_args[0], g_exec_args); - fprintf(stderr, "herbstluftwm: execvp \"%s\"", g_exec_args[0]); - perror(" failed"); - } - // on failure or if no other wm given, then fall back - HSDebug("==> Doing wmexec to %s\n", argv[0]); - execvp(argv[0], argv); - fprintf(stderr, "herbstluftwm: execvp \"%s\"", argv[1]); - perror(" failed"); - return EXIT_FAILURE; - } - return EXIT_SUCCESS; -} - diff -Nru herbstluftwm-0.6.2/src/main.cpp herbstluftwm-0.7.0/src/main.cpp --- herbstluftwm-0.6.2/src/main.cpp 1970-01-01 00:00:00.000000000 +0000 +++ herbstluftwm-0.7.0/src/main.cpp 2015-10-14 13:27:40.000000000 +0000 @@ -0,0 +1,1051 @@ +/** Copyright 2011-2013 Thorsten Wißmann. All rights reserved. + * + * This software is licensed under the "Simplified BSD License". + * See LICENSE for details */ + +// herbstluftwm +#include "clientlist.h" +#include "utils.h" +#include "key.h" +#include "layout.h" +#include "globals.h" +#include "ipc-server.h" +#include "ipc-protocol.h" +#include "command.h" +#include "settings.h" +#include "hook.h" +#include "mouse.h" +#include "rules.h" +#include "ewmh.h" +#include "stack.h" +#include "object.h" +#include "decoration.h" +#include "desktopwindow.h" +// standard +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +// gui +#include +#include +#include +#include +#include + +// globals: +int g_verbose = 0; +Display* g_display; +int g_screen; +Window g_root; +int g_screen_width; +int g_screen_height; +bool g_aboutToQuit; + +// module internals: +static Bool g_otherwm; +static int (*g_xerrorxlib)(Display *, XErrorEvent *); +static char* g_autostart_path = NULL; // if not set, then find it in $HOME or $XDG_CONFIG_HOME +static int* g_focus_follows_mouse = NULL; +static bool g_exec_before_quit = false; +static char** g_exec_args = NULL; +static int* g_raise_on_click = NULL; + +typedef void (*HandlerTable[LASTEvent]) (XEvent*); + +int quit(); +int reload(); +int version(int argc, char* argv[], GString* output); +int echo(int argc, char* argv[], GString* output); +int true_command(); +int false_command(); +int try_command(int argc, char* argv[], GString* output); +int silent_command(int argc, char* argv[]); +int print_layout_command(int argc, char** argv, GString* output); +int load_command(int argc, char** argv, GString* output); +int print_tag_status_command(int argc, char** argv, GString* output); +void execute_autostart_file(); +int raise_command(int argc, char** argv, GString* output); +int spawn(int argc, char** argv); +int wmexec(int argc, char** argv); +static void remove_zombies(int signal); +int custom_hook_emit(int argc, const char** argv); +int jumpto_command(int argc, char** argv, GString* output); +int getenv_command(int argc, char** argv, GString* output); +int setenv_command(int argc, char** argv, GString* output); +int unsetenv_command(int argc, char** argv, GString* output); + +// handler for X-Events +void buttonpress(XEvent* event); +void buttonrelease(XEvent* event); +void createnotify(XEvent* event); +void configurerequest(XEvent* event); +void configurenotify(XEvent* event); +void destroynotify(XEvent* event); +void enternotify(XEvent* event); +void expose(XEvent* event); +void focusin(XEvent* event); +void keypress(XEvent* event); +void mappingnotify(XEvent* event); +void motionnotify(XEvent* event); +void mapnotify(XEvent* event); +void maprequest(XEvent* event); +void propertynotify(XEvent* event); +void unmapnotify(XEvent* event); + +CommandBinding g_commands[] = { + CMD_BIND_NO_OUTPUT( "quit", quit), + CMD_BIND( "echo", echo), + CMD_BIND_NO_OUTPUT( "true", true_command), + CMD_BIND_NO_OUTPUT( "false", false_command), + CMD_BIND( "try", try_command), + CMD_BIND_NO_OUTPUT( "silent", silent_command), + CMD_BIND_NO_OUTPUT( "reload", reload), + CMD_BIND( "version", version), + CMD_BIND( "list_commands", list_commands), + CMD_BIND( "list_monitors", list_monitors), + CMD_BIND( "set_monitors", set_monitor_rects_command), + CMD_BIND( "disjoin_rects", disjoin_rects_command), + CMD_BIND( "list_keybinds", key_list_binds), + CMD_BIND( "list_padding", list_padding), + CMD_BIND( "keybind", keybind), + CMD_BIND( "keyunbind", keyunbind), + CMD_BIND( "mousebind", mouse_bind_command), + CMD_BIND_NO_OUTPUT( "mouseunbind", mouse_unbind_all), + CMD_BIND_NO_OUTPUT( "spawn", spawn), + CMD_BIND_NO_OUTPUT( "wmexec", wmexec), + CMD_BIND_NO_OUTPUT( "emit_hook", custom_hook_emit), + CMD_BIND( "bring", frame_current_bring), + CMD_BIND_NO_OUTPUT( "focus_nth", frame_current_set_selection), + CMD_BIND_NO_OUTPUT( "cycle", frame_current_cycle_selection), + CMD_BIND_NO_OUTPUT( "cycle_all", cycle_all_command), + CMD_BIND( "cycle_layout", frame_current_cycle_client_layout), + CMD_BIND_NO_OUTPUT( "cycle_frame", cycle_frame_command), + CMD_BIND( "close", close_command), + CMD_BIND_NO_OUTPUT( "close_or_remove",close_or_remove_command), + CMD_BIND_NO_OUTPUT( "close_and_remove",close_and_remove_command), + CMD_BIND( "split", frame_split_command), + CMD_BIND( "resize", frame_change_fraction_command), + CMD_BIND( "focus_edge", frame_focus_edge), + CMD_BIND( "focus", frame_focus_command), + CMD_BIND( "shift_edge", frame_move_window_edge), + CMD_BIND( "shift", frame_move_window_command), + CMD_BIND( "shift_to_monitor",shift_to_monitor), + CMD_BIND_NO_OUTPUT( "remove", frame_remove_command), + CMD_BIND( "set", settings_set_command), + CMD_BIND( "toggle", settings_toggle), + CMD_BIND( "cycle_value", settings_cycle_value), + CMD_BIND_NO_OUTPUT( "cycle_monitor", monitor_cycle_command), + CMD_BIND( "focus_monitor", monitor_focus_command), + CMD_BIND( "get", settings_get), + CMD_BIND( "add", tag_add_command), + CMD_BIND( "use", monitor_set_tag_command), + CMD_BIND( "use_index", monitor_set_tag_by_index_command), + CMD_BIND( "use_previous", monitor_set_previous_tag_command), + CMD_BIND( "jumpto", jumpto_command), + CMD_BIND( "floating", tag_set_floating_command), + CMD_BIND_NO_OUTPUT( "fullscreen", client_set_property_command), + CMD_BIND_NO_OUTPUT( "pseudotile", client_set_property_command), + CMD_BIND( "tag_status", print_tag_status_command), + CMD_BIND( "merge_tag", tag_remove_command), + CMD_BIND( "rename", tag_rename_command), + CMD_BIND( "move", tag_move_window_command), + CMD_BIND_NO_OUTPUT( "rotate", layout_rotate_command), + CMD_BIND( "move_index", tag_move_window_by_index_command), + CMD_BIND( "add_monitor", add_monitor_command), + CMD_BIND( "raise_monitor", monitor_raise_command), + CMD_BIND( "remove_monitor", remove_monitor_command), + CMD_BIND( "move_monitor", move_monitor_command), + CMD_BIND( "rename_monitor", rename_monitor_command), + CMD_BIND( "monitor_rect", monitor_rect_command), + CMD_BIND( "pad", monitor_set_pad_command), + CMD_BIND( "raise", raise_command), + CMD_BIND( "rule", rule_add_command), + CMD_BIND( "unrule", rule_remove_command), + CMD_BIND( "list_rules", rule_print_all_command), + CMD_BIND( "layout", print_layout_command), + CMD_BIND( "stack", print_stack_command), + CMD_BIND( "dump", print_layout_command), + CMD_BIND( "load", load_command), + CMD_BIND( "complete", complete_command), + CMD_BIND( "complete_shell", complete_command), + CMD_BIND_NO_OUTPUT( "lock", monitors_lock_command), + CMD_BIND_NO_OUTPUT( "unlock", monitors_unlock_command), + CMD_BIND( "lock_tag", monitor_lock_tag_command), + CMD_BIND( "unlock_tag", monitor_unlock_tag_command), + CMD_BIND( "set_layout", frame_current_set_client_layout), + CMD_BIND( "detect_monitors",detect_monitors_command), + CMD_BIND( "chain", command_chain_command), + CMD_BIND( "and", command_chain_command), + CMD_BIND( "or", command_chain_command), + CMD_BIND( "!", negate_command), + CMD_BIND( "attr", attr_command), + CMD_BIND( "compare", compare_command), + CMD_BIND( "object_tree", print_object_tree_command), + CMD_BIND( "get_attr", hsattribute_get_command), + CMD_BIND( "set_attr", hsattribute_set_command), + CMD_BIND( "new_attr", userattribute_command), + CMD_BIND( "mktemp", tmpattribute_command), + CMD_BIND( "remove_attr", userattribute_remove_command), + CMD_BIND( "substitute", substitute_command), + CMD_BIND( "sprintf", sprintf_command), + CMD_BIND( "getenv", getenv_command), + CMD_BIND( "setenv", setenv_command), + CMD_BIND( "unsetenv", unsetenv_command), + { CommandBindingCB() } +}; + +// core functions +int quit() { + g_aboutToQuit = true; + return 0; +} + +// reload config +int reload() { + execute_autostart_file(); + return 0; +} + +int version(int argc, char* argv[], GString* output) { + (void) argc; + (void) argv; + g_string_append(output, HERBSTLUFT_VERSION_STRING); + return 0; +} + +int echo(int argc, char* argv[], GString* output) { + if (SHIFT(argc, argv)) { + // if there still is an argument + g_string_append(output, argv[0]); + while (SHIFT(argc, argv)) { + g_string_append_c(output, ' '); + g_string_append(output, argv[0]); + } + } + g_string_append_c(output, '\n'); + return 0; +} + +int true_command() { + return 0; +} + +int false_command() { + return 1; +} + +int try_command(int argc, char* argv[], GString* output) { + if (argc <= 1) { + return HERBST_NEED_MORE_ARGS; + } + (void)SHIFT(argc, argv); + call_command(argc, argv, output); + return 0; +} + +int silent_command(int argc, char* argv[]) { + if (argc <= 1) { + return HERBST_NEED_MORE_ARGS; + } + (void)SHIFT(argc, argv); + return call_command_no_output(argc, argv); +} + +// prints or dumps the layout of an given tag +// first argument tells whether to print or to dump +int print_layout_command(int argc, char** argv, GString* output) { + HSTag* tag = NULL; + // an empty argv[1] means current focused tag + if (argc >= 2 && argv[1][0] != '\0') { + tag = find_tag(argv[1]); + if (!tag) { + g_string_append_printf(output, + "%s: Tag \"%s\" not found\n", argv[0], argv[1]); + return HERBST_INVALID_ARGUMENT; + } + } else { // use current tag + HSMonitor* m = get_current_monitor(); + tag = m->tag; + } + assert(tag != NULL); + + HSFrame* frame = lookup_frame(tag->frame, argc >= 3 ? argv[2] : ""); + if (argc > 0 && !strcmp(argv[0], "dump")) { + dump_frame_tree(frame, output); + } else { + print_frame_tree(frame, output); + } + return 0; +} + +int load_command(int argc, char** argv, GString* output) { + // usage: load TAG LAYOUT + HSTag* tag = NULL; + if (argc < 2) { + return HERBST_NEED_MORE_ARGS; + } + char* layout_string = argv[1]; + if (argc >= 3) { + tag = find_tag(argv[1]); + layout_string = argv[2]; + if (!tag) { + g_string_append_printf(output, + "%s: Tag \"%s\" not found\n", argv[0], argv[1]); + return HERBST_INVALID_ARGUMENT; + } + } else { // use current tag + HSMonitor* m = get_current_monitor(); + tag = m->tag; + } + assert(tag != NULL); + char* rest = load_frame_tree(tag->frame, layout_string, output); + if (output->len > 0) { + g_string_prepend(output, "load: "); + } + tag_set_flags_dirty(); // we probably changed some window positions + // arrange monitor + HSMonitor* m = find_monitor_with_tag(tag); + if (m) { + frame_show_recursive(tag->frame); + if (get_current_monitor() == m) { + frame_focus_recursive(tag->frame); + } + monitor_apply_layout(m); + } else { + frame_hide_recursive(tag->frame); + } + if (!rest) { + g_string_append_printf(output, + "%s: Error while parsing!\n", argv[0]); + return HERBST_INVALID_ARGUMENT; + } + if (rest[0] != '\0') { // if string was not parsed completely + g_string_append_printf(output, + "%s: Layout description was too long\n", argv[0]); + g_string_append_printf(output, + "%s: \"%s\" has not been parsed\n", argv[0], rest); + return HERBST_INVALID_ARGUMENT; + } + return 0; +} + +int print_tag_status_command(int argc, char** argv, GString* output) { + HSMonitor* monitor; + if (argc >= 2) { + monitor = string_to_monitor(argv[1]); + } else { + monitor = get_current_monitor(); + } + if (monitor == NULL) { + g_string_append_printf(output, + "%s: Monitor \"%s\" not found!\n", argv[0], argv[1]); + return HERBST_INVALID_ARGUMENT; + } + tag_update_flags(); + g_string_append_c(output, '\t'); + for (int i = 0; i < tag_get_count(); i++) { + HSTag* tag = get_tag_by_index(i); + // print flags + char c = '.'; + if (tag->flags & TAG_FLAG_USED) { + c = ':'; + } + HSMonitor *tag_monitor = find_monitor_with_tag(tag); + if (tag_monitor == monitor) { + c = '+'; + if (monitor == get_current_monitor()) { + c = '#'; + } + } else if (tag_monitor) { + c = '-'; + if (get_current_monitor() == tag_monitor) { + c = '%'; + } + } + if (tag->flags & TAG_FLAG_URGENT) { + c = '!'; + } + g_string_append_c(output, c); + g_string_append(output, tag->name->str); + g_string_append_c(output, '\t'); + } + return 0; +} + +int custom_hook_emit(int argc, const char** argv) { + hook_emit(argc - 1, argv + 1); + return 0; +} + +// spawn() heavily inspired by dwm.c +int spawn(int argc, char** argv) { + if (argc < 2) { + return HERBST_NEED_MORE_ARGS; + } + if (fork() == 0) { + // only look in child + if (g_display) { + close(ConnectionNumber(g_display)); + } + // shift all args in argv by 1 to the front + // so that we have space for a NULL entry at the end for execvp + char** execargs = argv_duplicate(argc, argv); + free(execargs[0]); + int i; + for (i = 0; i < argc-1; i++) { + execargs[i] = execargs[i+1]; + } + execargs[i] = NULL; + // do actual exec + setsid(); + execvp(execargs[0], execargs); + fprintf(stderr, "herbstluftwm: execvp \"%s\"", argv[1]); + perror(" failed"); + exit(0); + } + return 0; +} + +int wmexec(int argc, char** argv) { + if (argc >= 2) { + // shift all args in argv by 1 to the front + // so that we have space for a NULL entry at the end for execvp + char** execargs = argv_duplicate(argc, argv); + free(execargs[0]); + int i; + for (i = 0; i < argc-1; i++) { + execargs[i] = execargs[i+1]; + } + execargs[i] = NULL; + // quit and exec to new window manger + g_exec_args = execargs; + } else { + // exec into same command + g_exec_args = NULL; + } + g_exec_before_quit = true; + g_aboutToQuit = true; + return EXIT_SUCCESS; +} + +int raise_command(int argc, char** argv, GString* output) { + Window win; + HSClient* client = NULL; + win = string_to_client((argc > 1) ? argv[1] : "", &client); + if (client) { + client_raise(client); + } else { + XRaiseWindow(g_display, win); + } + return 0; +} + +int jumpto_command(int argc, char** argv, GString* output) { + if (argc < 2) { + return HERBST_NEED_MORE_ARGS; + } + HSClient* client = NULL; + string_to_client(argv[1], &client); + if (client) { + focus_client(client, true, true); + return 0; + } else { + g_string_append_printf(output, + "%s: Could not find client", argv[0]); + if (argc > 1) { + g_string_append_printf(output, " \"%s\".\n", argv[1]); + } else { + g_string_append(output, ".\n"); + } + return HERBST_INVALID_ARGUMENT; + } +} + +int getenv_command(int argc, char** argv, GString* output) { + if (argc < 2) { + return HERBST_NEED_MORE_ARGS; + } + char* envvar = getenv(argv[1]); + if (envvar == NULL) { + return HERBST_ENV_UNSET; + } + g_string_append_printf(output, "%s\n", envvar); + return 0; +} + +int setenv_command(int argc, char** argv, GString* output) { + if (argc < 3) { + return HERBST_NEED_MORE_ARGS; + } + if (setenv(argv[1], argv[2], 1) != 0) { + g_string_append_printf(output, + "%s: Could not set environment variable: %s\n", argv[0], strerror(errno)); + return HERBST_UNKNOWN_ERROR; + } + return 0; +} + +int unsetenv_command(int argc, char** argv, GString* output) { + if (argc < 2) { + return HERBST_NEED_MORE_ARGS; + } + if (unsetenv(argv[1]) != 0) { + g_string_append_printf(output, + "%s: Could not unset environment variable: %s\n", argv[0], strerror(errno)); + return HERBST_UNKNOWN_ERROR; + } + return 0; +} + +// handle x-events: + +void event_on_configure(XEvent event) { + XConfigureRequestEvent* cre = &event.xconfigurerequest; + HSClient* client = get_client_from_window(cre->window); + if (client) { + bool changes = false; + Rectangle newRect = client->float_size; + if (client->sizehints_floating && + (is_client_floated(client) || client->pseudotile)) + { + bool width_requested = 0 != (cre->value_mask & CWWidth); + bool height_requested = 0 != (cre->value_mask & CWHeight); + bool x_requested = 0 != (cre->value_mask & CWX); + bool y_requested = 0 != (cre->value_mask & CWY); + cre->width += 2*cre->border_width; + cre->height += 2*cre->border_width; + if (width_requested && newRect.width != cre->width) changes = true; + if (height_requested && newRect.height != cre->height) changes = true; + if (x_requested || y_requested) changes = true; + if (x_requested) newRect.x = cre->x; + if (y_requested) newRect.y = cre->y; + if (width_requested) newRect.width = cre->width; + if (height_requested) newRect.height = cre->height; + } + if (changes && is_client_floated(client)) { + client->float_size = newRect; + client_resize_floating(client, find_monitor_with_tag(client->tag)); + } else if (changes && client->pseudotile) { + client->float_size = newRect; + monitor_apply_layout(find_monitor_with_tag(client->tag)); + } else { + // FIXME: why send event and not XConfigureWindow or XMoveResizeWindow?? + client_send_configure(client); + } + } else { + // if client not known.. then allow configure. + // its probably a nice conky or dzen2 bar :) + XWindowChanges wc; + wc.x = cre->x; + wc.y = cre->y; + wc.width = cre->width; + wc.height = cre->height; + wc.border_width = cre->border_width; + wc.sibling = cre->above; + wc.stack_mode = cre->detail; + XConfigureWindow(g_display, cre->window, cre->value_mask, &wc); + } +} + +// from dwm.c +/* There's no way to check accesses to destroyed windows, thus those cases are + * ignored (especially on UnmapNotify's). Other types of errors call Xlibs + * default error handler, which may call exit. */ +int xerror(Display *dpy, XErrorEvent *ee) { + if(ee->error_code == BadWindow + || ee->error_code == BadGC + || ee->error_code == BadPixmap + || (ee->request_code == X_SetInputFocus && ee->error_code == BadMatch) + || (ee->request_code == X_PolyText8 && ee->error_code == BadDrawable) + || (ee->request_code == X_PolyFillRectangle && ee->error_code == BadDrawable) + || (ee->request_code == X_PolySegment && ee->error_code == BadDrawable) + || (ee->request_code == X_ConfigureWindow && ee->error_code == BadMatch) + || (ee->request_code == X_GrabButton && ee->error_code == BadAccess) + || (ee->request_code == X_GrabKey && ee->error_code == BadAccess) + || (ee->request_code == X_CopyArea && ee->error_code == BadDrawable)) { + return 0; + } + fprintf(stderr, "herbstluftwm: fatal error: request code=%d, error code=%d\n", + ee->request_code, ee->error_code); + if (ee->error_code == BadDrawable) { + HSDebug("Warning: ignoring X_BadDrawable"); + return 0; + } + // TODO + //return g_xerrorxlib(dpy, ee); /* may call exit */ + return 0; +} + +int xerrordummy(Display *dpy, XErrorEvent *ee) { + return 0; +} + +// from dwm.c +/* Startup Error handler to check if another window manager + * is already running. */ +int xerrorstart(Display *dpy, XErrorEvent *ee) { + g_otherwm = True; + return -1; +} + +// from dwm.c +void checkotherwm(void) { + g_otherwm = False; + g_xerrorxlib = XSetErrorHandler(xerrorstart); + /* this causes an error if some other window manager is running */ + XSelectInput(g_display, DefaultRootWindow(g_display), SubstructureRedirectMask); + XSync(g_display, False); + if(g_otherwm) + die("herbstluftwm: another window manager is already running\n"); + XSetErrorHandler(xerror); + XSync(g_display, False); +} + +// scan for windows and add them to the list of managed clients +// from dwm.c +void scan(void) { + unsigned int num; + Window d1, d2, *cl, *wins = NULL; + unsigned long cl_count; + XWindowAttributes wa; + + ewmh_get_original_client_list(&cl, &cl_count); + if (XQueryTree(g_display, g_root, &d1, &d2, &wins, &num)) { + for (int i = 0; i < num; i++) { + if(!XGetWindowAttributes(g_display, wins[i], &wa) + || wa.override_redirect || XGetTransientForHint(g_display, wins[i], &d1)) + continue; + // only manage mapped windows.. no strange wins like: + // luakit/dbus/(ncurses-)vim + // but manage it if it was in the ewmh property _NET_CLIENT_LIST by + // the previous window manager + // TODO: what would dwm do? + if (is_window_mapped(g_display, wins[i]) + || 0 <= array_find(cl, cl_count, sizeof(Window), wins+i)) { + manage_client(wins[i]); + XMapWindow(g_display, wins[i]); + } + } + if(wins) + XFree(wins); + } + // ensure every original client is managed again + for (int i = 0; i < cl_count; i++) { + if (get_client_from_window(cl[i])) continue; + if (!XGetWindowAttributes(g_display, cl[i], &wa) + || wa.override_redirect + || XGetTransientForHint(g_display, cl[i], &d1)) + { + continue; + } + XReparentWindow(g_display, cl[i], g_root, 0,0); + manage_client(cl[i]); + } +} + +void execute_autostart_file() { + GString* path = NULL; + if (g_autostart_path) { + path = g_string_new(g_autostart_path); + } else { + // find right directory + char* xdg_config_home = getenv("XDG_CONFIG_HOME"); + if (xdg_config_home) { + path = g_string_new(xdg_config_home); + } else { + char* home = getenv("HOME"); + if (!home) { + g_warning("Will not run autostart file. " + "Neither $HOME or $XDG_CONFIG_HOME is set.\n"); + return; + } + path = g_string_new(home); + g_string_append_c(path, G_DIR_SEPARATOR); + g_string_append(path, ".config"); + } + g_string_append_c(path, G_DIR_SEPARATOR); + g_string_append(path, HERBSTLUFT_AUTOSTART); + } + if (0 == fork()) { + if (g_display) { + close(ConnectionNumber(g_display)); + } + setsid(); + execl(path->str, path->str, NULL); + + const char* global_autostart = HERBSTLUFT_GLOBAL_AUTOSTART; + HSDebug("Can not execute %s, falling back to %s\n", path->str, global_autostart); + execl(global_autostart, global_autostart, NULL); + + fprintf(stderr, "herbstluftwm: execvp \"%s\"", global_autostart); + perror(" failed"); + exit(EXIT_FAILURE); + } + g_string_free(path, true); +} + +static void parse_arguments(int argc, char** argv) { + static struct option long_options[] = { + {"autostart", 1, 0, 'c'}, + {"version", 0, 0, 'v'}, + {"locked", 0, 0, 'l'}, + {"verbose", 0, &g_verbose, 1}, + {0, 0, 0, 0} + }; + // parse options + while (1) { + int option_index = 0; + int c = getopt_long(argc, argv, "+c:vl", long_options, &option_index); + if (c == -1) break; + switch (c) { + case 0: + /* ignore recognized long option */ + break; + case 'v': + printf("%s %s\n", argv[0], HERBSTLUFT_VERSION); + printf("Copyright (c) 2011-2014 Thorsten Wißmann\n"); + printf("Released under the Simplified BSD License\n"); + exit(0); + break; + case 'c': + g_autostart_path = optarg; + break; + case 'l': + g_initial_monitors_locked = 1; + break; + default: + exit(EXIT_FAILURE); + } + } +} + +static void remove_zombies(int signal) { + int bgstatus; + while (waitpid(-1, &bgstatus, WNOHANG) > 0); +} + +static void handle_signal(int signal) { + HSDebug("Interrupted by signal %d\n", signal); + g_aboutToQuit = true; + return; +} + +static void sigaction_signal(int signum, void (*handler)(int)) { + struct sigaction act; + act.sa_handler = handler; + sigemptyset(&act.sa_mask); + act.sa_flags = SA_NOCLDSTOP | SA_RESTART; + sigaction(signum, &act, NULL); +} + +static void fetch_settings() { + // fetch settings only for this main.c file from settings table + g_focus_follows_mouse = &(settings_find("focus_follows_mouse")->value.i); + g_raise_on_click = &(settings_find("raise_on_click")->value.i); +} + +HandlerTable g_default_handler; + +static void init_handler_table() { + g_default_handler[ ButtonPress ] = buttonpress; + g_default_handler[ ButtonRelease ] = buttonrelease; + g_default_handler[ ClientMessage ] = ewmh_handle_client_message; + g_default_handler[ ConfigureNotify ] = configurenotify; + g_default_handler[ ConfigureRequest ] = configurerequest; + g_default_handler[ CreateNotify ] = createnotify; + g_default_handler[ DestroyNotify ] = destroynotify; + g_default_handler[ EnterNotify ] = enternotify; + g_default_handler[ Expose ] = expose; + g_default_handler[ FocusIn ] = focusin; + g_default_handler[ KeyPress ] = keypress; + g_default_handler[ MapNotify ] = mapnotify; + g_default_handler[ MapRequest ] = maprequest; + g_default_handler[ MappingNotify ] = mappingnotify; + g_default_handler[ MotionNotify ] = motionnotify; + g_default_handler[ PropertyNotify ] = propertynotify; + g_default_handler[ UnmapNotify ] = unmapnotify; +} + +static struct { + void (*init)(); + void (*destroy)(); +} g_modules[] = { + { ipc_init, ipc_destroy }, + { object_tree_init, object_tree_destroy }, + { key_init, key_destroy }, + { settings_init, settings_destroy }, + { floating_init, floating_destroy }, + { stacklist_init, stacklist_destroy }, + { layout_init, layout_destroy }, + { tag_init, tag_destroy }, + { clientlist_init, clientlist_destroy }, + { decorations_init, decorations_destroy }, + { monitor_init, monitor_destroy }, + { ewmh_init, ewmh_destroy }, + { mouse_init, mouse_destroy }, + { hook_init, hook_destroy }, + { rules_init, rules_destroy }, +}; + +/* ----------------------------- */ +/* event handler implementations */ +/* ----------------------------- */ + +void buttonpress(XEvent* event) { + XButtonEvent* be = &(event->xbutton); + HSDebug("name is: ButtonPress on sub %lx, win %lx\n", be->subwindow, be->window); + if (mouse_binding_find(be->state, be->button)) { + mouse_handle_event(event); + } else { + HSClient* client = get_client_from_window(be->window); + if (client) { + focus_client(client, false, true); + if (*g_raise_on_click) { + client_raise(client); + } + } + } + XAllowEvents(g_display, ReplayPointer, be->time); +} + +void buttonrelease(XEvent* event) { + HSDebug("name is: ButtonRelease\n"); + mouse_stop_drag(); +} + +void createnotify(XEvent* event) { + // printf("name is: CreateNotify\n"); + if (is_ipc_connectable(event->xcreatewindow.window)) { + ipc_add_connection(event->xcreatewindow.window); + } +} + +void configurerequest(XEvent* event) { + HSDebug("name is: ConfigureRequest\n"); + event_on_configure(*event); +} + +void configurenotify(XEvent* event) { + if (event->xconfigure.window == g_root && + settings_find("auto_detect_monitors")->value.i) { + const char* args[] = { "detect_monitors" }; + detect_monitors_command(LENGTH(args), args, NULL); + } + // HSDebug("name is: ConfigureNotify\n"); +} + +void destroynotify(XEvent* event) { + // try to unmanage it + //HSDebug("name is: DestroyNotify for %lx\n", event->xdestroywindow.window); + unmanage_client(event->xdestroywindow.window); + if (!is_herbstluft_window(g_display, event->xdestroywindow.window)) { + DesktopWindow::unregisterDesktop(event->xdestroywindow.window); + } +} + +void enternotify(XEvent* event) { + XCrossingEvent *ce = &event->xcrossing; + //HSDebug("name is: EnterNotify, focus = %d\n", event->xcrossing.focus); + if (!mouse_is_dragging() + && *g_focus_follows_mouse + && ce->focus == false) { + HSClient* c = get_client_from_window(ce->window); + HSFrame* target; + if (c && c->tag->floating == false + && (target = find_frame_with_client(c->tag->frame, c)) + && target->content.clients.layout == LAYOUT_MAX + && frame_focused_client(target) != c) { + // don't allow focus_follows_mouse if another window would be + // hidden during that focus change (which only occurs in max layout) + } else if (c) { + focus_client(c, false, true); + } + } +} + +void expose(XEvent* event) { + //if (event->xexpose.count > 0) return; + //Window ewin = event->xexpose.window; + //HSDebug("name is: Expose for window %lx\n", ewin); +} + +void focusin(XEvent* event) { + //HSDebug("name is: FocusIn\n"); +} + +void keypress(XEvent* event) { + //HSDebug("name is: KeyPress\n"); + handle_key_press(event); +} + +void mappingnotify(XEvent* event) { + { + // regrab when keyboard map changes + XMappingEvent *ev = &event->xmapping; + XRefreshKeyboardMapping(ev); + if(ev->request == MappingKeyboard) { + regrab_keys(); + //TODO: mouse_regrab_all(); + } + } +} + +void motionnotify(XEvent* event) { + handle_motion_event(event); +} + +void mapnotify(XEvent* event) { + //HSDebug("name is: MapNotify\n"); + HSClient* c; + if ((c = get_client_from_window(event->xmap.window))) { + // reset focus. so a new window gets the focus if it shall have the + // input focus + frame_focus_recursive(g_cur_frame); + // also update the window title - just to be sure + client_update_title(c); + } +} + +void maprequest(XEvent* event) { + HSDebug("name is: MapRequest\n"); + XMapRequestEvent* mapreq = &event->xmaprequest; + if (is_herbstluft_window(g_display, mapreq->window)) { + // just map the window if it wants that + XWindowAttributes wa; + if (!XGetWindowAttributes(g_display, mapreq->window, &wa)) { + return; + } + XMapWindow(g_display, mapreq->window); + } else if (!get_client_from_window(mapreq->window)) { + // client should be managed (is not ignored) + // but is not managed yet + HSClient* client = manage_client(mapreq->window); + if (client && find_monitor_with_tag(client->tag)) { + XMapWindow(g_display, mapreq->window); + } + } + // else: ignore all other maprequests from windows + // that are managed already +} + +void propertynotify(XEvent* event) { + // printf("name is: PropertyNotify\n"); + XPropertyEvent *ev = &event->xproperty; + HSClient* client; + if (ev->state == PropertyNewValue) { + if (is_ipc_connectable(event->xproperty.window)) { + ipc_handle_connection(event->xproperty.window); + } else if((client = get_client_from_window(ev->window))) { + if (ev->atom == XA_WM_HINTS) { + client_update_wm_hints(client); + } else if (ev->atom == XA_WM_NORMAL_HINTS) { + updatesizehints(client); + HSMonitor* m = find_monitor_with_tag(client->tag); + if (m) monitor_apply_layout(m); + } else if (ev->atom == XA_WM_NAME || + ev->atom == g_netatom[NetWmName]) { + client_update_title(client); + } + } + } +} + +void unmapnotify(XEvent* event) { + HSDebug("name is: UnmapNotify for %lx\n", event->xunmap.window); + if (!clientlist_ignore_unmapnotify(event->xunmap.window)) { + unmanage_client(event->xunmap.window); + } +} + +/* ---- */ +/* main */ +/* ---- */ + +int main(int argc, char* argv[]) { + init_handler_table(); + + parse_arguments(argc, argv); + if(!(g_display = XOpenDisplay(NULL))) + die("herbstluftwm: cannot open display\n"); + checkotherwm(); + // remove zombies on SIGCHLD + sigaction_signal(SIGCHLD, remove_zombies); + sigaction_signal(SIGINT, handle_signal); + sigaction_signal(SIGQUIT, handle_signal); + sigaction_signal(SIGTERM, handle_signal); + // set some globals + g_screen = DefaultScreen(g_display); + g_screen_width = DisplayWidth(g_display, g_screen); + g_screen_height = DisplayHeight(g_display, g_screen); + g_root = RootWindow(g_display, g_screen); + XSelectInput(g_display, g_root, ROOT_EVENT_MASK); + + // initialize subsystems + for (int i = 0; i < LENGTH(g_modules); i++) { + g_modules[i].init(); + } + fetch_settings(); + + // setup + ensure_monitors_are_available(); + scan(); + tag_force_update_flags(); + all_monitors_apply_layout(); + ewmh_update_all(); + execute_autostart_file(); + clientlist_end_startup(); + + // main loop + XEvent event; + int x11_fd; + fd_set in_fds; + x11_fd = ConnectionNumber(g_display); + while (!g_aboutToQuit) { + FD_ZERO(&in_fds); + FD_SET(x11_fd, &in_fds); + // wait for an event or a signal + select(x11_fd + 1, &in_fds, 0, 0, NULL); + if (g_aboutToQuit) { + break; + } + while (XPending(g_display)) { + XNextEvent(g_display, &event); + void (*handler) (XEvent*) = g_default_handler[event.type]; + if (handler != NULL) { + handler(&event); + } + } + } + + // destroy all subsystems + for (int i = LENGTH(g_modules); i --> 0;) { + g_modules[i].destroy(); + } + XCloseDisplay(g_display); + // check if we shall restart an other window manager + if (g_exec_before_quit) { + if (g_exec_args) { + // do actual exec + HSDebug("==> Doing wmexec to %s\n", g_exec_args[0]); + execvp(g_exec_args[0], g_exec_args); + fprintf(stderr, "herbstluftwm: execvp \"%s\"", g_exec_args[0]); + perror(" failed"); + } + // on failure or if no other wm given, then fall back + HSDebug("==> Doing wmexec to %s\n", argv[0]); + execvp(argv[0], argv); + fprintf(stderr, "herbstluftwm: execvp \"%s\"", argv[1]); + perror(" failed"); + return EXIT_FAILURE; + } + return EXIT_SUCCESS; +} + diff -Nru herbstluftwm-0.6.2/src/monitor.c herbstluftwm-0.7.0/src/monitor.c --- herbstluftwm-0.6.2/src/monitor.c 2014-03-26 23:54:22.000000000 +0000 +++ herbstluftwm-0.7.0/src/monitor.c 1970-01-01 00:00:00.000000000 +0000 @@ -1,1386 +0,0 @@ -/** Copyright 2011-2013 Thorsten Wißmann. All rights reserved. - * - * This software is licensed under the "Simplified BSD License". - * See LICENSE for details */ - -#include -#include -#include -#include -#include -#ifdef XINERAMA -#include -#endif /* XINERAMA */ - -#include "globals.h" -#include "ipc-protocol.h" -#include "utils.h" -#include "mouse.h" -#include "hook.h" -#include "layout.h" -#include "tag.h" -#include "ewmh.h" -#include "monitor.h" -#include "settings.h" -#include "stack.h" -#include "clientlist.h" - -int* g_monitors_locked; -int* g_swap_monitors_to_get_tag; -int* g_smart_frame_surroundings; -int* g_mouse_recenter_gap; -HSStack* g_monitor_stack; -GArray* g_monitors; // Array of HSMonitor* -HSObject* g_monitor_object; -HSObject* g_monitor_by_name_object; - -typedef struct RectList { - Rectangle rect; - struct RectList* next; -} RectList; - -static RectList* reclist_insert_disjoint(RectList* head, RectList* mt); -static RectList* disjoin_rects(Rectangle* buf, size_t count); - -void monitor_init() { - g_monitors_locked = &(settings_find("monitors_locked")->value.i); - g_cur_monitor = 0; - g_monitors = g_array_new(false, false, sizeof(HSMonitor*)); - g_swap_monitors_to_get_tag = &(settings_find("swap_monitors_to_get_tag")->value.i); - g_smart_frame_surroundings = &(settings_find("smart_frame_surroundings")->value.i); - g_mouse_recenter_gap = &(settings_find("mouse_recenter_gap")->value.i); - g_monitor_stack = stack_create(); - g_monitor_object = hsobject_create_and_link(hsobject_root(), "monitors"); - HSAttribute attributes[] = { - ATTRIBUTE_UINT("count", g_monitors->len, ATTR_READ_ONLY), - ATTRIBUTE_LAST, - }; - hsobject_set_attributes(g_monitor_object, attributes); - g_monitor_by_name_object = hsobject_create_and_link(g_monitor_object, "by-name"); -} - -void monitor_destroy() { - for (int i = 0; i < g_monitors->len; i++) { - HSMonitor* m = monitor_with_index(i); - stack_remove_slice(g_monitor_stack, m->slice); - slice_destroy(m->slice); - hsobject_free(&m->object); - if (m->name) { - g_string_free(m->name, true); - } - g_string_free(m->display_name, true); - g_free(m); - } - hsobject_unlink_and_destroy(g_monitor_object, g_monitor_by_name_object); - hsobject_unlink_and_destroy(hsobject_root(), g_monitor_object); - stack_destroy(g_monitor_stack); - g_array_free(g_monitors, true); -} - -void monitor_apply_layout(HSMonitor* monitor) { - if (monitor) { - if (*g_monitors_locked) { - monitor->dirty = true; - return; - } - monitor->dirty = false; - Rectangle rect = monitor->rect; - // apply pad - rect.x += monitor->pad_left; - rect.width -= (monitor->pad_left + monitor->pad_right); - rect.y += monitor->pad_up; - rect.height -= (monitor->pad_up + monitor->pad_down); - if (!*g_smart_frame_surroundings || monitor->tag->frame->type == TYPE_FRAMES ) { - // apply frame gap - rect.x += *g_frame_gap; - rect.y += *g_frame_gap; - rect.height -= *g_frame_gap; - rect.width -= *g_frame_gap; - } - monitor_restack(monitor); - if (get_current_monitor() == monitor) { - frame_focus_recursive(monitor->tag->frame); - } - if (monitor->tag->floating) { - frame_apply_floating_layout(monitor->tag->frame, monitor); - } else { - frame_apply_layout(monitor->tag->frame, rect); - if (!monitor->lock_frames && !monitor->tag->floating) { - frame_update_frame_window_visibility(monitor->tag->frame); - } - } - // remove all enternotify-events from the event queue that were - // generated while arranging the clients on this monitor - drop_enternotify_events(); - } -} - -int list_monitors(int argc, char** argv, GString* output) { - (void)argc; - (void)argv; - int i; - GString* monitor_name = g_string_new(""); - for (i = 0; i < g_monitors->len; i++) { - HSMonitor* monitor = monitor_with_index(i); - if (monitor->name != NULL ) { - g_string_printf(monitor_name, ", named \"%s\"", - monitor->name->str); - } else { - g_string_truncate(monitor_name, 0); - } - g_string_append_printf(output, "%d: %dx%d%+d%+d with tag \"%s\"%s%s%s\n", - i, - monitor->rect.width, monitor->rect.height, - monitor->rect.x, monitor->rect.y, - monitor->tag ? monitor->tag->name->str : "???", - monitor_name->str, - (g_cur_monitor == i) ? " [FOCUS]" : "", - monitor->lock_tag ? " [LOCKED]" : ""); - } - g_string_free(monitor_name, true); - return 0; -} - -int list_padding(int argc, char** argv, GString* output) { - HSMonitor* monitor; - if (argc < 2) { - monitor = get_current_monitor(); - } else { - monitor = string_to_monitor(argv[1]); - if (monitor == NULL) { - g_string_append_printf(output, - "%s: Monitor \"%s\" not found!\n", argv[0], argv[1]); - return HERBST_INVALID_ARGUMENT; - } - } - g_string_append_printf(output, "%d %d %d %d\n", - monitor->pad_up, - monitor->pad_right, - monitor->pad_down, - monitor->pad_left); - return 0; -} - -static bool rects_intersect(RectList* m1, RectList* m2) { - Rectangle *r1 = &m1->rect, *r2 = &m2->rect; - bool is = TRUE; - is = is && intervals_intersect(r1->x, r1->x + r1->width, - r2->x, r2->x + r2->width); - is = is && intervals_intersect(r1->y, r1->y + r1->height, - r2->y, r2->y + r2->height); - return is; -} - -static Rectangle intersection_area(RectList* m1, RectList* m2) { - Rectangle r; // intersection between m1->rect and m2->rect - r.x = MAX(m1->rect.x, m2->rect.x); - r.y = MAX(m1->rect.y, m2->rect.y); - // the bottom right coordinates of the rects - int br1_x = m1->rect.x + m1->rect.width; - int br1_y = m1->rect.y + m1->rect.height; - int br2_x = m2->rect.x + m2->rect.width; - int br2_y = m2->rect.y + m2->rect.height; - r.width = MIN(br1_x, br2_x) - r.x; - r.height = MIN(br1_y, br2_y) - r.y; - return r; -} - -static RectList* rectlist_create_simple(int x1, int y1, int x2, int y2) { - if (x1 >= x2 || y1 >= y2) { - return NULL; - } - RectList* r = g_new0(RectList, 1); - r->rect.x = x1; - r->rect.y = y1; - r->rect.width = x2 - x1; - r->rect.height = y2 - y1; - r->next = NULL; - return r; -} - -static RectList* insert_rect_border(RectList* head, - Rectangle large, Rectangle center) -{ - // given a large rectangle and a center which guaranteed to be a subset of - // the large rect, the task is to split "large" into pieces and insert them - // like this: - // - // +------- large ---------+ - // | top | - // |------+--------+-------| - // | left | center | right | - // |------+--------+-------| - // | bottom | - // +-----------------------+ - RectList *top, *left, *right, *bottom; - // coordinates of the bottom right corner of large - int br_x = large.x + large.width, br_y = large.y + large.height; - RectList* (*r)(int,int,int,int) = rectlist_create_simple; - top = r(large.x, large.y, large.x + large.width, center.y); - left = r(large.x, center.y, center.x, center.y + center.height); - right = r(center.x + center.width, center.y, br_x, center.y + center.height); - bottom= r(large.x, center.y + center.height, br_x, br_y); - - RectList* parts[] = { top, left, right, bottom }; - for (int i = 0; i < LENGTH(parts); i++) { - head = reclist_insert_disjoint(head, parts[i]); - } - return head; -} - -// insert a new element without any intersections into the given list -static RectList* reclist_insert_disjoint(RectList* head, RectList* element) { - if (!element) { - return head; - } else if (!head) { - // if the list is empty, then intersection-free insertion is trivial - element->next = NULL; - return element; - } else if (!rects_intersect(head, element)) { - head->next = reclist_insert_disjoint(head->next, element); - return head; - } else { - // element intersects with the head rect - Rectangle center = intersection_area(head, element); - Rectangle large = head->rect; - head->rect = center; - head->next = insert_rect_border(head->next, large, center); - head->next = insert_rect_border(head->next, element->rect, center); - g_free(element); - return head; - } -} - -static void rectlist_free(RectList* head) { - if (!head) return; - RectList* next = head->next; - g_free(head); - rectlist_free(next); -} - -static int rectlist_length_acc(RectList* head, int acc) { - if (!head) return acc; - else return rectlist_length_acc(head->next, acc + 1); -} - -static int rectlist_length(RectList* head) { - return rectlist_length_acc(head, 0); -} - -static RectList* disjoin_rects(Rectangle* buf, size_t count) { - RectList* cur; - struct RectList* rects = NULL; - for (int i = 0; i < count; i++) { - cur = g_new0(RectList, 1); - cur->rect = buf[i]; - rects = reclist_insert_disjoint(rects, cur); - } - return rects; -} - - -int disjoin_rects_command(int argc, char** argv, GString* output) { - (void)SHIFT(argc, argv); - if (argc < 1) { - return HERBST_NEED_MORE_ARGS; - } - Rectangle* buf = g_new(Rectangle, argc); - for (int i = 0; i < argc; i++) { - buf[i] = parse_rectangle(argv[i]); - } - - RectList* rects = disjoin_rects(buf, argc); - for (RectList* cur = rects; cur; cur = cur->next) { - Rectangle r = cur->rect; - g_string_append_printf(output, "%dx%d%+d%+d\n", - r.width, r.height, r.x, r.y); - } - rectlist_free(rects); - g_free(buf); - return 0; -} - -int set_monitor_rects_command(int argc, char** argv, GString* output) { - (void)SHIFT(argc, argv); - if (argc < 1) { - return HERBST_NEED_MORE_ARGS; - } - Rectangle* templates = g_new0(Rectangle, argc); - for (int i = 0; i < argc; i++) { - templates[i] = parse_rectangle(argv[i]); - } - int status = set_monitor_rects(templates, argc); - g_free(templates); - if (status == HERBST_TAG_IN_USE) { - g_string_append_printf(output, - "%s: There are not enough free tags\n", argv[0]); - } else if (status == HERBST_INVALID_ARGUMENT) { - g_string_append_printf(output, - "%s: Need at least one rectangle\n", argv[0]); - } - return status; -} - -int set_monitor_rects(Rectangle* templates, size_t count) { - if (count < 1) { - return HERBST_INVALID_ARGUMENT; - } - HSTag* tag = NULL; - int i; - for (i = 0; i < MIN(count, g_monitors->len); i++) { - HSMonitor* m = monitor_with_index(i); - m->rect = templates[i]; - } - // add additional monitors - for (; i < count; i++) { - tag = find_unused_tag(); - if (!tag) { - return HERBST_TAG_IN_USE; - } - add_monitor(templates[i], tag, NULL); - frame_show_recursive(tag->frame); - } - // remove monitors if there are too much - while (i < g_monitors->len) { - remove_monitor(i); - } - monitor_update_focus_objects(); - all_monitors_apply_layout(); - return 0; -} - -int find_monitor_index_by_name(char* name) { - int i; - for (i = 0; i < g_monitors->len; i++) { - HSMonitor* mon = monitor_with_index(i); - if (mon != NULL && mon->name != NULL && !strcmp(mon->name->str, name)) { - return i; - } - } - return -1; -} - -HSMonitor* find_monitor_by_name(char* name) { - int i = find_monitor_index_by_name(name); - if (i == -1) { - return NULL; - } else { - return monitor_with_index(i); - } -} - -int string_to_monitor_index(char* string) { - if (string[0] == '\0') { - return g_cur_monitor; - } else if (string[0] == '-' || string[0] == '+') { - if (isdigit(string[1])) { - // relative monitor index - int idx = g_cur_monitor + atoi(string); - idx %= g_monitors->len; - idx += g_monitors->len; - idx %= g_monitors->len; - return idx; - } else if (string[0] == '-') { - enum HSDirection dir = char_to_direction(string[1]); - if (dir < 0) return -1; - return monitor_index_in_direction(get_current_monitor(), dir); - } else { - return -1; - } - } else if (isdigit(string[0])) { - // absolute monitor index - int idx = atoi(string); - if (idx < 0 || idx >= g_monitors->len) { - return -1; - } - return idx; - } else { - // monitor string - return find_monitor_index_by_name(string); - } -} - -int monitor_index_in_direction(HSMonitor* m, enum HSDirection dir) { - int cnt = monitor_count(); - RectangleIdx* rects = g_new0(RectangleIdx, cnt); - int relidx = -1; - FOR (i,0,cnt) { - rects[i].idx = i; - rects[i].r = monitor_with_index(i)->rect; - if (monitor_with_index(i) == m) relidx = i; - } - HSAssert(relidx >= 0); - int result = find_rectangle_in_direction(rects, cnt, relidx, dir); - g_free(rects); - return result; -} - -HSMonitor* string_to_monitor(char* string) { - int idx = string_to_monitor_index(string); - return monitor_with_index(idx); -} - -static int monitor_attr_index(void* data) { - HSMonitor* m = (HSMonitor*) data; - return monitor_index_of(m); -} - -static void monitor_attr_tag(void* data, GString* output) { - HSMonitor* m = (HSMonitor*) data; - g_string_append(output, m->tag->display_name->str); -} - -static void monitor_foreach(void (*action)(HSMonitor*)) { - for (int i = 0; i < g_monitors->len; i++) { - HSMonitor* m = monitor_with_index(i); - action(m); - } -} - -static void monitor_unlink_id_object(HSMonitor* m) { - hsobject_unlink(g_monitor_object, &m->object); -} - -static void monitor_link_id_object(HSMonitor* m) { - GString* index_str = g_string_new(""); - int index = monitor_index_of(m); - g_string_printf(index_str, "%d", index); - hsobject_link(g_monitor_object, &m->object, index_str->str); - g_string_free(index_str, true); -} - -HSMonitor* add_monitor(Rectangle rect, HSTag* tag, char* name) { - assert(tag != NULL); - HSMonitor* m = g_new0(HSMonitor, 1); - hsobject_init(&m->object); - if (name) { - hsobject_link(g_monitor_by_name_object, &m->object, name); - } - m->rect = rect; - m->tag = tag; - m->tag_previous = tag; - m->name = (name ? g_string_new(name) : NULL); - m->display_name = g_string_new(name ? name : ""); - m->mouse.x = 0; - m->mouse.y = 0; - m->dirty = true; - m->slice = slice_create_monitor(m); - m->stacking_window = XCreateSimpleWindow(g_display, g_root, - 42, 42, 42, 42, 1, 0, 0); - - m->object.data = m; - HSAttribute attributes[] = { - ATTRIBUTE_STRING( "name", m->display_name,ATTR_READ_ONLY ), - ATTRIBUTE_CUSTOM_INT("index", monitor_attr_index,ATTR_READ_ONLY ), - ATTRIBUTE_CUSTOM( "tag", monitor_attr_tag,ATTR_READ_ONLY ), - ATTRIBUTE_BOOL( "lock_tag", m->lock_tag, ATTR_READ_ONLY ), - ATTRIBUTE_LAST, - }; - hsobject_set_attributes(&m->object, attributes); - - stack_insert_slice(g_monitor_stack, m->slice); - g_array_append_val(g_monitors, m); - monitor_link_id_object(m); - - return g_array_index(g_monitors, HSMonitor*, g_monitors->len-1); -} - -int add_monitor_command(int argc, char** argv, GString* output) { - // usage: add_monitor RECTANGLE [TAG [NAME]] - if (argc < 2) { - return HERBST_NEED_MORE_ARGS; - } - Rectangle rect = parse_rectangle(argv[1]); - HSTag* tag = NULL; - char* name = NULL; - if (argc == 2 || !strcmp("", argv[2])) { - tag = find_unused_tag(); - if (!tag) { - g_string_append_printf(output, - "%s: There are not enough free tags\n", argv[0]); - return HERBST_TAG_IN_USE; - } - } - else { - tag = find_tag(argv[2]); - if (!tag) { - g_string_append_printf(output, - "%s: The tag \"%s\" does not exist\n", argv[0], argv[2]); - return HERBST_INVALID_ARGUMENT; - } - } - if (find_monitor_with_tag(tag)) { - g_string_append_printf(output, - "%s: The tag \"%s\" is already viewed on a monitor\n", argv[0], argv[2]); - return HERBST_TAG_IN_USE; - } - if (argc > 3) { - name = argv[3]; - if (isdigit(name[0])) { - g_string_append_printf(output, - "%s: The monitor name may not start with a number\n", argv[0]); - return HERBST_INVALID_ARGUMENT; - } - if (!strcmp("", name)) { - g_string_append_printf(output, - "%s: An empty monitor name is not permitted\n", argv[0]); - return HERBST_INVALID_ARGUMENT; - } - if (find_monitor_by_name(name)) { - g_string_append_printf(output, - "%s: A monitor with the same name already exists\n", argv[0]); - return HERBST_INVALID_ARGUMENT; - } - } - HSMonitor* monitor = add_monitor(rect, tag, name); - monitor_apply_layout(monitor); - frame_show_recursive(tag->frame); - emit_tag_changed(tag, g_monitors->len - 1); - drop_enternotify_events(); - return 0; -} - -int remove_monitor_command(int argc, char** argv, GString* output) { - // usage: remove_monitor INDEX - if (argc < 2) { - return HERBST_NEED_MORE_ARGS; - } - int index = string_to_monitor_index(argv[1]); - if (index == -1) { - g_string_append_printf(output, - "%s: Monitor \"%s\" not found!\n", argv[0], argv[1]); - return HERBST_INVALID_ARGUMENT; - } - int ret = remove_monitor(index); - if (ret == HERBST_INVALID_ARGUMENT) { - g_string_append_printf(output, - "%s: Index needs to be between 0 and %d\n", argv[0], g_monitors->len - 1); - } else if (ret == HERBST_FORBIDDEN) { - g_string_append_printf(output, - "%s: Can't remove the last monitor\n", argv[0]); - } - monitor_update_focus_objects(); - return ret; -} - -int remove_monitor(int index) { - if (index < 0 || index >= g_monitors->len) { - return HERBST_INVALID_ARGUMENT; - } - if (g_monitors->len <= 1) { - return HERBST_FORBIDDEN; - } - HSMonitor* monitor = monitor_with_index(index); - // adjust selection - if (g_cur_monitor > index) { - // same monitor shall be selected after remove - g_cur_monitor--; - } - assert(monitor->tag); - assert(monitor->tag->frame); - // hide clients - frame_hide_recursive(monitor->tag->frame); - // remove from monitor stack - stack_remove_slice(g_monitor_stack, monitor->slice); - slice_destroy(monitor->slice); - XDestroyWindow(g_display, monitor->stacking_window); - hsobject_unlink(g_monitor_by_name_object, &monitor->object); - hsobject_free(&monitor->object); - // and remove monitor completely - if (monitor->name) { - g_string_free(monitor->name, true); - } - g_string_free(monitor->display_name, true); - monitor_foreach(monitor_unlink_id_object); - g_array_remove_index(g_monitors, index); - g_free(monitor); - monitor_foreach(monitor_link_id_object); - if (g_cur_monitor >= g_monitors->len) { - g_cur_monitor--; - // if selection has changed, then relayout focused monitor - monitor_apply_layout(get_current_monitor()); - monitor_update_focus_objects(); - // also announce the new selection - ewmh_update_current_desktop(); - emit_tag_changed(get_current_monitor()->tag, g_cur_monitor); - } - return 0; -} - -int move_monitor_command(int argc, char** argv, GString* output) { - // usage: move_monitor INDEX RECT [PADUP [PADRIGHT [PADDOWN [PADLEFT]]]] - // moves monitor with number to RECT - if (argc < 3) { - return HERBST_NEED_MORE_ARGS; - } - HSMonitor* monitor = string_to_monitor(argv[1]); - if (monitor == NULL) { - g_string_append_printf(output, - "%s: Monitor \"%s\" not found!\n", argv[0], argv[1]); - return HERBST_INVALID_ARGUMENT; - } - Rectangle rect = parse_rectangle(argv[2]); - if (rect.width < WINDOW_MIN_WIDTH || rect.height < WINDOW_MIN_HEIGHT) { - g_string_append_printf(output, - "%s: Rectangle is too small\n", argv[0]); - return HERBST_INVALID_ARGUMENT; - } - // else: just move it: - monitor->rect = rect; - if (argc > 3 && argv[3][0] != '\0') monitor->pad_up = atoi(argv[3]); - if (argc > 4 && argv[4][0] != '\0') monitor->pad_right = atoi(argv[4]); - if (argc > 5 && argv[5][0] != '\0') monitor->pad_down = atoi(argv[5]); - if (argc > 6 && argv[6][0] != '\0') monitor->pad_left = atoi(argv[6]); - monitor_apply_layout(monitor); - return 0; -} - -int rename_monitor_command(int argc, char** argv, GString* output) { - if (argc < 3) { - return HERBST_NEED_MORE_ARGS; - } - HSMonitor* mon = string_to_monitor(argv[1]); - if (mon == NULL) { - g_string_append_printf(output, - "%s: Monitor \"%s\" not found!\n", argv[0], argv[1]); - return HERBST_INVALID_ARGUMENT; - } - if (isdigit(argv[2][0])) { - g_string_append_printf(output, - "%s: The monitor name may not start with a number\n", argv[0]); - return HERBST_INVALID_ARGUMENT; - } else if (!strcmp("", argv[2])) { - // empty name -> clear name - if (mon->name != NULL) { - hsobject_unlink_by_name(g_monitor_by_name_object, mon->name->str); - g_string_free(mon->name, true); - mon->name = NULL; - } - return 0; - } - if (find_monitor_by_name(argv[2])) { - g_string_append_printf(output, - "%s: A monitor with the same name already exists\n", argv[0]); - return HERBST_INVALID_ARGUMENT; - } - g_string_assign(mon->display_name, argv[2]); - if (mon->name == NULL) { - // not named before - GString* name = g_string_new(argv[2]); - mon->name = name; - } else { - hsobject_unlink_by_name(g_monitor_by_name_object, mon->name->str); - // already named - g_string_assign(mon->name, argv[2]); - } - hsobject_link(g_monitor_by_name_object, &mon->object, mon->name->str); - return 0; -} - -int monitor_rect_command(int argc, char** argv, GString* output) { - // usage: monitor_rect [[-p] INDEX] - char* monitor_str = NULL; - HSMonitor* m = NULL; - bool with_pad = false; - - // if monitor is supplied - if (argc > 1) { - monitor_str = argv[1]; - } - // if -p is supplied - if (argc > 2) { - monitor_str = argv[2]; - if (!strcmp("-p", argv[1])) { - with_pad = true; - } else { - g_string_append_printf(output, - "%s: Invalid argument \"%s\"\n", argv[0], argv[1]); - return HERBST_INVALID_ARGUMENT; - } - } - // if an index is set - if (monitor_str) { - m = string_to_monitor(monitor_str); - if (m == NULL) { - g_string_append_printf(output, - "%s: Monitor \"%s\" not found!\n", argv[0], monitor_str); - return HERBST_INVALID_ARGUMENT; - } - } else { - m = get_current_monitor(); - } - Rectangle rect = m->rect; - if (with_pad) { - rect.x += m->pad_left; - rect.width -= m->pad_left + m->pad_right; - rect.y += m->pad_up; - rect.height -= m->pad_up + m->pad_down; - } - g_string_append_printf(output, "%d %d %d %d", - rect.x, rect.y, rect.width, rect.height); - return 0; -} - -int monitor_set_pad_command(int argc, char** argv, GString* output) { - if (argc < 2) { - return HERBST_NEED_MORE_ARGS; - } - HSMonitor* monitor = string_to_monitor(argv[1]); - if (monitor == NULL) { - g_string_append_printf(output, - "%s: Monitor \"%s\" not found!\n", argv[0], argv[1]); - return HERBST_INVALID_ARGUMENT; - } - if (argc > 2 && argv[2][0] != '\0') monitor->pad_up = atoi(argv[2]); - if (argc > 3 && argv[3][0] != '\0') monitor->pad_right = atoi(argv[3]); - if (argc > 4 && argv[4][0] != '\0') monitor->pad_down = atoi(argv[4]); - if (argc > 5 && argv[5][0] != '\0') monitor->pad_left = atoi(argv[5]); - monitor_apply_layout(monitor); - return 0; -} - -HSMonitor* find_monitor_with_tag(HSTag* tag) { - int i; - for (i = 0; i < g_monitors->len; i++) { - HSMonitor* m = monitor_with_index(i); - if (m->tag == tag) { - return m; - } - } - return NULL; -} - -void ensure_monitors_are_available() { - if (g_monitors->len > 0) { - // nothing to do - return; - } - // add monitor if necessary - Rectangle rect = { - .x = 0, .y = 0, - .width = DisplayWidth(g_display, DefaultScreen(g_display)), - .height = DisplayHeight(g_display, DefaultScreen(g_display)), - }; - ensure_tags_are_available(); - // add monitor with first tag - HSMonitor* m = add_monitor(rect, g_array_index(g_tags, HSTag*, 0), NULL); - g_cur_monitor = 0; - g_cur_frame = m->tag->frame; - - monitor_update_focus_objects(); -} - -HSMonitor* monitor_with_frame(HSFrame* frame) { - // find toplevel Frame - while (frame->parent) { - frame = frame->parent; - } - HSTag* tag = find_tag_with_toplevel_frame(frame); - return find_monitor_with_tag(tag); -} - -HSMonitor* get_current_monitor() { - return g_array_index(g_monitors, HSMonitor*, g_cur_monitor); -} - -int monitor_count() { - return g_monitors->len; -} - -void all_monitors_apply_layout() { - monitor_foreach(monitor_apply_layout); -} - -int monitor_set_tag(HSMonitor* monitor, HSTag* tag) { - HSMonitor* other = find_monitor_with_tag(tag); - if (monitor == other) { - // nothing to do - return 0; - } - if (monitor->lock_tag) { - // If the monitor tag is locked, do not change the tag - if (other != NULL) { - // but if the tag is already visible, change to the - // displaying monitor - monitor_focus_by_index(monitor_index_of(other)); - return 0; - } - return 1; - } - if (other != NULL) { - if (*g_swap_monitors_to_get_tag) { - if (other->lock_tag) { - // the monitor we want to steal the tag from is - // locked. focus that monitor instead - monitor_focus_by_index(monitor_index_of(other)); - return 0; - } - // swap tags - other->tag = monitor->tag; - monitor->tag = tag; - // reset focus - frame_focus_recursive(tag->frame); - /* TODO: find the best order of restacking and layouting */ - monitor_restack(other); - monitor_restack(monitor); - monitor_apply_layout(other); - monitor_apply_layout(monitor); - // discard enternotify-events - drop_enternotify_events(); - monitor_update_focus_objects(); - ewmh_update_current_desktop(); - emit_tag_changed(other->tag, monitor_index_of(other)); - emit_tag_changed(tag, g_cur_monitor); - } else { - // if we are not allowed to steal the tag, then just focus the - // other monitor - monitor_focus_by_index(monitor_index_of(other)); - } - return 0; - } - HSTag* old_tag = monitor->tag; - // save old tag - monitor->tag_previous = old_tag; - // 1. show new tag - monitor->tag = tag; - // first reset focus and arrange windows - frame_focus_recursive(tag->frame); - monitor_restack(monitor); - monitor->lock_frames = true; - monitor_apply_layout(monitor); - monitor->lock_frames = false; - // then show them (should reduce flicker) - frame_show_recursive(tag->frame); - if (!monitor->tag->floating) { - frame_update_frame_window_visibility(monitor->tag->frame); - } - // 2. hide old tag - frame_hide_recursive(old_tag->frame); - // focus window just has been shown - // focus again to give input focus - frame_focus_recursive(tag->frame); - // discard enternotify-events - drop_enternotify_events(); - monitor_update_focus_objects(); - ewmh_update_current_desktop(); - emit_tag_changed(tag, g_cur_monitor); - return 0; -} - -int monitor_set_tag_command(int argc, char** argv, GString* output) { - if (argc < 2) { - return HERBST_NEED_MORE_ARGS; - } - HSMonitor* monitor = get_current_monitor(); - HSTag* tag = find_tag(argv[1]); - if (monitor && tag) { - int ret = monitor_set_tag(monitor, tag); - if (ret != 0) { - g_string_append_printf(output, - "%s: Could not change tag", argv[0]); - if (monitor->lock_tag) { - g_string_append_printf(output, - " (monitor %d is locked)", monitor_index_of(monitor)); - } - g_string_append_printf(output, "\n"); - } - return ret; - } else { - g_string_append_printf(output, - "%s: Invalid tag \"%s\"\n", argv[0], argv[1]); - return HERBST_INVALID_ARGUMENT; - } -} - -int monitor_set_tag_by_index_command(int argc, char** argv, GString* output) { - if (argc < 2) { - return HERBST_NEED_MORE_ARGS; - } - bool skip_visible = false; - if (argc >= 3 && !strcmp(argv[2], "--skip-visible")) { - skip_visible = true; - } - HSTag* tag = get_tag_by_index_str(argv[1], skip_visible); - if (!tag) { - g_string_append_printf(output, - "%s: Invalid index \"%s\"\n", argv[0], argv[1]); - return HERBST_INVALID_ARGUMENT; - } - int ret = monitor_set_tag(get_current_monitor(), tag); - if (ret != 0) { - g_string_append_printf(output, - "%s: Could not change tag (maybe monitor is locked?)\n", argv[0]); - } - return ret; -} - -int monitor_set_previous_tag_command(int argc, char** argv, GString* output) { - if (argc < 1) { - return HERBST_NEED_MORE_ARGS; - } - HSMonitor* monitor = get_current_monitor(); - HSTag* tag = monitor->tag_previous; - if (monitor && tag) { - int ret = monitor_set_tag(monitor, tag); - if (ret != 0) { - g_string_append_printf(output, - "%s: Could not change tag (maybe monitor is locked?)\n", argv[0]); - } - return ret; - } else { - g_string_append_printf(output, - "%s: Invalid monitor or tag\n", argv[0]); - return HERBST_INVALID_ARGUMENT; - } -} - -int monitor_focus_command(int argc, char** argv, GString* output) { - if (argc < 2) { - return HERBST_NEED_MORE_ARGS; - } - int new_selection = string_to_monitor_index(argv[1]); - if (new_selection < 0) { - g_string_append_printf(output, - "%s: Monitor \"%s\" not found!\n", argv[0], argv[1]); - return HERBST_INVALID_ARGUMENT; - } - // really change selection - monitor_focus_by_index(new_selection); - return 0; -} - -int monitor_cycle_command(int argc, char** argv) { - int delta = 1; - int count = g_monitors->len; - if (argc >= 2) { - delta = atoi(argv[1]); - } - int new_selection = g_cur_monitor + delta; - // fix range of index - new_selection %= count; - new_selection += count; - new_selection %= count; - // really change selection - monitor_focus_by_index(new_selection); - return 0; -} - -int monitor_index_of(HSMonitor* monitor) { - for (int i = 0; i < g_monitors->len; i++) { - if (monitor_with_index(i) == monitor) { - return i; - } - } - return -1; -} - -void monitor_focus_by_index(int new_selection) { - new_selection = CLAMP(new_selection, 0, g_monitors->len - 1); - HSMonitor* old = get_current_monitor(); - HSMonitor* monitor = monitor_with_index(new_selection); - if (old == monitor) { - // nothing to do - return; - } - // change selection globals - assert(monitor->tag); - assert(monitor->tag->frame); - g_cur_monitor = new_selection; - frame_focus_recursive(monitor->tag->frame); - // repaint monitors - monitor_apply_layout(old); - monitor_apply_layout(monitor); - int rx, ry; - { - // save old mouse position - Window win, child; - int wx, wy; - unsigned int mask; - if (True == XQueryPointer(g_display, g_root, &win, &child, - &rx, &ry, &wx, &wy, &mask)) { - old->mouse.x = rx - old->rect.x; - old->mouse.y = ry - old->rect.y; - old->mouse.x = CLAMP(old->mouse.x, 0, old->rect.width-1); - old->mouse.y = CLAMP(old->mouse.y, 0, old->rect.height-1); - } - } - // restore position of new monitor - // but only if mouse pointer is not already on new monitor - int new_x, new_y; - if ((monitor->rect.x <= rx) && (rx < monitor->rect.x + monitor->rect.width) - && (monitor->rect.y <= ry) && (ry < monitor->rect.y + monitor->rect.height)) { - // mouse already is on new monitor - } else { - // If the mouse is located in a gap indicated by - // mouse_recenter_gap at the outer border of the monitor, - // recenter the mouse. - if (min(monitor->mouse.x, abs(monitor->mouse.x - monitor->rect.width)) < *g_mouse_recenter_gap - || min(monitor->mouse.y, abs(monitor->mouse.y - monitor->rect.height)) < *g_mouse_recenter_gap) { - monitor->mouse.x = monitor->rect.width / 2; - monitor->mouse.y = monitor->rect.height / 2; - } - new_x = monitor->rect.x + monitor->mouse.x; - new_y = monitor->rect.y + monitor->mouse.y; - XWarpPointer(g_display, None, g_root, 0, 0, 0, 0, new_x, new_y); - // discard all mouse events caused by this pointer movage from the - // event queue, so the focus really stays in the last focused window on - // this monitor and doesn't jump to the window hovered by the mouse - drop_enternotify_events(); - } - // update objects - monitor_update_focus_objects(); - // emit hooks - ewmh_update_current_desktop(); - emit_tag_changed(monitor->tag, new_selection); -} - -void monitor_update_focus_objects() { - hsobject_link(g_monitor_object, &get_current_monitor()->object, "focus"); - tag_update_focus_objects(); -} - -int monitor_get_relative_x(HSMonitor* m, int x_root) { - return x_root - m->rect.x - m->pad_left; -} - -int monitor_get_relative_y(HSMonitor* m, int y_root) { - return y_root - m->rect.y - m->pad_up; -} - -HSMonitor* monitor_with_coordinate(int x, int y) { - int i; - for (i = 0; i < g_monitors->len; i++) { - HSMonitor* m = monitor_with_index(i); - if (m->rect.x + m->pad_left <= x - && m->rect.x + m->rect.width - m->pad_right > x - && m->rect.y + m->pad_up <= y - && m->rect.y + m->rect.height - m->pad_down > y) { - return m; - } - } - return NULL; -} - -HSMonitor* monitor_with_index(int index) { - if (index < 0 || index >= g_monitors->len) { - return NULL; - } - return g_array_index(g_monitors, HSMonitor*, index); -} - -int monitors_lock_command(int argc, char** argv) { - monitors_lock(); - return 0; -} - -void monitors_lock() { - // lock-number must never be negative - // ensure that lock value is valid - if (*g_monitors_locked < 0) { - *g_monitors_locked = 0; - } - // increase lock => it is definitely > 0, i.e. locked - (*g_monitors_locked)++; - monitors_lock_changed(); -} - -int monitors_unlock_command(int argc, char** argv) { - monitors_unlock(); - return 0; -} - -void monitors_unlock() { - // lock-number must never be lower than 1 if unlocking - // so: ensure that lock value is valid - if (*g_monitors_locked < 1) { - *g_monitors_locked = 1; - } - // decrease lock => unlock - (*g_monitors_locked)--; - monitors_lock_changed(); -} - -void monitors_lock_changed() { - if (*g_monitors_locked < 0) { - *g_monitors_locked = 0; - HSDebug("fixing invalid monitors_locked value to 0\n"); - } - if (!*g_monitors_locked) { - // if not locked anymore, then repaint all the dirty monitors - for (int i = 0; i < g_monitors->len; i++) { - HSMonitor* m = monitor_with_index(i); - if (m->dirty) { - monitor_apply_layout(m); - } - } - } -} - -int monitor_lock_tag_command(int argc, char** argv, GString* output) { - char* cmd_name = argv[0]; - (void)SHIFT(argc, argv); - HSMonitor *monitor; - if (argc >= 1) { - monitor = string_to_monitor(argv[0]); - if (monitor == NULL) { - g_string_append_printf(output, - "%s: Monitor \"%s\" not found!\n", cmd_name, argv[0]); - return HERBST_INVALID_ARGUMENT; - } - } else { - monitor = get_current_monitor(); - } - monitor->lock_tag = true; - return 0; -} - -int monitor_unlock_tag_command(int argc, char** argv, GString* output) { - char* cmd_name = argv[0]; - (void)SHIFT(argc, argv); - HSMonitor *monitor; - if (argc >= 1) { - monitor = string_to_monitor(argv[0]); - if (monitor == NULL) { - g_string_append_printf(output, - "%s: Monitor \"%s\" not found!\n", cmd_name, argv[0]); - return HERBST_INVALID_ARGUMENT; - } - } else { - monitor = get_current_monitor(); - } - monitor->lock_tag = false; - return 0; -} - -// monitor detection using xinerama (if available) -#ifdef XINERAMA -// inspired by dwm's isuniquegeom() -static bool geom_unique(XineramaScreenInfo *unique, size_t n, XineramaScreenInfo *info) { - while (n--) - if (unique[n].x_org == info->x_org && unique[n].y_org == info->y_org - && unique[n].width == info->width && unique[n].height == info->height) - return false; - return true; -} - -// inspired by dwm's updategeom() -bool detect_monitors_xinerama(Rectangle** ret_rects, size_t* ret_count) { - int i, j, n; - XineramaScreenInfo *info, *unique; - Rectangle *monitors; - - if (!XineramaIsActive(g_display)) { - return false; - } - info = XineramaQueryScreens(g_display, &n); - unique = g_new(XineramaScreenInfo, n); - /* only consider unique geometries as separate screens */ - for (i = 0, j = 0; i < n; i++) { - if (geom_unique(unique, j, &info[i])) - { - memcpy(&unique[j++], &info[i], sizeof(XineramaScreenInfo)); - } - } - XFree(info); - n = j; - - monitors = g_new(Rectangle, n); - for (i = 0; i < n; i++) { - monitors[i].x = unique[i].x_org; - monitors[i].y = unique[i].y_org; - monitors[i].width = unique[i].width; - monitors[i].height = unique[i].height; - } - *ret_count = n; - *ret_rects = monitors; - g_free(unique); - return true; -} -#else /* XINERAMA */ - -bool detect_monitors_xinerama(Rectangle** ret_rects, size_t* ret_count) { - return false; -} - -#endif /* XINERAMA */ - -// monitor detection that always works: one monitor across the entire screen -bool detect_monitors_simple(Rectangle** ret_rects, size_t* ret_count) { - XWindowAttributes attributes; - XGetWindowAttributes(g_display, g_root, &attributes); - - *ret_count = 1; - *ret_rects = g_new0(Rectangle, 1); - (*ret_rects)->x = 0; - (*ret_rects)->y = 0; - (*ret_rects)->width = attributes.width; - (*ret_rects)->height = attributes.height; - return true; -} - -bool detect_monitors_debug_example(Rectangle** ret_rects, size_t* ret_count) { - *ret_count = 2; - *ret_rects = g_new0(Rectangle, 2); - (*ret_rects)[0].x = 0; - (*ret_rects)[0].y = 0; - (*ret_rects)[0].width = g_screen_width * 2 / 3; - (*ret_rects)[0].height = g_screen_height * 2 / 3; - (*ret_rects)[1].x = g_screen_width / 3; - (*ret_rects)[1].y = g_screen_height / 3; - (*ret_rects)[1].width = g_screen_width * 2 / 3; - (*ret_rects)[1].height = g_screen_height * 2 / 3; - return true; -} - - -int detect_monitors_command(int argc, char **argv, GString* output) { - MonitorDetection detect[] = { - detect_monitors_xinerama, - detect_monitors_simple, - detect_monitors_debug_example, // move up for debugging - }; - Rectangle* monitors = NULL; - size_t count = 0; - // search for a working monitor detection - // at least the simple detection must work - for (int i = 0; i < LENGTH(detect); i++) { - if (detect[i](&monitors, &count)) { - break; - } - } - assert(count && monitors); - bool list_only = false; - bool disjoin = true; - //bool drop_small = true; - FOR (i,1,argc) { - if (!strcmp(argv[i], "-l")) list_only = true; - else if (!strcmp(argv[i], "--list")) list_only = true; - else if (!strcmp(argv[i], "--no-disjoin")) disjoin = false; - // TOOD: - // else if (!strcmp(argv[i], "--keep-small")) drop_small = false; - else { - g_string_append_printf(output, - "detect_monitors: unknown flag \"%s\"\n", argv[i]); - return HERBST_INVALID_ARGUMENT; - } - } - - int ret = 0; - if (list_only) { - FOR (i,0,count) { - g_string_append_printf(output, "%dx%d%+d%+d\n", - monitors[i].width, monitors[i].height, - monitors[i].x, monitors[i].y); - } - } else { - // possibly disjoin them - if (disjoin) { - RectList* rl = disjoin_rects(monitors, count); - count = rectlist_length(rl); - monitors = g_renew(Rectangle, monitors, count); - RectList* cur = rl; - FOR (i,0,count) { - monitors[i] = cur->rect; - cur = cur->next; - } - } - // apply it - ret = set_monitor_rects(monitors, count); - if (ret == HERBST_TAG_IN_USE && output != NULL) { - g_string_append_printf(output, - "%s: There are not enough free tags\n", argv[0]); - } - } - g_free(monitors); - return ret; -} - -int monitor_stack_window_count(bool real_clients) { - return stack_window_count(g_monitor_stack, real_clients); -} - -void monitor_stack_to_window_buf(Window* buf, int len, bool real_clients, - int* remain_len) { - stack_to_window_buf(g_monitor_stack, buf, len, real_clients, remain_len); -} - -HSStack* get_monitor_stack() { - return g_monitor_stack; -} - -int monitor_raise_command(int argc, char** argv, GString* output) { - char* cmd_name = argv[0]; - (void)SHIFT(argc, argv); - HSMonitor* monitor; - if (argc >= 1) { - monitor = string_to_monitor(argv[0]); - if (monitor == NULL) { - g_string_append_printf(output, - "%s: Monitor \"%s\" not found!\n", cmd_name, argv[0]); - return HERBST_INVALID_ARGUMENT; - } - } else { - monitor = get_current_monitor(); - } - stack_raise_slide(g_monitor_stack, monitor->slice); - return 0; -} - -void monitor_restack(HSMonitor* monitor) { - int count = 1 + stack_window_count(monitor->tag->stack, false); - Window* buf = g_new(Window, count); - buf[0] = monitor->stacking_window; - stack_to_window_buf(monitor->tag->stack, buf + 1, count - 1, false, NULL); - /* remove a focused fullscreen client */ - HSClient* client = frame_focused_client(monitor->tag->frame); - if (client && client->fullscreen) { - XRaiseWindow(g_display, client->dec.decwin); - int idx = array_find(buf, count, sizeof(*buf), &client->dec.decwin); - assert(idx >= 0); - count--; - memmove(buf + idx, buf + idx + 1, sizeof(*buf) * (count - idx)); - } - XRestackWindows(g_display, buf, count); - g_free(buf); -} - -int shift_to_monitor(int argc, char** argv, GString* output) { - if (argc <= 1) { - return HERBST_NEED_MORE_ARGS; - } - char* monitor_str = argv[1]; - HSMonitor* monitor = string_to_monitor(monitor_str); - if (!monitor) { - g_string_append_printf(output, - "%s: Invalid monitor\n", monitor_str); - return HERBST_INVALID_ARGUMENT; - } - tag_move_focused_client(monitor->tag); - return 0; -} - -void all_monitors_replace_previous_tag(HSTag *old, HSTag *new) { - int i; - for (i = 0; i < g_monitors->len; i++) { - HSMonitor* m = monitor_with_index(i); - if (m->tag_previous == old) { - m->tag_previous = new; - } - } -} - -void drop_enternotify_events() { - XEvent ev; - XSync(g_display, False); - while(XCheckMaskEvent(g_display, EnterWindowMask, &ev)); -} - -Rectangle monitor_get_floating_area(HSMonitor* m) { - Rectangle r = m->rect; - r.x += m->pad_left; - r.width -= m->pad_left + m->pad_right; - r.y += m->pad_up; - r.height -= m->pad_up + m->pad_down; - return r; -} - diff -Nru herbstluftwm-0.6.2/src/monitor.cpp herbstluftwm-0.7.0/src/monitor.cpp --- herbstluftwm-0.6.2/src/monitor.cpp 1970-01-01 00:00:00.000000000 +0000 +++ herbstluftwm-0.7.0/src/monitor.cpp 2015-10-14 13:27:40.000000000 +0000 @@ -0,0 +1,1389 @@ +/** Copyright 2011-2013 Thorsten Wißmann. All rights reserved. + * + * This software is licensed under the "Simplified BSD License". + * See LICENSE for details */ + +#include +#include +#include +#include +#include +#ifdef XINERAMA +#include +#endif /* XINERAMA */ + +#include "globals.h" +#include "ipc-protocol.h" +#include "utils.h" +#include "mouse.h" +#include "hook.h" +#include "layout.h" +#include "tag.h" +#include "ewmh.h" +#include "monitor.h" +#include "settings.h" +#include "stack.h" +#include "clientlist.h" +#include "desktopwindow.h" + +// module internals: +static int g_cur_monitor; +static int* g_monitors_locked; +static int* g_swap_monitors_to_get_tag; +static int* g_smart_frame_surroundings; +static int* g_mouse_recenter_gap; +static HSStack* g_monitor_stack; +static GArray* g_monitors; // Array of HSMonitor* +static HSObject* g_monitor_object; +static HSObject* g_monitor_by_name_object; + +typedef struct RectList { + Rectangle rect; + struct RectList* next; +} RectList; + +static RectList* reclist_insert_disjoint(RectList* head, RectList* mt); +static RectList* disjoin_rects(Rectangle* buf, size_t count); + +void monitor_init() { + g_monitors_locked = &(settings_find("monitors_locked")->value.i); + g_cur_monitor = 0; + g_monitors = g_array_new(false, false, sizeof(HSMonitor*)); + g_swap_monitors_to_get_tag = &(settings_find("swap_monitors_to_get_tag")->value.i); + g_smart_frame_surroundings = &(settings_find("smart_frame_surroundings")->value.i); + g_mouse_recenter_gap = &(settings_find("mouse_recenter_gap")->value.i); + g_monitor_stack = stack_create(); + g_monitor_object = hsobject_create_and_link(hsobject_root(), "monitors"); + HSAttribute attributes[] = { + ATTRIBUTE("count", g_monitors->len, ATTR_READ_ONLY), + ATTRIBUTE_LAST, + }; + hsobject_set_attributes(g_monitor_object, attributes); + g_monitor_by_name_object = hsobject_create_and_link(g_monitor_object, "by-name"); +} + +void monitor_destroy() { + for (unsigned int i = 0; i < g_monitors->len; i++) { + HSMonitor* m = monitor_with_index(i); + stack_remove_slice(g_monitor_stack, m->slice); + slice_destroy(m->slice); + hsobject_free(&m->object); + if (m->name) { + g_string_free(m->name, true); + } + g_string_free(m->display_name, true); + g_free(m); + } + hsobject_unlink_and_destroy(g_monitor_object, g_monitor_by_name_object); + hsobject_unlink_and_destroy(hsobject_root(), g_monitor_object); + stack_destroy(g_monitor_stack); + g_array_free(g_monitors, true); +} + +void monitor_apply_layout(HSMonitor* monitor) { + if (monitor) { + if (*g_monitors_locked) { + monitor->dirty = true; + return; + } + monitor->dirty = false; + Rectangle rect = monitor->rect; + // apply pad + rect.x += monitor->pad_left; + rect.width -= (monitor->pad_left + monitor->pad_right); + rect.y += monitor->pad_up; + rect.height -= (monitor->pad_up + monitor->pad_down); + if (!*g_smart_frame_surroundings || monitor->tag->frame->type == TYPE_FRAMES ) { + // apply frame gap + rect.x += *g_frame_gap; + rect.y += *g_frame_gap; + rect.height -= *g_frame_gap; + rect.width -= *g_frame_gap; + } + monitor_restack(monitor); + if (get_current_monitor() == monitor) { + frame_focus_recursive(monitor->tag->frame); + } + if (monitor->tag->floating) { + frame_apply_floating_layout(monitor->tag->frame, monitor); + } else { + frame_apply_layout(monitor->tag->frame, rect); + if (!monitor->lock_frames && !monitor->tag->floating) { + frame_update_frame_window_visibility(monitor->tag->frame); + } + } + // remove all enternotify-events from the event queue that were + // generated while arranging the clients on this monitor + drop_enternotify_events(); + } +} + +int list_monitors(int argc, char** argv, GString* output) { + (void)argc; + (void)argv; + GString* monitor_name = g_string_new(""); + for (unsigned int i = 0; i < g_monitors->len; i++) { + HSMonitor* monitor = monitor_with_index(i); + if (monitor->name != NULL ) { + g_string_printf(monitor_name, ", named \"%s\"", + monitor->name->str); + } else { + g_string_truncate(monitor_name, 0); + } + g_string_append_printf(output, "%d: %dx%d%+d%+d with tag \"%s\"%s%s%s\n", + i, + monitor->rect.width, monitor->rect.height, + monitor->rect.x, monitor->rect.y, + monitor->tag ? monitor->tag->name->str : "???", + monitor_name->str, + ((unsigned int) g_cur_monitor == i) ? " [FOCUS]" : "", + monitor->lock_tag ? " [LOCKED]" : ""); + } + g_string_free(monitor_name, true); + return 0; +} + +int list_padding(int argc, char** argv, GString* output) { + HSMonitor* monitor; + if (argc < 2) { + monitor = get_current_monitor(); + } else { + monitor = string_to_monitor(argv[1]); + if (monitor == NULL) { + g_string_append_printf(output, + "%s: Monitor \"%s\" not found!\n", argv[0], argv[1]); + return HERBST_INVALID_ARGUMENT; + } + } + g_string_append_printf(output, "%d %d %d %d\n", + monitor->pad_up, + monitor->pad_right, + monitor->pad_down, + monitor->pad_left); + return 0; +} + +static bool rects_intersect(RectList* m1, RectList* m2) { + Rectangle *r1 = &m1->rect, *r2 = &m2->rect; + bool is = TRUE; + is = is && intervals_intersect(r1->x, r1->x + r1->width, + r2->x, r2->x + r2->width); + is = is && intervals_intersect(r1->y, r1->y + r1->height, + r2->y, r2->y + r2->height); + return is; +} + +static Rectangle intersection_area(RectList* m1, RectList* m2) { + Rectangle r; // intersection between m1->rect and m2->rect + r.x = MAX(m1->rect.x, m2->rect.x); + r.y = MAX(m1->rect.y, m2->rect.y); + // the bottom right coordinates of the rects + int br1_x = m1->rect.x + m1->rect.width; + int br1_y = m1->rect.y + m1->rect.height; + int br2_x = m2->rect.x + m2->rect.width; + int br2_y = m2->rect.y + m2->rect.height; + r.width = MIN(br1_x, br2_x) - r.x; + r.height = MIN(br1_y, br2_y) - r.y; + return r; +} + +static RectList* rectlist_create_simple(int x1, int y1, int x2, int y2) { + if (x1 >= x2 || y1 >= y2) { + return NULL; + } + RectList* r = g_new0(RectList, 1); + r->rect.x = x1; + r->rect.y = y1; + r->rect.width = x2 - x1; + r->rect.height = y2 - y1; + r->next = NULL; + return r; +} + +static RectList* insert_rect_border(RectList* head, + Rectangle large, Rectangle center) +{ + // given a large rectangle and a center which guaranteed to be a subset of + // the large rect, the task is to split "large" into pieces and insert them + // like this: + // + // +------- large ---------+ + // | top | + // |------+--------+-------| + // | left | center | right | + // |------+--------+-------| + // | bottom | + // +-----------------------+ + RectList *top, *left, *right, *bottom; + // coordinates of the bottom right corner of large + int br_x = large.x + large.width, br_y = large.y + large.height; + RectList* (*r)(int,int,int,int) = rectlist_create_simple; + top = r(large.x, large.y, large.x + large.width, center.y); + left = r(large.x, center.y, center.x, center.y + center.height); + right = r(center.x + center.width, center.y, br_x, center.y + center.height); + bottom= r(large.x, center.y + center.height, br_x, br_y); + + RectList* parts[] = { top, left, right, bottom }; + for (unsigned int i = 0; i < LENGTH(parts); i++) { + head = reclist_insert_disjoint(head, parts[i]); + } + return head; +} + +// insert a new element without any intersections into the given list +static RectList* reclist_insert_disjoint(RectList* head, RectList* element) { + if (!element) { + return head; + } else if (!head) { + // if the list is empty, then intersection-free insertion is trivial + element->next = NULL; + return element; + } else if (!rects_intersect(head, element)) { + head->next = reclist_insert_disjoint(head->next, element); + return head; + } else { + // element intersects with the head rect + Rectangle center = intersection_area(head, element); + Rectangle large = head->rect; + head->rect = center; + head->next = insert_rect_border(head->next, large, center); + head->next = insert_rect_border(head->next, element->rect, center); + g_free(element); + return head; + } +} + +static void rectlist_free(RectList* head) { + if (!head) return; + RectList* next = head->next; + g_free(head); + rectlist_free(next); +} + +static int rectlist_length_acc(RectList* head, int acc) { + if (!head) return acc; + else return rectlist_length_acc(head->next, acc + 1); +} + +static int rectlist_length(RectList* head) { + return rectlist_length_acc(head, 0); +} + +static RectList* disjoin_rects(Rectangle* buf, size_t count) { + RectList* cur; + struct RectList* rects = NULL; + for (unsigned int i = 0; i < count; i++) { + cur = g_new0(RectList, 1); + cur->rect = buf[i]; + rects = reclist_insert_disjoint(rects, cur); + } + return rects; +} + + +int disjoin_rects_command(int argc, char** argv, GString* output) { + (void)SHIFT(argc, argv); + if (argc < 1) { + return HERBST_NEED_MORE_ARGS; + } + Rectangle* buf = g_new(Rectangle, argc); + for (int i = 0; i < argc; i++) { + buf[i] = parse_rectangle(argv[i]); + } + + RectList* rects = disjoin_rects(buf, argc); + for (RectList* cur = rects; cur; cur = cur->next) { + Rectangle r = cur->rect; + g_string_append_printf(output, "%dx%d%+d%+d\n", + r.width, r.height, r.x, r.y); + } + rectlist_free(rects); + g_free(buf); + return 0; +} + +int set_monitor_rects_command(int argc, char** argv, GString* output) { + (void)SHIFT(argc, argv); + if (argc < 1) { + return HERBST_NEED_MORE_ARGS; + } + Rectangle* templates = g_new0(Rectangle, argc); + for (int i = 0; i < argc; i++) { + templates[i] = parse_rectangle(argv[i]); + } + int status = set_monitor_rects(templates, argc); + g_free(templates); + if (status == HERBST_TAG_IN_USE) { + g_string_append_printf(output, + "%s: There are not enough free tags\n", argv[0]); + } else if (status == HERBST_INVALID_ARGUMENT) { + g_string_append_printf(output, + "%s: Need at least one rectangle\n", argv[0]); + } + return status; +} + +int set_monitor_rects(Rectangle* templates, size_t count) { + if (count < 1) { + return HERBST_INVALID_ARGUMENT; + } + HSTag* tag = NULL; + int i; + for (i = 0; i < MIN(count, g_monitors->len); i++) { + HSMonitor* m = monitor_with_index(i); + m->rect = templates[i]; + } + // add additional monitors + for (; i < count; i++) { + tag = find_unused_tag(); + if (!tag) { + return HERBST_TAG_IN_USE; + } + add_monitor(templates[i], tag, NULL); + frame_show_recursive(tag->frame); + } + // remove monitors if there are too much + while (i < g_monitors->len) { + remove_monitor(i); + } + monitor_update_focus_objects(); + all_monitors_apply_layout(); + return 0; +} + +int find_monitor_index_by_name(char* name) { + int i; + for (i = 0; i < g_monitors->len; i++) { + HSMonitor* mon = monitor_with_index(i); + if (mon != NULL && mon->name != NULL && !strcmp(mon->name->str, name)) { + return i; + } + } + return -1; +} + +HSMonitor* find_monitor_by_name(char* name) { + int i = find_monitor_index_by_name(name); + if (i == -1) { + return NULL; + } else { + return monitor_with_index(i); + } +} + +int string_to_monitor_index(char* string) { + if (string[0] == '\0') { + return g_cur_monitor; + } else if (string[0] == '-' || string[0] == '+') { + if (isdigit(string[1])) { + // relative monitor index + int idx = g_cur_monitor + atoi(string); + idx %= g_monitors->len; + idx += g_monitors->len; + idx %= g_monitors->len; + return idx; + } else if (string[0] == '-') { + enum HSDirection dir = char_to_direction(string[1]); + if (dir < 0) return -1; + return monitor_index_in_direction(get_current_monitor(), dir); + } else { + return -1; + } + } else if (isdigit(string[0])) { + // absolute monitor index + int idx = atoi(string); + if (idx < 0 || idx >= g_monitors->len) { + return -1; + } + return idx; + } else { + // monitor string + return find_monitor_index_by_name(string); + } +} + +int monitor_index_in_direction(HSMonitor* m, enum HSDirection dir) { + int cnt = monitor_count(); + RectangleIdx* rects = g_new0(RectangleIdx, cnt); + int relidx = -1; + FOR (i,0,cnt) { + rects[i].idx = i; + rects[i].r = monitor_with_index(i)->rect; + if (monitor_with_index(i) == m) relidx = i; + } + HSAssert(relidx >= 0); + int result = find_rectangle_in_direction(rects, cnt, relidx, dir); + g_free(rects); + return result; +} + +HSMonitor* string_to_monitor(char* string) { + int idx = string_to_monitor_index(string); + return monitor_with_index(idx); +} + +static int monitor_attr_index(void* data) { + HSMonitor* m = (HSMonitor*) data; + return monitor_index_of(m); +} + +static void monitor_attr_tag(void* data, GString* output) { + HSMonitor* m = (HSMonitor*) data; + g_string_append(output, m->tag->display_name->str); +} + +static void monitor_foreach(void (*action)(HSMonitor*)) { + for (int i = 0; i < g_monitors->len; i++) { + HSMonitor* m = monitor_with_index(i); + action(m); + } +} + +static void monitor_unlink_id_object(HSMonitor* m) { + hsobject_unlink(g_monitor_object, &m->object); +} + +static void monitor_link_id_object(HSMonitor* m) { + GString* index_str = g_string_new(""); + int index = monitor_index_of(m); + g_string_printf(index_str, "%d", index); + hsobject_link(g_monitor_object, &m->object, index_str->str); + g_string_free(index_str, true); +} + +HSMonitor* add_monitor(Rectangle rect, HSTag* tag, char* name) { + assert(tag != NULL); + HSMonitor* m = g_new0(HSMonitor, 1); + hsobject_init(&m->object); + if (name) { + hsobject_link(g_monitor_by_name_object, &m->object, name); + } + m->rect = rect; + m->tag = tag; + m->tag_previous = tag; + m->name = (name ? g_string_new(name) : NULL); + m->display_name = g_string_new(name ? name : ""); + m->mouse.x = 0; + m->mouse.y = 0; + m->dirty = true; + m->slice = slice_create_monitor(m); + m->stacking_window = XCreateSimpleWindow(g_display, g_root, + 42, 42, 42, 42, 1, 0, 0); + + m->object.data = m; + HSAttribute attributes[] = { + ATTRIBUTE("name", m->display_name,ATTR_READ_ONLY ), + ATTRIBUTE("index", monitor_attr_index,ATTR_READ_ONLY ), + ATTRIBUTE("tag", monitor_attr_tag,ATTR_READ_ONLY ), + ATTRIBUTE("lock_tag", m->lock_tag, ATTR_READ_ONLY ), + ATTRIBUTE_LAST, + }; + hsobject_set_attributes(&m->object, attributes); + + stack_insert_slice(g_monitor_stack, m->slice); + g_array_append_val(g_monitors, m); + monitor_link_id_object(m); + + return g_array_index(g_monitors, HSMonitor*, g_monitors->len-1); +} + +int add_monitor_command(int argc, char** argv, GString* output) { + // usage: add_monitor RECTANGLE [TAG [NAME]] + if (argc < 2) { + return HERBST_NEED_MORE_ARGS; + } + Rectangle rect = parse_rectangle(argv[1]); + HSTag* tag = NULL; + char* name = NULL; + if (argc == 2 || !strcmp("", argv[2])) { + tag = find_unused_tag(); + if (!tag) { + g_string_append_printf(output, + "%s: There are not enough free tags\n", argv[0]); + return HERBST_TAG_IN_USE; + } + } + else { + tag = find_tag(argv[2]); + if (!tag) { + g_string_append_printf(output, + "%s: The tag \"%s\" does not exist\n", argv[0], argv[2]); + return HERBST_INVALID_ARGUMENT; + } + } + if (find_monitor_with_tag(tag)) { + g_string_append_printf(output, + "%s: The tag \"%s\" is already viewed on a monitor\n", argv[0], argv[2]); + return HERBST_TAG_IN_USE; + } + if (argc > 3) { + name = argv[3]; + if (isdigit(name[0])) { + g_string_append_printf(output, + "%s: The monitor name may not start with a number\n", argv[0]); + return HERBST_INVALID_ARGUMENT; + } + if (!strcmp("", name)) { + g_string_append_printf(output, + "%s: An empty monitor name is not permitted\n", argv[0]); + return HERBST_INVALID_ARGUMENT; + } + if (find_monitor_by_name(name)) { + g_string_append_printf(output, + "%s: A monitor with the same name already exists\n", argv[0]); + return HERBST_INVALID_ARGUMENT; + } + } + HSMonitor* monitor = add_monitor(rect, tag, name); + monitor_apply_layout(monitor); + frame_show_recursive(tag->frame); + emit_tag_changed(tag, g_monitors->len - 1); + drop_enternotify_events(); + return 0; +} + +int remove_monitor_command(int argc, char** argv, GString* output) { + // usage: remove_monitor INDEX + if (argc < 2) { + return HERBST_NEED_MORE_ARGS; + } + int index = string_to_monitor_index(argv[1]); + if (index == -1) { + g_string_append_printf(output, + "%s: Monitor \"%s\" not found!\n", argv[0], argv[1]); + return HERBST_INVALID_ARGUMENT; + } + int ret = remove_monitor(index); + if (ret == HERBST_INVALID_ARGUMENT) { + g_string_append_printf(output, + "%s: Index needs to be between 0 and %d\n", argv[0], g_monitors->len - 1); + } else if (ret == HERBST_FORBIDDEN) { + g_string_append_printf(output, + "%s: Can't remove the last monitor\n", argv[0]); + } + monitor_update_focus_objects(); + return ret; +} + +int remove_monitor(int index) { + if (index < 0 || index >= g_monitors->len) { + return HERBST_INVALID_ARGUMENT; + } + if (g_monitors->len <= 1) { + return HERBST_FORBIDDEN; + } + HSMonitor* monitor = monitor_with_index(index); + // adjust selection + if (g_cur_monitor > index) { + // same monitor shall be selected after remove + g_cur_monitor--; + } + assert(monitor->tag); + assert(monitor->tag->frame); + // hide clients + frame_hide_recursive(monitor->tag->frame); + // remove from monitor stack + stack_remove_slice(g_monitor_stack, monitor->slice); + slice_destroy(monitor->slice); + XDestroyWindow(g_display, monitor->stacking_window); + hsobject_unlink(g_monitor_by_name_object, &monitor->object); + hsobject_free(&monitor->object); + // and remove monitor completely + if (monitor->name) { + g_string_free(monitor->name, true); + } + g_string_free(monitor->display_name, true); + monitor_foreach(monitor_unlink_id_object); + g_array_remove_index(g_monitors, index); + g_free(monitor); + monitor_foreach(monitor_link_id_object); + if (g_cur_monitor >= g_monitors->len) { + g_cur_monitor--; + // if selection has changed, then relayout focused monitor + monitor_apply_layout(get_current_monitor()); + monitor_update_focus_objects(); + // also announce the new selection + ewmh_update_current_desktop(); + emit_tag_changed(get_current_monitor()->tag, g_cur_monitor); + } + return 0; +} + +int move_monitor_command(int argc, char** argv, GString* output) { + // usage: move_monitor INDEX RECT [PADUP [PADRIGHT [PADDOWN [PADLEFT]]]] + // moves monitor with number to RECT + if (argc < 3) { + return HERBST_NEED_MORE_ARGS; + } + HSMonitor* monitor = string_to_monitor(argv[1]); + if (monitor == NULL) { + g_string_append_printf(output, + "%s: Monitor \"%s\" not found!\n", argv[0], argv[1]); + return HERBST_INVALID_ARGUMENT; + } + Rectangle rect = parse_rectangle(argv[2]); + if (rect.width < WINDOW_MIN_WIDTH || rect.height < WINDOW_MIN_HEIGHT) { + g_string_append_printf(output, + "%s: Rectangle is too small\n", argv[0]); + return HERBST_INVALID_ARGUMENT; + } + // else: just move it: + monitor->rect = rect; + if (argc > 3 && argv[3][0] != '\0') monitor->pad_up = atoi(argv[3]); + if (argc > 4 && argv[4][0] != '\0') monitor->pad_right = atoi(argv[4]); + if (argc > 5 && argv[5][0] != '\0') monitor->pad_down = atoi(argv[5]); + if (argc > 6 && argv[6][0] != '\0') monitor->pad_left = atoi(argv[6]); + monitor_apply_layout(monitor); + return 0; +} + +int rename_monitor_command(int argc, char** argv, GString* output) { + if (argc < 3) { + return HERBST_NEED_MORE_ARGS; + } + HSMonitor* mon = string_to_monitor(argv[1]); + if (mon == NULL) { + g_string_append_printf(output, + "%s: Monitor \"%s\" not found!\n", argv[0], argv[1]); + return HERBST_INVALID_ARGUMENT; + } + if (isdigit(argv[2][0])) { + g_string_append_printf(output, + "%s: The monitor name may not start with a number\n", argv[0]); + return HERBST_INVALID_ARGUMENT; + } else if (!strcmp("", argv[2])) { + // empty name -> clear name + if (mon->name != NULL) { + hsobject_unlink_by_name(g_monitor_by_name_object, mon->name->str); + g_string_free(mon->name, true); + mon->name = NULL; + } + return 0; + } + if (find_monitor_by_name(argv[2])) { + g_string_append_printf(output, + "%s: A monitor with the same name already exists\n", argv[0]); + return HERBST_INVALID_ARGUMENT; + } + g_string_assign(mon->display_name, argv[2]); + if (mon->name == NULL) { + // not named before + GString* name = g_string_new(argv[2]); + mon->name = name; + } else { + hsobject_unlink_by_name(g_monitor_by_name_object, mon->name->str); + // already named + g_string_assign(mon->name, argv[2]); + } + hsobject_link(g_monitor_by_name_object, &mon->object, mon->name->str); + return 0; +} + +int monitor_rect_command(int argc, char** argv, GString* output) { + // usage: monitor_rect [[-p] INDEX] + char* monitor_str = NULL; + HSMonitor* m = NULL; + bool with_pad = false; + + // if monitor is supplied + if (argc > 1) { + monitor_str = argv[1]; + } + // if -p is supplied + if (argc > 2) { + monitor_str = argv[2]; + if (!strcmp("-p", argv[1])) { + with_pad = true; + } else { + g_string_append_printf(output, + "%s: Invalid argument \"%s\"\n", argv[0], argv[1]); + return HERBST_INVALID_ARGUMENT; + } + } + // if an index is set + if (monitor_str) { + m = string_to_monitor(monitor_str); + if (m == NULL) { + g_string_append_printf(output, + "%s: Monitor \"%s\" not found!\n", argv[0], monitor_str); + return HERBST_INVALID_ARGUMENT; + } + } else { + m = get_current_monitor(); + } + Rectangle rect = m->rect; + if (with_pad) { + rect.x += m->pad_left; + rect.width -= m->pad_left + m->pad_right; + rect.y += m->pad_up; + rect.height -= m->pad_up + m->pad_down; + } + g_string_append_printf(output, "%d %d %d %d", + rect.x, rect.y, rect.width, rect.height); + return 0; +} + +int monitor_set_pad_command(int argc, char** argv, GString* output) { + if (argc < 2) { + return HERBST_NEED_MORE_ARGS; + } + HSMonitor* monitor = string_to_monitor(argv[1]); + if (monitor == NULL) { + g_string_append_printf(output, + "%s: Monitor \"%s\" not found!\n", argv[0], argv[1]); + return HERBST_INVALID_ARGUMENT; + } + if (argc > 2 && argv[2][0] != '\0') monitor->pad_up = atoi(argv[2]); + if (argc > 3 && argv[3][0] != '\0') monitor->pad_right = atoi(argv[3]); + if (argc > 4 && argv[4][0] != '\0') monitor->pad_down = atoi(argv[4]); + if (argc > 5 && argv[5][0] != '\0') monitor->pad_left = atoi(argv[5]); + monitor_apply_layout(monitor); + return 0; +} + +HSMonitor* find_monitor_with_tag(HSTag* tag) { + int i; + for (i = 0; i < g_monitors->len; i++) { + HSMonitor* m = monitor_with_index(i); + if (m->tag == tag) { + return m; + } + } + return NULL; +} + +void ensure_monitors_are_available() { + if (g_monitors->len > 0) { + // nothing to do + return; + } + // add monitor if necessary + Rectangle rect = Rectangle(0,0, + DisplayWidth(g_display, DefaultScreen(g_display)), + DisplayHeight(g_display, DefaultScreen(g_display))); + ensure_tags_are_available(); + // add monitor with first tag + HSMonitor* m = add_monitor(rect, get_tag_by_index(0), NULL); + g_cur_monitor = 0; + g_cur_frame = m->tag->frame; + + monitor_update_focus_objects(); +} + +HSMonitor* monitor_with_frame(HSFrame* frame) { + // find toplevel Frame + while (frame->parent) { + frame = frame->parent; + } + HSTag* tag = find_tag_with_toplevel_frame(frame); + return find_monitor_with_tag(tag); +} + +HSMonitor* get_current_monitor() { + return g_array_index(g_monitors, HSMonitor*, g_cur_monitor); +} + +int monitor_count() { + return g_monitors->len; +} + +void all_monitors_apply_layout() { + monitor_foreach(monitor_apply_layout); +} + +int monitor_set_tag(HSMonitor* monitor, HSTag* tag) { + HSMonitor* other = find_monitor_with_tag(tag); + if (monitor == other) { + // nothing to do + return 0; + } + if (monitor->lock_tag) { + // If the monitor tag is locked, do not change the tag + if (other != NULL) { + // but if the tag is already visible, change to the + // displaying monitor + monitor_focus_by_index(monitor_index_of(other)); + return 0; + } + return 1; + } + if (other != NULL) { + if (*g_swap_monitors_to_get_tag) { + if (other->lock_tag) { + // the monitor we want to steal the tag from is + // locked. focus that monitor instead + monitor_focus_by_index(monitor_index_of(other)); + return 0; + } + // swap tags + other->tag = monitor->tag; + monitor->tag = tag; + // reset focus + frame_focus_recursive(tag->frame); + /* TODO: find the best order of restacking and layouting */ + monitor_restack(other); + monitor_restack(monitor); + monitor_apply_layout(other); + monitor_apply_layout(monitor); + // discard enternotify-events + drop_enternotify_events(); + monitor_update_focus_objects(); + ewmh_update_current_desktop(); + emit_tag_changed(other->tag, monitor_index_of(other)); + emit_tag_changed(tag, g_cur_monitor); + } else { + // if we are not allowed to steal the tag, then just focus the + // other monitor + monitor_focus_by_index(monitor_index_of(other)); + } + return 0; + } + HSTag* old_tag = monitor->tag; + // save old tag + monitor->tag_previous = old_tag; + // 1. show new tag + monitor->tag = tag; + // first reset focus and arrange windows + frame_focus_recursive(tag->frame); + monitor_restack(monitor); + monitor->lock_frames = true; + monitor_apply_layout(monitor); + monitor->lock_frames = false; + // then show them (should reduce flicker) + frame_show_recursive(tag->frame); + if (!monitor->tag->floating) { + frame_update_frame_window_visibility(monitor->tag->frame); + } + // 2. hide old tag + frame_hide_recursive(old_tag->frame); + // focus window just has been shown + // focus again to give input focus + frame_focus_recursive(tag->frame); + // discard enternotify-events + drop_enternotify_events(); + monitor_update_focus_objects(); + ewmh_update_current_desktop(); + emit_tag_changed(tag, g_cur_monitor); + return 0; +} + +int monitor_set_tag_command(int argc, char** argv, GString* output) { + if (argc < 2) { + return HERBST_NEED_MORE_ARGS; + } + HSMonitor* monitor = get_current_monitor(); + HSTag* tag = find_tag(argv[1]); + if (monitor && tag) { + int ret = monitor_set_tag(monitor, tag); + if (ret != 0) { + g_string_append_printf(output, + "%s: Could not change tag", argv[0]); + if (monitor->lock_tag) { + g_string_append_printf(output, + " (monitor %d is locked)", monitor_index_of(monitor)); + } + g_string_append_printf(output, "\n"); + } + return ret; + } else { + g_string_append_printf(output, + "%s: Invalid tag \"%s\"\n", argv[0], argv[1]); + return HERBST_INVALID_ARGUMENT; + } +} + +int monitor_set_tag_by_index_command(int argc, char** argv, GString* output) { + if (argc < 2) { + return HERBST_NEED_MORE_ARGS; + } + bool skip_visible = false; + if (argc >= 3 && !strcmp(argv[2], "--skip-visible")) { + skip_visible = true; + } + HSTag* tag = get_tag_by_index_str(argv[1], skip_visible); + if (!tag) { + g_string_append_printf(output, + "%s: Invalid index \"%s\"\n", argv[0], argv[1]); + return HERBST_INVALID_ARGUMENT; + } + int ret = monitor_set_tag(get_current_monitor(), tag); + if (ret != 0) { + g_string_append_printf(output, + "%s: Could not change tag (maybe monitor is locked?)\n", argv[0]); + } + return ret; +} + +int monitor_set_previous_tag_command(int argc, char** argv, GString* output) { + if (argc < 1) { + return HERBST_NEED_MORE_ARGS; + } + HSMonitor* monitor = get_current_monitor(); + HSTag* tag = monitor->tag_previous; + if (monitor && tag) { + int ret = monitor_set_tag(monitor, tag); + if (ret != 0) { + g_string_append_printf(output, + "%s: Could not change tag (maybe monitor is locked?)\n", argv[0]); + } + return ret; + } else { + g_string_append_printf(output, + "%s: Invalid monitor or tag\n", argv[0]); + return HERBST_INVALID_ARGUMENT; + } +} + +int monitor_focus_command(int argc, char** argv, GString* output) { + if (argc < 2) { + return HERBST_NEED_MORE_ARGS; + } + int new_selection = string_to_monitor_index(argv[1]); + if (new_selection < 0) { + g_string_append_printf(output, + "%s: Monitor \"%s\" not found!\n", argv[0], argv[1]); + return HERBST_INVALID_ARGUMENT; + } + // really change selection + monitor_focus_by_index(new_selection); + return 0; +} + +int monitor_cycle_command(int argc, char** argv) { + int delta = 1; + int count = g_monitors->len; + if (argc >= 2) { + delta = atoi(argv[1]); + } + int new_selection = g_cur_monitor + delta; + // fix range of index + new_selection %= count; + new_selection += count; + new_selection %= count; + // really change selection + monitor_focus_by_index(new_selection); + return 0; +} + +int monitor_index_of(HSMonitor* monitor) { + for (int i = 0; i < g_monitors->len; i++) { + if (monitor_with_index(i) == monitor) { + return i; + } + } + return -1; +} + +void monitor_focus_by_index(int new_selection) { + new_selection = CLAMP(new_selection, 0, g_monitors->len - 1); + HSMonitor* old = get_current_monitor(); + HSMonitor* monitor = monitor_with_index(new_selection); + if (old == monitor) { + // nothing to do + return; + } + // change selection globals + assert(monitor->tag); + assert(monitor->tag->frame); + g_cur_monitor = new_selection; + frame_focus_recursive(monitor->tag->frame); + // repaint monitors + monitor_apply_layout(old); + monitor_apply_layout(monitor); + int rx, ry; + { + // save old mouse position + Window win, child; + int wx, wy; + unsigned int mask; + if (True == XQueryPointer(g_display, g_root, &win, &child, + &rx, &ry, &wx, &wy, &mask)) { + old->mouse.x = rx - old->rect.x; + old->mouse.y = ry - old->rect.y; + old->mouse.x = CLAMP(old->mouse.x, 0, old->rect.width-1); + old->mouse.y = CLAMP(old->mouse.y, 0, old->rect.height-1); + } + } + // restore position of new monitor + // but only if mouse pointer is not already on new monitor + int new_x, new_y; + if ((monitor->rect.x <= rx) && (rx < monitor->rect.x + monitor->rect.width) + && (monitor->rect.y <= ry) && (ry < monitor->rect.y + monitor->rect.height)) { + // mouse already is on new monitor + } else { + // If the mouse is located in a gap indicated by + // mouse_recenter_gap at the outer border of the monitor, + // recenter the mouse. + if (min(monitor->mouse.x, abs(monitor->mouse.x - monitor->rect.width)) < *g_mouse_recenter_gap + || min(monitor->mouse.y, abs(monitor->mouse.y - monitor->rect.height)) < *g_mouse_recenter_gap) { + monitor->mouse.x = monitor->rect.width / 2; + monitor->mouse.y = monitor->rect.height / 2; + } + new_x = monitor->rect.x + monitor->mouse.x; + new_y = monitor->rect.y + monitor->mouse.y; + XWarpPointer(g_display, None, g_root, 0, 0, 0, 0, new_x, new_y); + // discard all mouse events caused by this pointer movage from the + // event queue, so the focus really stays in the last focused window on + // this monitor and doesn't jump to the window hovered by the mouse + drop_enternotify_events(); + } + // update objects + monitor_update_focus_objects(); + // emit hooks + ewmh_update_current_desktop(); + emit_tag_changed(monitor->tag, new_selection); +} + +void monitor_update_focus_objects() { + hsobject_link(g_monitor_object, &get_current_monitor()->object, "focus"); + tag_update_focus_objects(); +} + +int monitor_get_relative_x(HSMonitor* m, int x_root) { + return x_root - m->rect.x - m->pad_left; +} + +int monitor_get_relative_y(HSMonitor* m, int y_root) { + return y_root - m->rect.y - m->pad_up; +} + +HSMonitor* monitor_with_coordinate(int x, int y) { + int i; + for (i = 0; i < g_monitors->len; i++) { + HSMonitor* m = monitor_with_index(i); + if (m->rect.x + m->pad_left <= x + && m->rect.x + m->rect.width - m->pad_right > x + && m->rect.y + m->pad_up <= y + && m->rect.y + m->rect.height - m->pad_down > y) { + return m; + } + } + return NULL; +} + +HSMonitor* monitor_with_index(int index) { + if (index < 0 || index >= g_monitors->len) { + return NULL; + } + return g_array_index(g_monitors, HSMonitor*, index); +} + +int monitors_lock_command(int argc, const char** argv) { + monitors_lock(); + return 0; +} + +void monitors_lock() { + // lock-number must never be negative + // ensure that lock value is valid + if (*g_monitors_locked < 0) { + *g_monitors_locked = 0; + } + // increase lock => it is definitely > 0, i.e. locked + (*g_monitors_locked)++; + monitors_lock_changed(); +} + +int monitors_unlock_command(int argc, const char** argv) { + monitors_unlock(); + return 0; +} + +void monitors_unlock() { + // lock-number must never be lower than 1 if unlocking + // so: ensure that lock value is valid + if (*g_monitors_locked < 1) { + *g_monitors_locked = 1; + } + // decrease lock => unlock + (*g_monitors_locked)--; + monitors_lock_changed(); +} + +void monitors_lock_changed() { + if (*g_monitors_locked < 0) { + *g_monitors_locked = 0; + HSDebug("fixing invalid monitors_locked value to 0\n"); + } + if (!*g_monitors_locked) { + // if not locked anymore, then repaint all the dirty monitors + for (int i = 0; i < g_monitors->len; i++) { + HSMonitor* m = monitor_with_index(i); + if (m->dirty) { + monitor_apply_layout(m); + } + } + } +} + +int monitor_lock_tag_command(int argc, char** argv, GString* output) { + char* cmd_name = argv[0]; + (void)SHIFT(argc, argv); + HSMonitor *monitor; + if (argc >= 1) { + monitor = string_to_monitor(argv[0]); + if (monitor == NULL) { + g_string_append_printf(output, + "%s: Monitor \"%s\" not found!\n", cmd_name, argv[0]); + return HERBST_INVALID_ARGUMENT; + } + } else { + monitor = get_current_monitor(); + } + monitor->lock_tag = true; + return 0; +} + +int monitor_unlock_tag_command(int argc, char** argv, GString* output) { + char* cmd_name = argv[0]; + (void)SHIFT(argc, argv); + HSMonitor *monitor; + if (argc >= 1) { + monitor = string_to_monitor(argv[0]); + if (monitor == NULL) { + g_string_append_printf(output, + "%s: Monitor \"%s\" not found!\n", cmd_name, argv[0]); + return HERBST_INVALID_ARGUMENT; + } + } else { + monitor = get_current_monitor(); + } + monitor->lock_tag = false; + return 0; +} + +// monitor detection using xinerama (if available) +#ifdef XINERAMA +// inspired by dwm's isuniquegeom() +static bool geom_unique(XineramaScreenInfo *unique, size_t n, XineramaScreenInfo *info) { + while (n--) + if (unique[n].x_org == info->x_org && unique[n].y_org == info->y_org + && unique[n].width == info->width && unique[n].height == info->height) + return false; + return true; +} + +// inspired by dwm's updategeom() +bool detect_monitors_xinerama(Rectangle** ret_rects, size_t* ret_count) { + int i, j, n; + XineramaScreenInfo *info, *unique; + Rectangle *monitors; + + if (!XineramaIsActive(g_display)) { + return false; + } + info = XineramaQueryScreens(g_display, &n); + unique = g_new(XineramaScreenInfo, n); + /* only consider unique geometries as separate screens */ + for (i = 0, j = 0; i < n; i++) { + if (geom_unique(unique, j, &info[i])) + { + memcpy(&unique[j++], &info[i], sizeof(XineramaScreenInfo)); + } + } + XFree(info); + n = j; + + monitors = g_new(Rectangle, n); + for (i = 0; i < n; i++) { + monitors[i].x = unique[i].x_org; + monitors[i].y = unique[i].y_org; + monitors[i].width = unique[i].width; + monitors[i].height = unique[i].height; + } + *ret_count = n; + *ret_rects = monitors; + g_free(unique); + return true; +} +#else /* XINERAMA */ + +bool detect_monitors_xinerama(Rectangle** ret_rects, size_t* ret_count) { + return false; +} + +#endif /* XINERAMA */ + +// monitor detection that always works: one monitor across the entire screen +bool detect_monitors_simple(Rectangle** ret_rects, size_t* ret_count) { + XWindowAttributes attributes; + XGetWindowAttributes(g_display, g_root, &attributes); + + *ret_count = 1; + *ret_rects = g_new0(Rectangle, 1); + (*ret_rects)->x = 0; + (*ret_rects)->y = 0; + (*ret_rects)->width = attributes.width; + (*ret_rects)->height = attributes.height; + return true; +} + +bool detect_monitors_debug_example(Rectangle** ret_rects, size_t* ret_count) { + *ret_count = 2; + *ret_rects = g_new0(Rectangle, 2); + (*ret_rects)[0].x = 0; + (*ret_rects)[0].y = 0; + (*ret_rects)[0].width = g_screen_width * 2 / 3; + (*ret_rects)[0].height = g_screen_height * 2 / 3; + (*ret_rects)[1].x = g_screen_width / 3; + (*ret_rects)[1].y = g_screen_height / 3; + (*ret_rects)[1].width = g_screen_width * 2 / 3; + (*ret_rects)[1].height = g_screen_height * 2 / 3; + return true; +} + + +int detect_monitors_command(int argc, const char **argv, GString* output) { + MonitorDetection detect[] = { + detect_monitors_xinerama, + detect_monitors_simple, + detect_monitors_debug_example, // move up for debugging + }; + Rectangle* monitors = NULL; + size_t count = 0; + // search for a working monitor detection + // at least the simple detection must work + for (int i = 0; i < LENGTH(detect); i++) { + if (detect[i](&monitors, &count)) { + break; + } + } + assert(count && monitors); + bool list_only = false; + bool disjoin = true; + //bool drop_small = true; + FOR (i,1,argc) { + if (!strcmp(argv[i], "-l")) list_only = true; + else if (!strcmp(argv[i], "--list")) list_only = true; + else if (!strcmp(argv[i], "--no-disjoin")) disjoin = false; + // TOOD: + // else if (!strcmp(argv[i], "--keep-small")) drop_small = false; + else { + g_string_append_printf(output, + "detect_monitors: unknown flag \"%s\"\n", argv[i]); + return HERBST_INVALID_ARGUMENT; + } + } + + int ret = 0; + if (list_only) { + FOR (i,0,count) { + g_string_append_printf(output, "%dx%d%+d%+d\n", + monitors[i].width, monitors[i].height, + monitors[i].x, monitors[i].y); + } + } else { + // possibly disjoin them + if (disjoin) { + RectList* rl = disjoin_rects(monitors, count); + count = rectlist_length(rl); + monitors = g_renew(Rectangle, monitors, count); + RectList* cur = rl; + FOR (i,0,count) { + RectList* next = cur->next; + monitors[i] = cur->rect; + g_free(cur); + cur = next; + } + } + // apply it + ret = set_monitor_rects(monitors, count); + if (ret == HERBST_TAG_IN_USE && output != NULL) { + g_string_append_printf(output, + "%s: There are not enough free tags\n", argv[0]); + } + } + g_free(monitors); + return ret; +} + +int monitor_stack_window_count(bool real_clients) { + return stack_window_count(g_monitor_stack, real_clients); +} + +void monitor_stack_to_window_buf(Window* buf, int len, bool real_clients, + int* remain_len) { + stack_to_window_buf(g_monitor_stack, buf, len, real_clients, remain_len); +} + +HSStack* get_monitor_stack() { + return g_monitor_stack; +} + +int monitor_raise_command(int argc, char** argv, GString* output) { + char* cmd_name = argv[0]; + (void)SHIFT(argc, argv); + HSMonitor* monitor; + if (argc >= 1) { + monitor = string_to_monitor(argv[0]); + if (monitor == NULL) { + g_string_append_printf(output, + "%s: Monitor \"%s\" not found!\n", cmd_name, argv[0]); + return HERBST_INVALID_ARGUMENT; + } + } else { + monitor = get_current_monitor(); + } + stack_raise_slide(g_monitor_stack, monitor->slice); + return 0; +} + +void monitor_restack(HSMonitor* monitor) { + int count = 1 + stack_window_count(monitor->tag->stack, false); + Window* buf = g_new(Window, count); + buf[0] = monitor->stacking_window; + stack_to_window_buf(monitor->tag->stack, buf + 1, count - 1, false, NULL); + /* remove a focused fullscreen client */ + HSClient* client = frame_focused_client(monitor->tag->frame); + if (client && client->fullscreen) { + XRaiseWindow(g_display, client->dec.decwin); + int idx = array_find(buf, count, sizeof(*buf), &client->dec.decwin); + assert(idx >= 0); + count--; + memmove(buf + idx, buf + idx + 1, sizeof(*buf) * (count - idx)); + } + DesktopWindow::lowerDesktopWindows(); + XRestackWindows(g_display, buf, count); + g_free(buf); +} + +int shift_to_monitor(int argc, char** argv, GString* output) { + if (argc <= 1) { + return HERBST_NEED_MORE_ARGS; + } + char* monitor_str = argv[1]; + HSMonitor* monitor = string_to_monitor(monitor_str); + if (!monitor) { + g_string_append_printf(output, + "%s: Invalid monitor\n", monitor_str); + return HERBST_INVALID_ARGUMENT; + } + tag_move_focused_client(monitor->tag); + return 0; +} + +void all_monitors_replace_previous_tag(HSTag *old, HSTag *newmon) { + int i; + for (i = 0; i < g_monitors->len; i++) { + HSMonitor* m = monitor_with_index(i); + if (m->tag_previous == old) { + m->tag_previous = newmon; + } + } +} + +void drop_enternotify_events() { + XEvent ev; + XSync(g_display, False); + while(XCheckMaskEvent(g_display, EnterWindowMask, &ev)); +} + +Rectangle monitor_get_floating_area(HSMonitor* m) { + Rectangle r = m->rect; + r.x += m->pad_left; + r.width -= m->pad_left + m->pad_right; + r.y += m->pad_up; + r.height -= m->pad_up + m->pad_down; + return r; +} + diff -Nru herbstluftwm-0.6.2/src/monitor.h herbstluftwm-0.7.0/src/monitor.h --- herbstluftwm-0.6.2/src/monitor.h 2014-03-26 23:54:22.000000000 +0000 +++ herbstluftwm-0.7.0/src/monitor.h 2015-10-14 13:27:40.000000000 +0000 @@ -45,9 +45,6 @@ Window stacking_window; // window used for making stacking easy } HSMonitor; -// globals -int g_cur_monitor; - void monitor_init(); void monitor_destroy(); @@ -90,15 +87,15 @@ int monitor_set_previous_tag_command(int argc, char** argv, GString* output); void monitors_lock(); void monitors_unlock(); -int monitors_lock_command(int argc, char** argv); -int monitors_unlock_command(int argc, char** argv); +int monitors_lock_command(int argc, const char** argv); +int monitors_unlock_command(int argc, const char** argv); void monitors_lock_changed(); int monitor_lock_tag_command(int argc, char** argv, GString* output); int monitor_unlock_tag_command(int argc, char** argv, GString* output); void monitor_apply_layout(HSMonitor* monitor); void all_monitors_apply_layout(); void ensure_monitors_are_available(); -void all_monitors_replace_previous_tag(struct HSTag* old, struct HSTag* new); +void all_monitors_replace_previous_tag(struct HSTag* old, struct HSTag* newmon); void drop_enternotify_events(); @@ -113,7 +110,7 @@ typedef bool (*MonitorDetection)(Rectangle**, size_t*); bool detect_monitors_xinerama(Rectangle** ret_rects, size_t* ret_count); bool detect_monitors_simple(Rectangle** ret_rects, size_t* ret_count); -int detect_monitors_command(int argc, char **argv, GString* output); +int detect_monitors_command(int argc, const char **argv, GString* output); int shift_to_monitor(int argc, char** argv, GString* output); diff -Nru herbstluftwm-0.6.2/src/mouse.c herbstluftwm-0.7.0/src/mouse.c --- herbstluftwm-0.6.2/src/mouse.c 2014-03-26 23:54:22.000000000 +0000 +++ herbstluftwm-0.7.0/src/mouse.c 1970-01-01 00:00:00.000000000 +0000 @@ -1,536 +0,0 @@ -/** Copyright 2011-2013 Thorsten Wißmann. All rights reserved. - * - * This software is licensed under the "Simplified BSD License". - * See LICENSE for details */ - -#include "mouse.h" -#include "globals.h" -#include "clientlist.h" -#include "layout.h" -#include "key.h" -#include "ipc-protocol.h" -#include "utils.h" -#include "settings.h" -#include "command.h" - -#include -#include -#include -#include "glib-backports.h" - -// gui -#include -#include -#include -#include -#include - -static Point2D g_button_drag_start; -static Rectangle g_win_drag_start; -static HSClient* g_win_drag_client = NULL; -static HSMonitor* g_drag_monitor = NULL; -static MouseDragFunction g_drag_function = NULL; - -static Cursor g_cursor; -static GList* g_mouse_binds = NULL; -static unsigned int* g_numlockmask_ptr; -static int* g_snap_distance; -static int* g_snap_gap; - -#define CLEANMASK(mask) ((mask) & ~(*g_numlockmask_ptr|LockMask)) -#define REMOVEBUTTONMASK(mask) ((mask) & \ - ~( Button1Mask \ - | Button2Mask \ - | Button3Mask \ - | Button4Mask \ - | Button5Mask )) - -void mouse_init() { - g_numlockmask_ptr = get_numlockmask_ptr(); - g_snap_distance = &(settings_find("snap_distance")->value.i); - g_snap_gap = &(settings_find("snap_gap")->value.i); - /* set cursor theme */ - g_cursor = XCreateFontCursor(g_display, XC_left_ptr); - XDefineCursor(g_display, g_root, g_cursor); -} - -void mouse_destroy() { - mouse_unbind_all(); - XFreeCursor(g_display, g_cursor); -} - -void mouse_handle_event(XEvent* ev) { - XButtonEvent* be = &(ev->xbutton); - MouseBinding* b = mouse_binding_find(be->state, be->button); - HSClient* client = get_client_from_window(ev->xbutton.window); - if (!b || !client) { - // there is no valid bind for this type of mouse event - return; - } - b->action(client, b->argc, b->argv); -} - -void mouse_initiate_move(HSClient* client, int argc, char** argv) { - (void) argc; (void) argv; - mouse_initiate_drag(client, mouse_function_move); -} - -void mouse_initiate_zoom(HSClient* client, int argc, char** argv) { - (void) argc; (void) argv; - mouse_initiate_drag(client, mouse_function_zoom); -} - -void mouse_initiate_resize(HSClient* client, int argc, char** argv) { - (void) argc; (void) argv; - mouse_initiate_drag(client, mouse_function_resize); -} - -void mouse_call_command(struct HSClient* client, int argc, char** argv) { - // TODO: add completion - client_set_dragged(client, true); - call_command_no_output(argc, argv); - client_set_dragged(client, false); -} - - -void mouse_initiate_drag(HSClient* client, MouseDragFunction function) { - g_drag_function = function; - g_win_drag_client = client; - g_drag_monitor = find_monitor_with_tag(client->tag); - if (!g_drag_monitor || client->tag->floating == false) { - // only can drag wins in floating mode - g_win_drag_client = NULL; - g_drag_function = NULL; - return; - } - client_set_dragged(g_win_drag_client, true); - g_win_drag_start = g_win_drag_client->float_size; - g_button_drag_start = get_cursor_position(); - XGrabPointer(g_display, client->window, True, - PointerMotionMask|ButtonReleaseMask, GrabModeAsync, - GrabModeAsync, None, None, CurrentTime); -} - -void mouse_stop_drag() { - if (g_win_drag_client) { - client_set_dragged(g_win_drag_client, false); - // resend last size - monitor_apply_layout(g_drag_monitor); - } - g_win_drag_client = NULL; - g_drag_function = NULL; - XUngrabPointer(g_display, CurrentTime); - // remove all enternotify-events from the event queue that were - // generated by the XUngrabPointer - XEvent ev; - XSync(g_display, False); - while(XCheckMaskEvent(g_display, EnterWindowMask, &ev)); -} - -void handle_motion_event(XEvent* ev) { - if (!g_drag_monitor) { return; } - if (!g_win_drag_client) return; - if (!g_drag_function) return; - if (ev->type != MotionNotify) return; - // get newest motion notification - while (XCheckMaskEvent(g_display, ButtonMotionMask, ev)); - // call function that handles it - g_drag_function(&(ev->xmotion)); -} - -bool mouse_is_dragging() { - return g_drag_function != NULL; -} - -static void mouse_binding_free(void* voidmb) { - MouseBinding* mb = voidmb; - if (!mb) return; - argv_free(mb->argc, mb->argv); - g_free(mb); -} - -int mouse_unbind_all() { - g_list_free_full(g_mouse_binds, mouse_binding_free); - g_mouse_binds = NULL; - HSClient* client = get_current_client(); - if (client) { - grab_client_buttons(client, true); - } - return 0; -} - -int mouse_binding_equals(MouseBinding* a, MouseBinding* b) { - if((REMOVEBUTTONMASK(CLEANMASK(a->modifiers)) - == REMOVEBUTTONMASK(CLEANMASK(b->modifiers))) - && (a->button == b->button)) { - return 0; - } else { - return -1; - } -} - -int mouse_bind_command(int argc, char** argv, GString* output) { - if (argc < 3) { - return HERBST_NEED_MORE_ARGS; - } - unsigned int modifiers = 0; - char* string = argv[1]; - if (!string2modifiers(string, &modifiers)) { - g_string_append_printf(output, - "%s: Modifier \"%s\" does not exist\n", argv[0], string); - return HERBST_INVALID_ARGUMENT; - } - // last one is the mouse button - char* last_token = strlasttoken(string, KEY_COMBI_SEPARATORS); - unsigned int button = string2button(last_token); - if (button == 0) { - g_string_append_printf(output, - "%s: Unknown mouse button \"%s\"\n", argv[0], last_token); - return HERBST_INVALID_ARGUMENT; - } - MouseFunction function = string2mousefunction(argv[2]); - if (!function) { - g_string_append_printf(output, - "%s: Unknown mouse action \"%s\"\n", argv[0], argv[2]); - return HERBST_INVALID_ARGUMENT; - } - - // actually create a binding - MouseBinding* mb = g_new(MouseBinding, 1); - mb->button = button; - mb->modifiers = modifiers; - mb->action = function; - mb->argc = argc - 3; - mb->argv = argv_duplicate(argc - 3, argv + 3);; - g_mouse_binds = g_list_prepend(g_mouse_binds, mb); - HSClient* client = get_current_client(); - if (client) { - grab_client_buttons(client, true); - } - return 0; -} - -MouseFunction string2mousefunction(char* name) { - static struct { - char* name; - MouseFunction function; - } table[] = { - { "move", mouse_initiate_move }, - { "zoom", mouse_initiate_zoom }, - { "resize", mouse_initiate_resize }, - { "call", mouse_call_command }, - }; - int i; - for (i = 0; i < LENGTH(table); i++) { - if (!strcmp(table[i].name, name)) { - return table[i].function; - } - } - return NULL; -} - -static struct { - char* name; - unsigned int button; -} string2button_table[] = { - { "Button1", Button1 }, - { "Button2", Button2 }, - { "Button3", Button3 }, - { "Button4", Button4 }, - { "Button5", Button5 }, - { "B1", Button1 }, - { "B2", Button2 }, - { "B3", Button3 }, - { "B4", Button4 }, - { "B5", Button5 }, -}; -unsigned int string2button(char* name) { - for (int i = 0; i < LENGTH(string2button_table); i++) { - if (!strcmp(string2button_table[i].name, name)) { - return string2button_table[i].button; - } - } - return 0; -} - - -void complete_against_mouse_buttons(char* needle, char* prefix, GString* output) { - for (int i = 0; i < LENGTH(string2button_table); i++) { - char* buttonname = string2button_table[i].name; - try_complete_prefix(needle, buttonname, prefix, output); - } -} - -MouseBinding* mouse_binding_find(unsigned int modifiers, unsigned int button) { - MouseBinding mb = { .modifiers = modifiers, .button = button, 0}; - GList* elem = g_list_find_custom(g_mouse_binds, &mb, - (GCompareFunc)mouse_binding_equals); - return elem ? elem->data : NULL; -} - -static void grab_client_button(MouseBinding* bind, HSClient* client) { - unsigned int modifiers[] = { 0, LockMask, *g_numlockmask_ptr, *g_numlockmask_ptr|LockMask }; - for(int j = 0; j < LENGTH(modifiers); j++) { - XGrabButton(g_display, bind->button, - bind->modifiers | modifiers[j], - client->window, False, ButtonPressMask | ButtonReleaseMask, - GrabModeAsync, GrabModeSync, None, None); - } -} - -void grab_client_buttons(HSClient* client, bool focused) { - update_numlockmask(); - XUngrabButton(g_display, AnyButton, AnyModifier, client->window); - if (focused) { - g_list_foreach(g_mouse_binds, (GFunc)grab_client_button, client); - } - unsigned int btns[] = { Button1, Button2, Button3 }; - for (int i = 0; i < LENGTH(btns); i++) { - XGrabButton(g_display, btns[i], AnyModifier, client->window, False, - ButtonPressMask|ButtonReleaseMask, GrabModeSync, - GrabModeSync, None, None); - } -} - -void mouse_function_move(XMotionEvent* me) { - int x_diff = me->x_root - g_button_drag_start.x; - int y_diff = me->y_root - g_button_drag_start.y; - g_win_drag_client->float_size = g_win_drag_start; - g_win_drag_client->float_size.x += x_diff; - g_win_drag_client->float_size.y += y_diff; - // snap it to other windows - int dx, dy; - client_snap_vector(g_win_drag_client, g_drag_monitor, - SNAP_EDGE_ALL, &dx, &dy); - g_win_drag_client->float_size.x += dx; - g_win_drag_client->float_size.y += dy; - client_resize_floating(g_win_drag_client, g_drag_monitor); -} - -void mouse_function_resize(XMotionEvent* me) { - int x_diff = me->x_root - g_button_drag_start.x; - int y_diff = me->y_root - g_button_drag_start.y; - g_win_drag_client->float_size = g_win_drag_start; - // relative x/y coords in drag window - HSMonitor* m = g_drag_monitor; - int rel_x = monitor_get_relative_x(m, g_button_drag_start.x) - g_win_drag_start.x; - int rel_y = monitor_get_relative_y(m, g_button_drag_start.y) - g_win_drag_start.y; - bool top = false; - bool left = false; - if (rel_y < g_win_drag_start.height/2) { - top = true; - y_diff *= -1; - } - if (rel_x < g_win_drag_start.width/2) { - left = true; - x_diff *= -1; - } - // avoid an overflow - int new_width = g_win_drag_client->float_size.width + x_diff; - int new_height = g_win_drag_client->float_size.height + y_diff; - int min_width = WINDOW_MIN_WIDTH; - int min_height = WINDOW_MIN_HEIGHT; - HSClient* client = g_win_drag_client; - if (client->sizehints_floating) { - min_width = MAX(WINDOW_MIN_WIDTH, client->minw); - min_height = MAX(WINDOW_MIN_HEIGHT, client->minh); - } - if (new_width < min_width) { - new_width = min_width; - x_diff = new_width - g_win_drag_client->float_size.width; - } - if (new_height < min_height) { - new_height = min_height; - y_diff = new_height - g_win_drag_client->float_size.height; - } - if (left) g_win_drag_client->float_size.x -= x_diff; - if (top) g_win_drag_client->float_size.y -= y_diff; - g_win_drag_client->float_size.width = new_width; - g_win_drag_client->float_size.height = new_height; - // snap it to other windows - int dx, dy; - int snap_flags = 0; - if (left) snap_flags |= SNAP_EDGE_LEFT; - else snap_flags |= SNAP_EDGE_RIGHT; - if (top) snap_flags |= SNAP_EDGE_TOP; - else snap_flags |= SNAP_EDGE_BOTTOM; - client_snap_vector(g_win_drag_client, g_drag_monitor, - snap_flags, &dx, &dy); - if (left) { - g_win_drag_client->float_size.x += dx; - dx *= -1; - } - if (top) { - g_win_drag_client->float_size.y += dy; - dy *= -1; - } - g_win_drag_client->float_size.width += dx; - g_win_drag_client->float_size.height += dy; - client_resize_floating(g_win_drag_client, g_drag_monitor); -} - -void mouse_function_zoom(XMotionEvent* me) { - // stretch, where center stays at the same position - int x_diff = me->x_root - g_button_drag_start.x; - int y_diff = me->y_root - g_button_drag_start.y; - // relative x/y coords in drag window - HSMonitor* m = g_drag_monitor; - int rel_x = monitor_get_relative_x(m, g_button_drag_start.x) - g_win_drag_start.x; - int rel_y = monitor_get_relative_y(m, g_button_drag_start.y) - g_win_drag_start.y; - int cent_x = g_win_drag_start.x + g_win_drag_start.width / 2; - int cent_y = g_win_drag_start.y + g_win_drag_start.height / 2; - if (rel_x < g_win_drag_start.width/2) { - x_diff *= -1; - } - if (rel_y < g_win_drag_start.height/2) { - y_diff *= -1; - } - HSClient* client = g_win_drag_client; - - // avoid an overflow - int new_width = g_win_drag_start.width + 2 * x_diff; - int new_height = g_win_drag_start.height + 2 * y_diff; - // apply new rect - client->float_size = g_win_drag_start; - client->float_size.x = cent_x - new_width / 2; - client->float_size.y = cent_y - new_height / 2; - client->float_size.width = new_width; - client->float_size.height = new_height; - // snap it to other windows - int right_dx, bottom_dy; - int left_dx, top_dy; - // we have to distinguish the direction in which we zoom - client_snap_vector(g_win_drag_client, m, - SNAP_EDGE_BOTTOM | SNAP_EDGE_RIGHT, &right_dx, &bottom_dy); - client_snap_vector(g_win_drag_client, m, - SNAP_EDGE_TOP | SNAP_EDGE_LEFT, &left_dx, &top_dy); - // e.g. if window snaps by vector (3,3) at topleft, window has to be shrinked - // but if the window snaps by vector (3,3) at bottomright, window has to grow - if (abs(right_dx) < abs(left_dx)) { - right_dx = -left_dx; - } - if (abs(bottom_dy) < abs(top_dy)) { - bottom_dy = -top_dy; - } - new_width += 2 * right_dx; - new_height += 2 * bottom_dy; - applysizehints(client, &new_width, &new_height); - // center window again - client->float_size.width = new_width; - client->float_size.height = new_height; - client->float_size.x = cent_x - new_width / 2; - client->float_size.y = cent_y - new_height / 2; - client_resize_floating(g_win_drag_client, g_drag_monitor); -} - -struct SnapData { - HSClient* client; - Rectangle rect; - enum SnapFlags flags; - int dx, dy; // the vector from client to other to make them snap -}; - -bool is_point_between(int point, int left, int right) { - return (point < right && point >= left); -} - -// tells if the intervals [a_left, a_right) [b_left, b_right) intersect -bool intervals_intersect(int a_left, int a_right, int b_left, int b_right) { - return (b_left < a_right) && (a_left < b_right); -} - -// compute vector to snap a point to an edge -static void snap_1d(int x, int edge, int* delta) { - // whats the vector from subject to edge? - int cur_delta = edge - x; - // if distance is smaller then all other deltas - if (abs(cur_delta) < abs(*delta)) { - // then snap it, i.e. save vector - *delta = cur_delta; - } -} - -static int client_snap_helper(HSClient* candidate, struct SnapData* d) { - if (candidate == d->client) { - return 0; - } - Rectangle subject = d->rect; - Rectangle other = candidate->dec.last_outer_rect; - // increase other by snap gap - other.x -= *g_snap_gap; - other.y -= *g_snap_gap; - other.width += *g_snap_gap * 2; - other.height += *g_snap_gap * 2; - if (intervals_intersect(other.y, other.y + other.height, subject.y, subject.y + subject.height)) { - // check if x can snap to the right - if (d->flags & SNAP_EDGE_RIGHT) { - snap_1d(subject.x + subject.width, other.x, &d->dx); - } - // or to the left - if (d->flags & SNAP_EDGE_LEFT) { - snap_1d(subject.x, other.x + other.width, &d->dx); - } - } - if (intervals_intersect(other.x, other.x + other.width, subject.x, subject.x + subject.width)) { - // if we can snap to the top - if (d->flags & SNAP_EDGE_TOP) { - snap_1d(subject.y, other.y + other.height, &d->dy); - } - // or to the bottom - if (d->flags & SNAP_EDGE_BOTTOM) { - snap_1d(subject.y + subject.height, other.y, &d->dy); - } - } - return 0; -} - -// get the vector to snap a client to it's neighbour -void client_snap_vector(struct HSClient* client, struct HSMonitor* monitor, - enum SnapFlags flags, - int* return_dx, int* return_dy) { - struct SnapData d; - HSTag* tag = monitor->tag; - int distance = (*g_snap_distance > 0) ? *g_snap_distance : 0; - // init delta - *return_dx = 0; - *return_dy = 0; - if (!distance) { - // nothing to do - return; - } - d.client = client; - // translate client rectangle to global coordinates - d.rect = client_outer_floating_rect(client); - d.rect.x += monitor->rect.x + monitor->pad_left; - d.rect.y += monitor->rect.y + monitor->pad_up; - d.flags = flags; - d.dx = distance; - d.dy = distance; - - // snap to monitor edges - HSMonitor* m = g_drag_monitor; - if (flags & SNAP_EDGE_TOP) { - snap_1d(d.rect.y, m->rect.y + m->pad_up + *g_snap_gap, &d.dy); - } - if (flags & SNAP_EDGE_LEFT) { - snap_1d(d.rect.x, m->rect.x + m->pad_left + *g_snap_gap, &d.dx); - } - if (flags & SNAP_EDGE_RIGHT) { - snap_1d(d.rect.x + d.rect.width, m->rect.x + m->rect.width - m->pad_right - *g_snap_gap, &d.dx); - } - if (flags & SNAP_EDGE_BOTTOM) { - snap_1d(d.rect.y + d.rect.height, m->rect.y + m->rect.height - m->pad_down - *g_snap_gap, &d.dy); - } - - // snap to other clients - frame_foreach_client(tag->frame, (ClientAction)client_snap_helper, &d); - - // write back results - if (abs(d.dx) < abs(distance)) { - *return_dx = d.dx; - } - if (abs(d.dy) < abs(distance)) { - *return_dy = d.dy; - } -} - diff -Nru herbstluftwm-0.6.2/src/mouse.cpp herbstluftwm-0.7.0/src/mouse.cpp --- herbstluftwm-0.6.2/src/mouse.cpp 1970-01-01 00:00:00.000000000 +0000 +++ herbstluftwm-0.7.0/src/mouse.cpp 2015-10-14 13:27:40.000000000 +0000 @@ -0,0 +1,538 @@ +/** Copyright 2011-2013 Thorsten Wißmann. All rights reserved. + * + * This software is licensed under the "Simplified BSD License". + * See LICENSE for details */ + +#include "mouse.h" +#include "globals.h" +#include "clientlist.h" +#include "layout.h" +#include "key.h" +#include "ipc-protocol.h" +#include "utils.h" +#include "settings.h" +#include "command.h" + +#include +#include +#include +#include "glib-backports.h" + +// gui +#include +#include +#include +#include +#include + +static Point2D g_button_drag_start; +static Rectangle g_win_drag_start; +static HSClient* g_win_drag_client = NULL; +static HSMonitor* g_drag_monitor = NULL; +static MouseDragFunction g_drag_function = NULL; + +static Cursor g_cursor; +static GList* g_mouse_binds = NULL; +static unsigned int* g_numlockmask_ptr; +static int* g_snap_distance; +static int* g_snap_gap; + +#define CLEANMASK(mask) ((mask) & ~(*g_numlockmask_ptr|LockMask)) +#define REMOVEBUTTONMASK(mask) ((mask) & \ + ~( Button1Mask \ + | Button2Mask \ + | Button3Mask \ + | Button4Mask \ + | Button5Mask )) + +void mouse_init() { + g_numlockmask_ptr = get_numlockmask_ptr(); + g_snap_distance = &(settings_find("snap_distance")->value.i); + g_snap_gap = &(settings_find("snap_gap")->value.i); + /* set cursor theme */ + g_cursor = XCreateFontCursor(g_display, XC_left_ptr); + XDefineCursor(g_display, g_root, g_cursor); +} + +void mouse_destroy() { + mouse_unbind_all(); + XFreeCursor(g_display, g_cursor); +} + +void mouse_handle_event(XEvent* ev) { + XButtonEvent* be = &(ev->xbutton); + MouseBinding* b = mouse_binding_find(be->state, be->button); + HSClient* client = get_client_from_window(ev->xbutton.window); + if (!b || !client) { + // there is no valid bind for this type of mouse event + return; + } + b->action(client, b->argc, b->argv); +} + +void mouse_initiate_move(HSClient* client, int argc, char** argv) { + (void) argc; (void) argv; + mouse_initiate_drag(client, mouse_function_move); +} + +void mouse_initiate_zoom(HSClient* client, int argc, char** argv) { + (void) argc; (void) argv; + mouse_initiate_drag(client, mouse_function_zoom); +} + +void mouse_initiate_resize(HSClient* client, int argc, char** argv) { + (void) argc; (void) argv; + mouse_initiate_drag(client, mouse_function_resize); +} + +void mouse_call_command(struct HSClient* client, int argc, char** argv) { + // TODO: add completion + client_set_dragged(client, true); + call_command_no_output(argc, argv); + client_set_dragged(client, false); +} + + +void mouse_initiate_drag(HSClient* client, MouseDragFunction function) { + g_drag_function = function; + g_win_drag_client = client; + g_drag_monitor = find_monitor_with_tag(client->tag); + if (!g_drag_monitor || client->tag->floating == false) { + // only can drag wins in floating mode + g_win_drag_client = NULL; + g_drag_function = NULL; + return; + } + client_set_dragged(g_win_drag_client, true); + g_win_drag_start = g_win_drag_client->float_size; + g_button_drag_start = get_cursor_position(); + XGrabPointer(g_display, client->window, True, + PointerMotionMask|ButtonReleaseMask, GrabModeAsync, + GrabModeAsync, None, None, CurrentTime); +} + +void mouse_stop_drag() { + if (g_win_drag_client) { + client_set_dragged(g_win_drag_client, false); + // resend last size + monitor_apply_layout(g_drag_monitor); + } + g_win_drag_client = NULL; + g_drag_function = NULL; + XUngrabPointer(g_display, CurrentTime); + // remove all enternotify-events from the event queue that were + // generated by the XUngrabPointer + XEvent ev; + XSync(g_display, False); + while(XCheckMaskEvent(g_display, EnterWindowMask, &ev)); +} + +void handle_motion_event(XEvent* ev) { + if (!g_drag_monitor) { return; } + if (!g_win_drag_client) return; + if (!g_drag_function) return; + if (ev->type != MotionNotify) return; + // get newest motion notification + while (XCheckMaskEvent(g_display, ButtonMotionMask, ev)); + // call function that handles it + g_drag_function(&(ev->xmotion)); +} + +bool mouse_is_dragging() { + return g_drag_function != NULL; +} + +static void mouse_binding_free(void* voidmb) { + MouseBinding* mb = (MouseBinding*)voidmb; + if (!mb) return; + argv_free(mb->argc, mb->argv); + g_free(mb); +} + +int mouse_unbind_all() { + g_list_free_full(g_mouse_binds, mouse_binding_free); + g_mouse_binds = NULL; + HSClient* client = get_current_client(); + if (client) { + grab_client_buttons(client, true); + } + return 0; +} + +int mouse_binding_equals(MouseBinding* a, MouseBinding* b) { + if((REMOVEBUTTONMASK(CLEANMASK(a->modifiers)) + == REMOVEBUTTONMASK(CLEANMASK(b->modifiers))) + && (a->button == b->button)) { + return 0; + } else { + return -1; + } +} + +int mouse_bind_command(int argc, char** argv, GString* output) { + if (argc < 3) { + return HERBST_NEED_MORE_ARGS; + } + unsigned int modifiers = 0; + char* string = argv[1]; + if (!string2modifiers(string, &modifiers)) { + g_string_append_printf(output, + "%s: Modifier \"%s\" does not exist\n", argv[0], string); + return HERBST_INVALID_ARGUMENT; + } + // last one is the mouse button + const char* last_token = strlasttoken(string, KEY_COMBI_SEPARATORS); + unsigned int button = string2button(last_token); + if (button == 0) { + g_string_append_printf(output, + "%s: Unknown mouse button \"%s\"\n", argv[0], last_token); + return HERBST_INVALID_ARGUMENT; + } + MouseFunction function = string2mousefunction(argv[2]); + if (!function) { + g_string_append_printf(output, + "%s: Unknown mouse action \"%s\"\n", argv[0], argv[2]); + return HERBST_INVALID_ARGUMENT; + } + + // actually create a binding + MouseBinding* mb = g_new(MouseBinding, 1); + mb->button = button; + mb->modifiers = modifiers; + mb->action = function; + mb->argc = argc - 3; + mb->argv = argv_duplicate(argc - 3, argv + 3);; + g_mouse_binds = g_list_prepend(g_mouse_binds, mb); + HSClient* client = get_current_client(); + if (client) { + grab_client_buttons(client, true); + } + return 0; +} + +MouseFunction string2mousefunction(char* name) { + static struct { + const char* name; + MouseFunction function; + } table[] = { + { "move", mouse_initiate_move }, + { "zoom", mouse_initiate_zoom }, + { "resize", mouse_initiate_resize }, + { "call", mouse_call_command }, + }; + int i; + for (i = 0; i < LENGTH(table); i++) { + if (!strcmp(table[i].name, name)) { + return table[i].function; + } + } + return NULL; +} + +static struct { + const char* name; + unsigned int button; +} string2button_table[] = { + { "Button1", Button1 }, + { "Button2", Button2 }, + { "Button3", Button3 }, + { "Button4", Button4 }, + { "Button5", Button5 }, + { "B1", Button1 }, + { "B2", Button2 }, + { "B3", Button3 }, + { "B4", Button4 }, + { "B5", Button5 }, +}; +unsigned int string2button(const char* name) { + for (int i = 0; i < LENGTH(string2button_table); i++) { + if (!strcmp(string2button_table[i].name, name)) { + return string2button_table[i].button; + } + } + return 0; +} + + +void complete_against_mouse_buttons(const char* needle, char* prefix, GString* output) { + for (int i = 0; i < LENGTH(string2button_table); i++) { + const char* buttonname = string2button_table[i].name; + try_complete_prefix(needle, buttonname, prefix, output); + } +} + +MouseBinding* mouse_binding_find(unsigned int modifiers, unsigned int button) { + MouseBinding mb = { 0 }; + mb.modifiers = modifiers; + mb.button = button; + GList* elem = g_list_find_custom(g_mouse_binds, &mb, + (GCompareFunc)mouse_binding_equals); + return elem ? ((MouseBinding*)elem->data) : NULL; +} + +static void grab_client_button(MouseBinding* bind, HSClient* client) { + unsigned int modifiers[] = { 0, LockMask, *g_numlockmask_ptr, *g_numlockmask_ptr|LockMask }; + for(int j = 0; j < LENGTH(modifiers); j++) { + XGrabButton(g_display, bind->button, + bind->modifiers | modifiers[j], + client->window, False, ButtonPressMask | ButtonReleaseMask, + GrabModeAsync, GrabModeSync, None, None); + } +} + +void grab_client_buttons(HSClient* client, bool focused) { + update_numlockmask(); + XUngrabButton(g_display, AnyButton, AnyModifier, client->window); + if (focused) { + g_list_foreach(g_mouse_binds, (GFunc)grab_client_button, client); + } + unsigned int btns[] = { Button1, Button2, Button3 }; + for (int i = 0; i < LENGTH(btns); i++) { + XGrabButton(g_display, btns[i], AnyModifier, client->window, False, + ButtonPressMask|ButtonReleaseMask, GrabModeSync, + GrabModeSync, None, None); + } +} + +void mouse_function_move(XMotionEvent* me) { + int x_diff = me->x_root - g_button_drag_start.x; + int y_diff = me->y_root - g_button_drag_start.y; + g_win_drag_client->float_size = g_win_drag_start; + g_win_drag_client->float_size.x += x_diff; + g_win_drag_client->float_size.y += y_diff; + // snap it to other windows + int dx, dy; + client_snap_vector(g_win_drag_client, g_drag_monitor, + SNAP_EDGE_ALL, &dx, &dy); + g_win_drag_client->float_size.x += dx; + g_win_drag_client->float_size.y += dy; + client_resize_floating(g_win_drag_client, g_drag_monitor); +} + +void mouse_function_resize(XMotionEvent* me) { + int x_diff = me->x_root - g_button_drag_start.x; + int y_diff = me->y_root - g_button_drag_start.y; + g_win_drag_client->float_size = g_win_drag_start; + // relative x/y coords in drag window + HSMonitor* m = g_drag_monitor; + int rel_x = monitor_get_relative_x(m, g_button_drag_start.x) - g_win_drag_start.x; + int rel_y = monitor_get_relative_y(m, g_button_drag_start.y) - g_win_drag_start.y; + bool top = false; + bool left = false; + if (rel_y < g_win_drag_start.height/2) { + top = true; + y_diff *= -1; + } + if (rel_x < g_win_drag_start.width/2) { + left = true; + x_diff *= -1; + } + // avoid an overflow + int new_width = g_win_drag_client->float_size.width + x_diff; + int new_height = g_win_drag_client->float_size.height + y_diff; + int min_width = WINDOW_MIN_WIDTH; + int min_height = WINDOW_MIN_HEIGHT; + HSClient* client = g_win_drag_client; + if (client->sizehints_floating) { + min_width = MAX(WINDOW_MIN_WIDTH, client->minw); + min_height = MAX(WINDOW_MIN_HEIGHT, client->minh); + } + if (new_width < min_width) { + new_width = min_width; + x_diff = new_width - g_win_drag_client->float_size.width; + } + if (new_height < min_height) { + new_height = min_height; + y_diff = new_height - g_win_drag_client->float_size.height; + } + if (left) g_win_drag_client->float_size.x -= x_diff; + if (top) g_win_drag_client->float_size.y -= y_diff; + g_win_drag_client->float_size.width = new_width; + g_win_drag_client->float_size.height = new_height; + // snap it to other windows + int dx, dy; + int snap_flags = 0; + if (left) snap_flags |= SNAP_EDGE_LEFT; + else snap_flags |= SNAP_EDGE_RIGHT; + if (top) snap_flags |= SNAP_EDGE_TOP; + else snap_flags |= SNAP_EDGE_BOTTOM; + client_snap_vector(g_win_drag_client, g_drag_monitor, + (SnapFlags)snap_flags, &dx, &dy); + if (left) { + g_win_drag_client->float_size.x += dx; + dx *= -1; + } + if (top) { + g_win_drag_client->float_size.y += dy; + dy *= -1; + } + g_win_drag_client->float_size.width += dx; + g_win_drag_client->float_size.height += dy; + client_resize_floating(g_win_drag_client, g_drag_monitor); +} + +void mouse_function_zoom(XMotionEvent* me) { + // stretch, where center stays at the same position + int x_diff = me->x_root - g_button_drag_start.x; + int y_diff = me->y_root - g_button_drag_start.y; + // relative x/y coords in drag window + HSMonitor* m = g_drag_monitor; + int rel_x = monitor_get_relative_x(m, g_button_drag_start.x) - g_win_drag_start.x; + int rel_y = monitor_get_relative_y(m, g_button_drag_start.y) - g_win_drag_start.y; + int cent_x = g_win_drag_start.x + g_win_drag_start.width / 2; + int cent_y = g_win_drag_start.y + g_win_drag_start.height / 2; + if (rel_x < g_win_drag_start.width/2) { + x_diff *= -1; + } + if (rel_y < g_win_drag_start.height/2) { + y_diff *= -1; + } + HSClient* client = g_win_drag_client; + + // avoid an overflow + int new_width = g_win_drag_start.width + 2 * x_diff; + int new_height = g_win_drag_start.height + 2 * y_diff; + // apply new rect + client->float_size = g_win_drag_start; + client->float_size.x = cent_x - new_width / 2; + client->float_size.y = cent_y - new_height / 2; + client->float_size.width = new_width; + client->float_size.height = new_height; + // snap it to other windows + int right_dx, bottom_dy; + int left_dx, top_dy; + // we have to distinguish the direction in which we zoom + client_snap_vector(g_win_drag_client, m, + (SnapFlags)(SNAP_EDGE_BOTTOM | SNAP_EDGE_RIGHT), &right_dx, &bottom_dy); + client_snap_vector(g_win_drag_client, m, + (SnapFlags)(SNAP_EDGE_TOP | SNAP_EDGE_LEFT), &left_dx, &top_dy); + // e.g. if window snaps by vector (3,3) at topleft, window has to be shrinked + // but if the window snaps by vector (3,3) at bottomright, window has to grow + if (abs(right_dx) < abs(left_dx)) { + right_dx = -left_dx; + } + if (abs(bottom_dy) < abs(top_dy)) { + bottom_dy = -top_dy; + } + new_width += 2 * right_dx; + new_height += 2 * bottom_dy; + applysizehints(client, &new_width, &new_height); + // center window again + client->float_size.width = new_width; + client->float_size.height = new_height; + client->float_size.x = cent_x - new_width / 2; + client->float_size.y = cent_y - new_height / 2; + client_resize_floating(g_win_drag_client, g_drag_monitor); +} + +struct SnapData { + HSClient* client; + Rectangle rect; + enum SnapFlags flags; + int dx, dy; // the vector from client to other to make them snap +}; + +bool is_point_between(int point, int left, int right) { + return (point < right && point >= left); +} + +// tells if the intervals [a_left, a_right) [b_left, b_right) intersect +bool intervals_intersect(int a_left, int a_right, int b_left, int b_right) { + return (b_left < a_right) && (a_left < b_right); +} + +// compute vector to snap a point to an edge +static void snap_1d(int x, int edge, int* delta) { + // whats the vector from subject to edge? + int cur_delta = edge - x; + // if distance is smaller then all other deltas + if (abs(cur_delta) < abs(*delta)) { + // then snap it, i.e. save vector + *delta = cur_delta; + } +} + +static int client_snap_helper(HSClient* candidate, struct SnapData* d) { + if (candidate == d->client) { + return 0; + } + Rectangle subject = d->rect; + Rectangle other = candidate->dec.last_outer_rect; + // increase other by snap gap + other.x -= *g_snap_gap; + other.y -= *g_snap_gap; + other.width += *g_snap_gap * 2; + other.height += *g_snap_gap * 2; + if (intervals_intersect(other.y, other.y + other.height, subject.y, subject.y + subject.height)) { + // check if x can snap to the right + if (d->flags & SNAP_EDGE_RIGHT) { + snap_1d(subject.x + subject.width, other.x, &d->dx); + } + // or to the left + if (d->flags & SNAP_EDGE_LEFT) { + snap_1d(subject.x, other.x + other.width, &d->dx); + } + } + if (intervals_intersect(other.x, other.x + other.width, subject.x, subject.x + subject.width)) { + // if we can snap to the top + if (d->flags & SNAP_EDGE_TOP) { + snap_1d(subject.y, other.y + other.height, &d->dy); + } + // or to the bottom + if (d->flags & SNAP_EDGE_BOTTOM) { + snap_1d(subject.y + subject.height, other.y, &d->dy); + } + } + return 0; +} + +// get the vector to snap a client to it's neighbour +void client_snap_vector(struct HSClient* client, struct HSMonitor* monitor, + enum SnapFlags flags, + int* return_dx, int* return_dy) { + struct SnapData d; + HSTag* tag = monitor->tag; + int distance = (*g_snap_distance > 0) ? *g_snap_distance : 0; + // init delta + *return_dx = 0; + *return_dy = 0; + if (!distance) { + // nothing to do + return; + } + d.client = client; + // translate client rectangle to global coordinates + d.rect = client_outer_floating_rect(client); + d.rect.x += monitor->rect.x + monitor->pad_left; + d.rect.y += monitor->rect.y + monitor->pad_up; + d.flags = flags; + d.dx = distance; + d.dy = distance; + + // snap to monitor edges + HSMonitor* m = g_drag_monitor; + if (flags & SNAP_EDGE_TOP) { + snap_1d(d.rect.y, m->rect.y + m->pad_up + *g_snap_gap, &d.dy); + } + if (flags & SNAP_EDGE_LEFT) { + snap_1d(d.rect.x, m->rect.x + m->pad_left + *g_snap_gap, &d.dx); + } + if (flags & SNAP_EDGE_RIGHT) { + snap_1d(d.rect.x + d.rect.width, m->rect.x + m->rect.width - m->pad_right - *g_snap_gap, &d.dx); + } + if (flags & SNAP_EDGE_BOTTOM) { + snap_1d(d.rect.y + d.rect.height, m->rect.y + m->rect.height - m->pad_down - *g_snap_gap, &d.dy); + } + + // snap to other clients + frame_foreach_client(tag->frame, (ClientAction)client_snap_helper, &d); + + // write back results + if (abs(d.dx) < abs(distance)) { + *return_dx = d.dx; + } + if (abs(d.dy) < abs(distance)) { + *return_dy = d.dy; + } +} + diff -Nru herbstluftwm-0.6.2/src/mouse.h herbstluftwm-0.7.0/src/mouse.h --- herbstluftwm-0.6.2/src/mouse.h 2014-03-26 23:54:22.000000000 +0000 +++ herbstluftwm-0.7.0/src/mouse.h 2015-10-14 13:27:40.000000000 +0000 @@ -47,7 +47,7 @@ int mouse_unbind_all(); MouseBinding* mouse_binding_find(unsigned int modifiers, unsigned int button); -unsigned int string2button(char* name); +unsigned int string2button(const char* name); MouseFunction string2mousefunction(char* name); void grab_client_buttons(struct HSClient* client, bool focused); @@ -76,7 +76,7 @@ void mouse_function_resize(XMotionEvent* me); void mouse_function_zoom(XMotionEvent* me); -void complete_against_mouse_buttons(char* needle, char* prefix, GString* output); +void complete_against_mouse_buttons(const char* needle, char* prefix, GString* output); #endif diff -Nru herbstluftwm-0.6.2/src/object.c herbstluftwm-0.7.0/src/object.c --- herbstluftwm-0.6.2/src/object.c 2014-03-26 23:54:22.000000000 +0000 +++ herbstluftwm-0.7.0/src/object.c 1970-01-01 00:00:00.000000000 +0000 @@ -1,1044 +0,0 @@ -/** Copyright 2011-2013 Thorsten Wißmann. All rights reserved. - * - * This software is licensed under the "Simplified BSD License". - * See LICENSE for details */ - -#include "object.h" -#include "command.h" -#include "utils.h" -#include "assert.h" -#include "globals.h" -#include "ipc-protocol.h" - -#include -#include -#include - -typedef struct { - char* name; - HSObject* child; -} HSObjectChild; - -static void hsobjectchild_destroy(HSObjectChild* oc); -static HSObjectChild* hsobjectchild_create(const char* name, HSObject* obj); -static void hsattribute_free(HSAttribute* attr); - -static HSObject g_root_object; -static HSObject* g_tmp_object; - -void object_tree_init() { - hsobject_init(&g_root_object); - g_tmp_object = hsobject_create_and_link(&g_root_object, TMP_OBJECT_PATH); - -} - -void object_tree_destroy() { - hsobject_unlink_and_destroy(&g_root_object, g_tmp_object); - hsobject_free(&g_root_object); -} - -HSObject* hsobject_root() { - return &g_root_object; -} - -bool hsobject_init(HSObject* obj) { - obj->attributes = NULL; - obj->attribute_count = 0; - obj->children = NULL; - return true; -} - -void hsobject_free(HSObject* obj) { - for (int i = 0; i < obj->attribute_count; i++) { - hsattribute_free(obj->attributes + i); - } - g_free(obj->attributes); - g_list_free_full(obj->children, (GDestroyNotify)hsobjectchild_destroy); -} - -static void hsattribute_free(HSAttribute* attr) { - if (attr->user_data) { - g_free(attr->name); - if (attr->type == HSATTR_TYPE_STRING) { - g_string_free(attr->user_data->str, true); - } - g_free(attr->user_data); - } - if (attr->unparsed_value) { - g_string_free(attr->unparsed_value, true); - } -} - -HSObject* hsobject_create() { - HSObject* obj = g_new(HSObject, 1); - hsobject_init(obj); - return obj; -} - -void hsobject_destroy(HSObject* obj) { - if (!obj) return; - hsobject_free(obj); - g_free(obj); -} - -HSObject* hsobject_create_and_link(HSObject* parent, const char* name) { - HSObject* obj = hsobject_create(); - hsobject_link(parent, obj, name); - return obj; -} - -void hsobject_unlink_and_destroy(HSObject* parent, HSObject* child) { - hsobject_unlink(parent, child); - hsobject_destroy(child); -} - -static HSObjectChild* hsobjectchild_create(const char* name, HSObject* obj) { - HSObjectChild* oc = g_new(HSObjectChild, 1); - oc->name = g_strdup(name); - oc->child = obj; - return oc; -} - -static void hsobjectchild_destroy(HSObjectChild* oc) { - if (!oc) return; - g_free(oc->name); - g_free(oc); -} - -struct HSObjectComplChild { - char* needle; - char* prefix; - GString* curname; - GString* output; -}; - -static void completion_helper(HSObjectChild* child, struct HSObjectComplChild* data) { - g_string_assign(data->curname, child->name); - g_string_append_c(data->curname, OBJECT_PATH_SEPARATOR); - try_complete_prefix_partial(data->needle, data->curname->str, data->prefix, data->output); -} - -void hsobject_complete_children(HSObject* obj, char* needle, char* prefix, GString* output) { - struct HSObjectComplChild data = { - needle, - prefix, - g_string_new(""), - output - }; - g_list_foreach(obj->children, (GFunc) completion_helper, &data); - g_string_free(data.curname, true); -} - -void hsobject_complete_attributes(HSObject* obj, bool user_only, char* needle, - char* prefix, GString* output) { - for (int i = 0; i < obj->attribute_count; i++) { - HSAttribute* attr = obj->attributes + i; - if (user_only && !attr->user_attribute) { - // do not complete default attributes if user_only is set - continue; - } - try_complete_prefix(needle, attr->name, prefix, output); - } -} - -static int child_check_name(HSObjectChild* child, char* name) { - return strcmp(child->name, name); -} - -void hsobject_link(HSObject* parent, HSObject* child, const char* name) { - GList* elem = g_list_find_custom(parent->children, name, - (GCompareFunc)child_check_name); - if (!elem) { - // create a new child node - HSObjectChild* oc = hsobjectchild_create(name, child); - parent->children = g_list_append(parent->children, oc); - } else { - // replace it - HSObjectChild* oc = (HSObjectChild*) elem->data; - oc->child = child; - } -} - -static int child_check_object(HSObjectChild* child, HSObject* obj) { - // return 0 if they are identical - return (child->child == obj) ? 0 : 1; -} - -static void hsobject_unlink_helper(HSObject* parent, GCompareFunc f, void* data) { - GList* elem = parent->children; - while (elem) { - elem = g_list_find_custom(elem, data, f); - if (elem) { - GList* next = elem->next; - hsobjectchild_destroy((HSObjectChild*)elem->data); - parent->children = g_list_delete_link(parent->children, elem); - elem = next; - } - } -} - -void hsobject_unlink(HSObject* parent, HSObject* child) { - hsobject_unlink_helper(parent, - (GCompareFunc)child_check_object, - child); -} - -void hsobject_unlink_by_name(HSObject* parent, char* name) { - hsobject_unlink_helper(parent, - (GCompareFunc)child_check_name, - name); -} - -void hsobject_link_rename(HSObject* parent, char* oldname, char* newname) { - if (!strcmp(oldname, newname)) { - return; - } - // remove object with target name - hsobject_unlink_by_name(parent, newname); - GList* elem = g_list_find_custom(parent->children, - oldname, - (GCompareFunc)child_check_name); - HSObjectChild* child = (HSObjectChild*)elem->data; - g_free(child->name); - child->name = g_strdup(newname); -} - -void hsobject_link_rename_object(HSObject* parent, HSObject* child, char* newname) { - // remove occurrences of that object - hsobject_unlink(parent, child); - // link it again (replacing any object with newname) - hsobject_link(parent, child, newname); -} - -HSObject* hsobject_find_child(HSObject* obj, const char* name) { - GList* elem = g_list_find_custom(obj->children, name, - (GCompareFunc)child_check_name); - if (elem) { - return ((HSObjectChild*)(elem->data))->child; - } else { - return NULL; - } -} - -HSAttribute* hsobject_find_attribute(HSObject* obj, const char* name) { - for (int i = 0; i < obj->attribute_count; i++) { - if (!strcmp(name, obj->attributes[i].name)) { - return obj->attributes + i; - } - } - return NULL; -} - -void hsobject_set_attributes_always_callback(HSObject* obj) { - for (int i = 0; i < obj->attribute_count; i++) { - obj->attributes[i].always_callback = true; - } -} - -static void print_child_name(HSObjectChild* child, GString* output) { - g_string_append_printf(output, " %s%c\n", child->name, OBJECT_PATH_SEPARATOR); -} - -void hsattribute_append_to_string(HSAttribute* attribute, GString* output) { - switch (attribute->type) { - case HSATTR_TYPE_BOOL: - if (*(attribute->value.b)) { - g_string_append_printf(output, "true"); - } else { - g_string_append_printf(output, "false"); - } - break; - case HSATTR_TYPE_INT: - g_string_append_printf(output, "%d", *attribute->value.i); - break; - case HSATTR_TYPE_UINT: - g_string_append_printf(output, "%u", *attribute->value.u); - break; - case HSATTR_TYPE_STRING: - g_string_append_printf(output, "%s", (*attribute->value.str)->str); - break; - case HSATTR_TYPE_COLOR: - g_string_append_printf(output, "%s", attribute->unparsed_value->str); - break; - case HSATTR_TYPE_CUSTOM: - attribute->value.custom(attribute->data ? attribute->data - : attribute->object->data, output); - break; - case HSATTR_TYPE_CUSTOM_INT: - g_string_append_printf(output, "%d", - attribute->value.custom_int(attribute->data ? attribute->data - : attribute->object->data)); - break; - } -} - -GString* hsattribute_to_string(HSAttribute* attribute) { - GString* str = g_string_new(""); - hsattribute_append_to_string(attribute, str); - return str; -} - -int attr_command(int argc, char* argv[], GString* output) { - char* path = (argc < 2) ? "" : argv[1]; - char* unparsable; - GString* errormsg = g_string_new(""); - HSObject* obj = hsobject_parse_path_verbose(path, &unparsable, errormsg); - HSAttribute* attribute = NULL; - if (strcmp("", unparsable)) { - // if object could not be parsed, try attribute - attribute = hsattribute_parse_path_verbose(path, errormsg); - obj = NULL; - } - if (!obj && !attribute) { - // if nothing was found - g_string_append(output, errormsg->str); - g_string_free(errormsg, true); - return HERBST_INVALID_ARGUMENT; - } else { - g_string_free(errormsg, true); - } - char* new_value = (argc >= 3) ? argv[2] : NULL; - if (obj && new_value) { - g_string_append_printf(output, - "%s: Can not assign value \"%s\" to object \"%s\",", - argv[0], new_value, path); - } else if (obj && !new_value) { - // list children - int childcount = g_list_length(obj->children); - g_string_append_printf(output, "%d children%c\n", childcount, - childcount ? ':' : '.'); - g_list_foreach(obj->children, (GFunc) print_child_name, output); - if (childcount > 0) { - g_string_append_printf(output, "\n"); - } - // list attributes - g_string_append_printf(output, "%zu attributes", obj->attribute_count); - if (obj->attribute_count > 0) { - g_string_append_printf(output, ":\n"); - g_string_append_printf(output, " .---- type\n"); - g_string_append_printf(output, " | .-- writeable\n"); - g_string_append_printf(output, " V V\n"); - } else { - g_string_append_printf(output, ".\n"); - } - for (int i = 0; i < obj->attribute_count; i++) { - HSAttribute* a = obj->attributes + i; - char write = hsattribute_is_read_only(a) ? '-' : 'w'; - char t = hsattribute_type_indicator(a->type); - g_string_append_printf(output, " %c %c %-20s = ", t, write, a->name); - if (a->type == HSATTR_TYPE_STRING) { - g_string_append_c(output, '\"'); - } - hsattribute_append_to_string(a, output); - if (a->type == HSATTR_TYPE_STRING) { - g_string_append_c(output, '\"'); - } - g_string_append_c(output, '\n'); - } - } else if (new_value) { // && (attribute) - return hsattribute_assign(attribute, new_value, output); - } else { // !new_value && (attribute) - hsattribute_append_to_string(attribute, output); - } - return 0; -} - -static void object_append_caption(HSTree tree, GString* output) { - HSObjectChild* oc = (HSObjectChild*) tree; - g_string_append(output, oc->name); -} - -static size_t object_child_count(HSTree tree) { - HSObjectChild* oc = (HSObjectChild*) tree; - return g_list_length(oc->child->children); -} - -static HSTreeInterface object_nth_child(HSTree tree, size_t idx) { - HSObjectChild* oc = (HSObjectChild*) tree; - assert(oc->child); - HSTreeInterface intf = { - .nth_child = object_nth_child, - .data = (HSTree) g_list_nth_data(oc->child->children, idx), - .destructor = NULL, - .child_count = object_child_count, - .append_caption = object_append_caption, - }; - return intf; -} - -HSObject* hsobject_by_path(char* path) { - HSObject* obj = hsobject_parse_path(path, &path); - if (!strcmp("", path)) { - return obj; - } else { - // an invalid path was given if it was not parsed entirely - return NULL; - } -} - -HSObject* hsobject_parse_path_verbose(char* path, char** unparsable, - GString* output) { - char* origpath = path; - char* pathdup = strdup(path); - char* curname = pathdup; - char* lastname = "root"; - char seps[] = { OBJECT_PATH_SEPARATOR, '\0' }; - // replace all separators by null bytes - g_strdelimit(curname, seps, '\0'); - HSObject* obj = hsobject_root(); - HSObject* child; - // skip separator characters - while (*path == OBJECT_PATH_SEPARATOR) { - path++; - curname++; - } - while (strcmp("", path)) { - child = hsobject_find_child(obj, curname); - if (!child) { - if (output) { - g_string_append_printf(output, "Invalid path \"%s\": ", origpath); - g_string_append_printf(output, "No child \"%s\" in object %s\n", - curname, lastname); - } - break; - } - lastname = curname; - obj = child; - // skip the name - path += strlen(curname); - curname += strlen(curname); - // skip separator characters - while (*path == OBJECT_PATH_SEPARATOR) { - path++; - curname++; - } - } - *unparsable = path; - free(pathdup); - return obj; -} - -HSObject* hsobject_parse_path(char* path, char** unparsable) { - return hsobject_parse_path_verbose(path, unparsable, NULL); -} - -HSAttribute* hsattribute_parse_path_verbose(char* path, GString* output) { - GString* object_error = g_string_new(""); - HSAttribute* attr; - char* unparsable; - HSObject* obj = hsobject_parse_path_verbose(path, &unparsable, object_error); - if (obj == NULL || strchr(unparsable, OBJECT_PATH_SEPARATOR) != NULL) { - // if there is still another path separator - // then unparsable is more than just the attribute name. - g_string_append(output, object_error->str); - attr = NULL; - } else { - // if there is no path remaining separator, then unparsable contains - // the attribute name - attr = hsobject_find_attribute(obj, unparsable); - if (!attr) { - GString* obj_path = g_string_new(path); - g_string_truncate(obj_path, unparsable - path); - g_string_append_printf(output, - "Unknown attribute \"%s\" in object \"%s\".\n", - unparsable, obj_path->str); - g_string_free(obj_path, true); - } - } - g_string_free(object_error, true); - return attr; -} - -HSAttribute* hsattribute_parse_path(char* path) { - GString* out = g_string_new(""); - HSAttribute* attr = hsattribute_parse_path_verbose(path, out); - if (!attr) { - HSError("Cannot parse %s: %s", path, out->str); - } - g_string_free(out, true); - return attr; -} - -int print_object_tree_command(int argc, char* argv[], GString* output) { - char* unparsable; - char* path = (argc < 2) ? "" : argv[1]; - HSObjectChild oc = { - .name = path, - .child = hsobject_parse_path_verbose(path, &unparsable, output), - }; - if (strcmp("", unparsable)) { - return HERBST_INVALID_ARGUMENT; - } - HSTreeInterface intf = { - .nth_child = object_nth_child, - .data = &oc, - .destructor = NULL, - .child_count = object_child_count, - .append_caption = object_append_caption, - }; - tree_print_to(&intf, output); - return 0; -} - -void hsobject_set_attributes(HSObject* obj, HSAttribute* attributes) { - // calculate new size - size_t count; - for (count = 0; attributes[count].name != NULL; count++) - ; - obj->attributes = g_renew(HSAttribute, obj->attributes, count); - obj->attribute_count = count; - memcpy(obj->attributes, attributes, count * sizeof(HSAttribute)); - for (int i = 0; i < count; i++) { - obj->attributes[i].object = obj; - } -} - -int hsattribute_get_command(int argc, char* argv[], GString* output) { - if (argc < 2) { - return HERBST_NEED_MORE_ARGS; - } - HSAttribute* attr = hsattribute_parse_path_verbose(argv[1], output); - if (!attr) { - return HERBST_INVALID_ARGUMENT; - } - hsattribute_append_to_string(attr, output); - return 0; -} - -int hsattribute_set_command(int argc, char* argv[], GString* output) { - if (argc < 3) { - return HERBST_NEED_MORE_ARGS; - } - HSAttribute* attr = hsattribute_parse_path_verbose(argv[1], output); - if (!attr) { - return HERBST_INVALID_ARGUMENT; - } - return hsattribute_assign(attr, argv[2], output); -} - -bool hsattribute_is_read_only(HSAttribute* attr) { - bool custom = attr->type == HSATTR_TYPE_CUSTOM - || attr->type == HSATTR_TYPE_CUSTOM_INT; - assert(!(custom && attr->on_change)); - if (custom) return attr->change_custom == NULL; - else return attr->on_change == NULL; -} - -int hsattribute_assign(HSAttribute* attr, const char* new_value_str, GString* output) { - if (hsattribute_is_read_only(attr)) { - g_string_append_printf(output, - "Can not write read-only attribute \"%s\"\n", - attr->name); - return HERBST_FORBIDDEN; - } - - bool error = false; - union { - bool b; - int i; - unsigned int u; - GString* str; - HSColor color; - } new_value, old_value; - bool nothing_to_do = false; - -#define ATTR_DO_ASSIGN_COMPARE(NAME,MEM) \ - do { \ - if (error) { \ - g_string_append_printf(output, \ - "Can not parse "NAME" from \"%s\"", \ - new_value_str); \ - } else { \ - old_value.MEM = *attr->value.MEM; \ - if (old_value.MEM == new_value.MEM) { \ - nothing_to_do = true; \ - } else { \ - *attr->value.MEM = new_value.MEM; \ - } \ - } \ - } while (0); - // change the value and backup the old value - switch (attr->type) { - case HSATTR_TYPE_BOOL: - new_value.b = string_to_bool_error(new_value_str, - *attr->value.b, - &error); - ATTR_DO_ASSIGN_COMPARE("boolean", b); - break; - - case HSATTR_TYPE_INT: - error = (1 != sscanf(new_value_str, "%d", &new_value.i)); - ATTR_DO_ASSIGN_COMPARE("integer", i); - break; - - case HSATTR_TYPE_UINT: - error = (1 != sscanf(new_value_str, "%u", &new_value.u)); - ATTR_DO_ASSIGN_COMPARE("unsigned integer", u); - break; - - case HSATTR_TYPE_STRING: - if (!strcmp(new_value_str, (*attr->value.str)->str)) { - nothing_to_do = true; - } else { - old_value.str = g_string_new((*attr->value.str)->str); - g_string_assign(*attr->value.str, new_value_str); - } - break; - - case HSATTR_TYPE_COLOR: - error = !getcolor_error(new_value_str, &new_value.color); - if (error) { - g_string_append_printf(output, - "\"%s\" is not a valid color.", new_value_str); - break; - } - if (!strcmp(new_value_str, (attr->unparsed_value)->str)) { - nothing_to_do = true; - } else { - old_value.color = *attr->value.color; - *attr->value.color = new_value.color; - } - break; - - case HSATTR_TYPE_CUSTOM: break; - case HSATTR_TYPE_CUSTOM_INT: break; - } - if (error) { - return HERBST_INVALID_ARGUMENT; - } - if (attr->always_callback) { - nothing_to_do = false; // pretend that there was a change - } - if (nothing_to_do) { - return 0; - } - - GString* old_unparsed_value = attr->unparsed_value; - if (attr->unparsed_value) attr->unparsed_value = g_string_new(new_value_str); - - // ask the attribute about the change - GString* errormsg = NULL; - if (attr->on_change) errormsg = attr->on_change(attr); - else errormsg = attr->change_custom(attr, new_value_str); - int exit_status = 0; - if (errormsg && errormsg->len > 0) { - exit_status = HERBST_INVALID_ARGUMENT; - // print the message - if (errormsg->str[errormsg->len - 1] == '\n') { - g_string_truncate(errormsg, errormsg->len - 1); - } - g_string_append_printf(output, - "Can not write attribute \"%s\": %s\n", - attr->name, - errormsg->str); - g_string_free(errormsg, true); - // restore old value - if (old_unparsed_value) { - g_string_free(attr->unparsed_value, true); - attr->unparsed_value = old_unparsed_value; - } - switch (attr->type) { - case HSATTR_TYPE_BOOL: *attr->value.b = old_value.b; break; - case HSATTR_TYPE_INT: *attr->value.i = old_value.i; break; - case HSATTR_TYPE_UINT: *attr->value.u = old_value.u; break; - case HSATTR_TYPE_STRING: - g_string_assign(*attr->value.str, old_value.str->str); - break; - case HSATTR_TYPE_COLOR: - *attr->value.color = old_value.color; break; - break; - case HSATTR_TYPE_CUSTOM: break; - case HSATTR_TYPE_CUSTOM_INT: break; - } - } - // free old_value - switch (attr->type) { - case HSATTR_TYPE_BOOL: break; - case HSATTR_TYPE_INT: break; - case HSATTR_TYPE_UINT: break; - case HSATTR_TYPE_STRING: - g_string_free(old_value.str, true); - break; - case HSATTR_TYPE_COLOR: - g_string_assign(attr->unparsed_value, new_value_str); - break; - case HSATTR_TYPE_CUSTOM: break; - case HSATTR_TYPE_CUSTOM_INT: break; - } - if (old_unparsed_value) { - g_string_free(old_unparsed_value, true); - } - return exit_status; -} - - -int substitute_command(int argc, char* argv[], GString* output) { - // usage: substitute identifier attribute command [args ...] - // 0 1 2 3 - if (argc < 4) { - return HERBST_NEED_MORE_ARGS; - } - char* identifier = argv[1]; - HSAttribute* attribute = hsattribute_parse_path_verbose(argv[2], output); - if (!attribute) { - return HERBST_INVALID_ARGUMENT; - } - GString* attribute_string = hsattribute_to_string(attribute); - char* repl = attribute_string->str; - - (void) SHIFT(argc, argv); // remove command name - (void) SHIFT(argc, argv); // remove identifier - (void) SHIFT(argc, argv); // remove attribute - - int status = call_command_substitute(identifier, repl, argc, argv, output); - g_string_free(attribute_string, true); - return status; -} - -GString* ATTR_ACCEPT_ALL(HSAttribute* attr) { - (void) attr; - return NULL; -} - -int compare_command(int argc, char* argv[], GString* output) { - // usage: compare attribute operator constant - if (argc < 4) { - return HERBST_NEED_MORE_ARGS; - } - HSAttribute* attr = hsattribute_parse_path_verbose(argv[1], output); - if (!attr) { - return HERBST_INVALID_ARGUMENT; - } - char* op = argv[2]; - char* rvalue = argv[3]; - if (attr->type == HSATTR_TYPE_INT - || attr->type == HSATTR_TYPE_UINT - || attr->type == HSATTR_TYPE_CUSTOM_INT) - { - long long l; - long long r; - if (1 != sscanf(rvalue, "%lld", &r)) { - g_string_append_printf(output, - "Can not parse integer from \"%s\"\n", - rvalue); - return HERBST_INVALID_ARGUMENT; - } - switch (attr->type) { - case HSATTR_TYPE_INT: l = *attr->value.i; break; - case HSATTR_TYPE_UINT: l = *attr->value.u; break; - case HSATTR_TYPE_CUSTOM_INT: - l = attr->value.custom_int(attr->data ? attr->data : attr->object->data); - break; - default: return HERBST_UNKNOWN_ERROR; break; - } - struct { - char* name; - bool result; - } eval[] = { - { "=", l == r }, - { "!=", l != r }, - { "le", l <= r }, - { "lt", l < r }, - { "ge", l >= r }, - { "gt", l > r }, - }; - int result = -1; - for (int i = 0; i < LENGTH(eval); i++) { - if (!strcmp(eval[i].name, op)) { - result = !eval[i].result; // make false -> 1, true -> 0 - } - } - if (result == -1) { - g_string_append_printf(output, "Invalid operator \"%s\"", op); - result = HERBST_INVALID_ARGUMENT; - } - return result; - } else if (attr->type == HSATTR_TYPE_BOOL) { - bool l = *attr->value.b; - bool error = false; - bool r = string_to_bool_error(rvalue, l, &error); - if (error) { - g_string_append_printf(output, "Can not parse boolean from \"%s\"\n", rvalue); - return HERBST_INVALID_ARGUMENT; - } - if (!strcmp("=", op)) return !(l == r); - if (!strcmp("!=", op)) return !(l != r); - g_string_append_printf(output, "Invalid boolean operator \"%s\"", op); - return HERBST_INVALID_ARGUMENT; - } else if (attr->type == HSATTR_TYPE_COLOR) { - HSColor l = *attr->value.color; - HSColor r = getcolor(rvalue); - if (!strcmp("=", op)) return !(l == r); - if (!strcmp("!=", op)) return !(l != r); - g_string_append_printf(output, "Invalid color operator \"%s\"", op); - return HERBST_INVALID_ARGUMENT; - } else { // STRING or CUSTOM - GString* l; - bool free_l = false; - if (attr->type == HSATTR_TYPE_STRING) { - l = *attr->value.str; - } else { // TYPE == CUSTOM - l = g_string_new(""); - attr->value.custom(attr->data ? attr->data : attr->object->data, l); - free_l = true; - } - bool equals = !strcmp(l->str, rvalue); - int status; - if (!strcmp("=", op)) status = !equals; - else if (!strcmp("!=", op)) status = equals; - else status = -1; - if (free_l) { - g_string_free(l, true); - } - if (status == -1) { - g_string_append_printf(output, "Invalid string operator \"%s\"", op); - return HERBST_INVALID_ARGUMENT; - } - return status; - } -} - -char hsattribute_type_indicator(int type) { - switch (type) { - case HSATTR_TYPE_BOOL: return 'b'; - case HSATTR_TYPE_UINT: return 'u'; - case HSATTR_TYPE_INT: return 'i'; - case HSATTR_TYPE_STRING: return 's'; - case HSATTR_TYPE_CUSTOM: return 's'; - case HSATTR_TYPE_CUSTOM_INT:return 'i'; - case HSATTR_TYPE_COLOR: return 'c'; - } - return '?'; -} - -int userattribute_command(int argc, char* argv[], GString* output) { - if (argc < 3) { - return HERBST_NEED_MORE_ARGS; - } - char* type_str = argv[1]; - char* path = argv[2]; - char* unparsable; - GString* errormsg = g_string_new(""); - HSObject* obj = hsobject_parse_path_verbose(path, &unparsable, errormsg); - if (obj == NULL || strchr(unparsable, OBJECT_PATH_SEPARATOR) != NULL) { - g_string_append(output, errormsg->str); - g_string_free(errormsg, true); - return HERBST_INVALID_ARGUMENT; - } else { - g_string_free(errormsg, true); - } - // check for an already existing attribute - if (hsobject_find_attribute(obj, unparsable)) { - g_string_append_printf(output, "Error: an attribute called \"%s\" already exists\n", - unparsable); - return HERBST_FORBIDDEN; - } - // do not check for children with that name, because they must not start - // with the USER_ATTRIBUTE_PREFIX. - // now create a new attribute named unparsable at obj - const char* prefix = USER_ATTRIBUTE_PREFIX; - if (strncmp(unparsable, prefix, strlen(prefix))) { - g_string_append(output, "Error: the name of user attributes has to "); - g_string_append_printf(output, "start with \"%s\" but yours is \"%s\"\n", - prefix, unparsable); - return HERBST_INVALID_ARGUMENT; - } - HSAttribute* attr = hsattribute_create(obj, unparsable, type_str, output); - if (!attr) { - return HERBST_INVALID_ARGUMENT; - } - attr->user_attribute = true; - return 0; -} - -HSAttribute* hsattribute_create(HSObject* obj, char* name, char* type_str, - GString* output) -{ - struct { - char* name; - int type; - } types[] = { - { "bool", HSATTR_TYPE_BOOL }, - { "uint", HSATTR_TYPE_UINT }, - { "int", HSATTR_TYPE_INT }, - { "string", HSATTR_TYPE_STRING }, - { "color", HSATTR_TYPE_COLOR }, - }; - int type = -1; - for (int i = 0; i < LENGTH(types); i++) { - if (!strcmp(type_str, types[i].name)) { - type = types[i].type; - break; - } - } - if (type < 0) { - g_string_append_printf(output, "Unknown attribute type \"%s\"\n", - type_str); - return NULL; - } - size_t count = obj->attribute_count + 1; - obj->attributes = g_renew(HSAttribute, obj->attributes, count); - obj->attribute_count = count; - // initialize object - HSAttribute* attr = obj->attributes + count - 1; - memset(attr, 0, sizeof(*attr)); - attr->object = obj; - attr->type = type; - attr->name = g_strdup(name); - attr->on_change = ATTR_ACCEPT_ALL; - attr->user_attribute = false; - attr->user_data = g_new(HSAttributeValue, 1); - switch (type) { - case HSATTR_TYPE_BOOL: - attr->user_data->b = false; - attr->value.b = &attr->user_data->b; - break; - case HSATTR_TYPE_INT: - attr->user_data->i = 0; - attr->value.i = &attr->user_data->i; - break; - case HSATTR_TYPE_UINT: - attr->user_data->u = 0; - attr->value.u = &attr->user_data->u; - break; - case HSATTR_TYPE_STRING: - attr->user_data->str = g_string_new(""); - attr->value.str = &attr->user_data->str; - break; - case HSATTR_TYPE_COLOR: - attr->user_data->color = getcolor("#000000"); - attr->unparsed_value = g_string_new("#000000"); - attr->value.color = &attr->user_data->color; - default: - break; - } - return attr; -} - -int userattribute_remove_command(int argc, char* argv[], GString* output) { - if (argc < 2) { - return HERBST_NEED_MORE_ARGS; - } - char* path = argv[1]; - HSAttribute* attr = hsattribute_parse_path_verbose(path, output); - if (!attr) { - return HERBST_INVALID_ARGUMENT; - } - if (!attr->user_attribute) { - g_string_append_printf(output, "Can only user-defined attributes, " - "but \"%s\" is not user-defined.\n", - path); - return HERBST_FORBIDDEN; - } - return userattribute_remove(attr) ? 0 : HERBST_UNKNOWN_ERROR; -} - -bool userattribute_remove(HSAttribute* attr) { - HSObject* obj = attr->object; - int idx = attr - obj->attributes; - if (idx < 0 || idx >= obj->attribute_count) { - fprintf(stderr, "Assertion 0 <= idx < count failed.\n"); - return false; - } - hsattribute_free(attr); - // remove it from buf - size_t count = obj->attribute_count - 1; - size_t bytes = (count - idx) * sizeof(HSAttribute); - memmove(obj->attributes + idx, obj->attributes + idx + 1, bytes); - obj->attributes = g_renew(HSAttribute, obj->attributes, count); - obj->attribute_count = count; - return 0; -} - -#define FORMAT_CHAR '%' - -int sprintf_command(int argc, char* argv[], GString* output) { - // usage: sprintf IDENTIFIER FORMAT [Params...] COMMAND [ARGS ...] - if (argc < 4) { - return HERBST_NEED_MORE_ARGS; - } - char* identifier = argv[1]; - char* format = argv[2]; - (void) SHIFT(argc, argv); - (void) SHIFT(argc, argv); - (void) SHIFT(argc, argv); - GString* repl = g_string_new(""); - int nextarg = 0; // next argument to consider - for (int i = 0; format[i] != '\0'; i++) { - if (format[i] == FORMAT_CHAR) { - // FORMAT_CHAR is our format character - // '%' is the printf format character - switch (format[i+1]) { - case FORMAT_CHAR: - g_string_append(repl, "%%"); - break; - - case 's': - if (nextarg >= (argc - 1)) { - g_string_append_printf(output, - "Error: Too few parameters. A %dth parameter missing. " - "(treating \"%s\" as the command to execute)\n", - nextarg, argv[argc - 1]); - g_string_free(repl, true); - return HERBST_INVALID_ARGUMENT; - } - HSAttribute* attr; - attr = hsattribute_parse_path_verbose(argv[nextarg], output); - if (!attr) { - g_string_free(repl, true); - return HERBST_INVALID_ARGUMENT; - } - GString* gs = hsattribute_to_string(attr); - g_string_append(repl, gs->str); - g_string_free(gs, true); - nextarg++; - break; - - default: - g_string_append_printf(output, - "Error: unknown format specifier \'%c\' in format " - "\"%s\" at position %d\n", - format[i+1] ? format[i+1] : '?', format, i); - g_string_free(repl, true); - return HERBST_INVALID_ARGUMENT; - break; - } - i++; - } else { - g_string_append_c(repl, format[i]); - } - } - int cmdc = argc - nextarg; - char** cmdv = argv + nextarg; - int status; - status = call_command_substitute(identifier, repl->str, cmdc, cmdv, output); - g_string_free(repl, true); - return status; -} - -int tmpattribute_command(int argc, char* argv[], GString* output) { - // usage: tmp type IDENTIFIER COMMAND [ARGS...] - if (argc < 4) { - return HERBST_NEED_MORE_ARGS; - } - static int tmpcount = 0; - tmpcount++; - char* name = g_strdup_printf("%stmp%d", USER_ATTRIBUTE_PREFIX, tmpcount); - // attr may change, so only remember the object - HSAttribute* attr = hsattribute_create(g_tmp_object, name, argv[1], output); - if (!attr) { - tmpcount--; - g_free(name); - return HERBST_INVALID_ARGUMENT; - } - HSObject* obj = attr->object; - char* path = g_strdup_printf("%s%c%s", TMP_OBJECT_PATH, - OBJECT_PATH_SEPARATOR, name); - int status = call_command_substitute(argv[2], path, argc - 3, argv + 3, output); - userattribute_remove(hsobject_find_attribute(obj, name)); - g_free(name); - g_free(path); - tmpcount--; - return status; -} - diff -Nru herbstluftwm-0.6.2/src/object.cpp herbstluftwm-0.7.0/src/object.cpp --- herbstluftwm-0.6.2/src/object.cpp 1970-01-01 00:00:00.000000000 +0000 +++ herbstluftwm-0.7.0/src/object.cpp 2015-10-14 13:27:40.000000000 +0000 @@ -0,0 +1,1040 @@ +/** Copyright 2011-2013 Thorsten Wißmann. All rights reserved. + * + * This software is licensed under the "Simplified BSD License". + * See LICENSE for details */ + +#include "object.h" +#include "command.h" +#include "utils.h" +#include "assert.h" +#include "globals.h" +#include "ipc-protocol.h" + +#include +#include +#include + +typedef struct { + char* name; + HSObject* child; +} HSObjectChild; + +static void hsobjectchild_destroy(HSObjectChild* oc); +static HSObjectChild* hsobjectchild_create(const char* name, HSObject* obj); +static void hsattribute_free(HSAttribute* attr); + +static HSObject g_root_object; +static HSObject* g_tmp_object; + +void object_tree_init() { + hsobject_init(&g_root_object); + g_tmp_object = hsobject_create_and_link(&g_root_object, TMP_OBJECT_PATH); + +} + +void object_tree_destroy() { + hsobject_unlink_and_destroy(&g_root_object, g_tmp_object); + hsobject_free(&g_root_object); +} + +HSObject* hsobject_root() { + return &g_root_object; +} + +bool hsobject_init(HSObject* obj) { + obj->attributes = NULL; + obj->attribute_count = 0; + obj->children = NULL; + return true; +} + +void hsobject_free(HSObject* obj) { + for (int i = 0; i < obj->attribute_count; i++) { + hsattribute_free(obj->attributes + i); + } + g_free(obj->attributes); + g_list_free_full(obj->children, (GDestroyNotify)hsobjectchild_destroy); +} + +static void hsattribute_free(HSAttribute* attr) { + if (attr->user_data) { + g_free((char*)attr->name); + if (attr->type == HSATTR_TYPE_STRING) { + g_string_free(attr->user_data->str, true); + } + g_free(attr->user_data); + } + if (attr->unparsed_value) { + g_string_free(attr->unparsed_value, true); + } +} + +HSObject* hsobject_create() { + HSObject* obj = g_new(HSObject, 1); + hsobject_init(obj); + return obj; +} + +void hsobject_destroy(HSObject* obj) { + if (!obj) return; + hsobject_free(obj); + g_free(obj); +} + +HSObject* hsobject_create_and_link(HSObject* parent, const char* name) { + HSObject* obj = hsobject_create(); + hsobject_link(parent, obj, name); + return obj; +} + +void hsobject_unlink_and_destroy(HSObject* parent, HSObject* child) { + hsobject_unlink(parent, child); + hsobject_destroy(child); +} + +static HSObjectChild* hsobjectchild_create(const char* name, HSObject* obj) { + HSObjectChild* oc = g_new(HSObjectChild, 1); + oc->name = g_strdup(name); + oc->child = obj; + return oc; +} + +static void hsobjectchild_destroy(HSObjectChild* oc) { + if (!oc) return; + g_free((char*)oc->name); + g_free(oc); +} + +struct HSObjectComplChild { + const char* needle; + const char* prefix; + GString* curname; + GString* output; +}; + +static void completion_helper(HSObjectChild* child, struct HSObjectComplChild* data) { + g_string_assign(data->curname, child->name); + g_string_append_c(data->curname, OBJECT_PATH_SEPARATOR); + try_complete_prefix_partial(data->needle, data->curname->str, data->prefix, data->output); +} + +void hsobject_complete_children(HSObject* obj, const char* needle, const char* prefix, GString* output) { + struct HSObjectComplChild data = { + needle, + prefix, + g_string_new(""), + output + }; + g_list_foreach(obj->children, (GFunc) completion_helper, &data); + g_string_free(data.curname, true); +} + +void hsobject_complete_attributes(HSObject* obj, bool user_only, const char* needle, + const char* prefix, GString* output) { + for (int i = 0; i < obj->attribute_count; i++) { + HSAttribute* attr = obj->attributes + i; + if (user_only && !attr->user_attribute) { + // do not complete default attributes if user_only is set + continue; + } + try_complete_prefix(needle, attr->name, prefix, output); + } +} + +static int child_check_name(HSObjectChild* child, char* name) { + return strcmp(child->name, name); +} + +void hsobject_link(HSObject* parent, HSObject* child, const char* name) { + GList* elem = g_list_find_custom(parent->children, name, + (GCompareFunc)child_check_name); + if (!elem) { + // create a new child node + HSObjectChild* oc = hsobjectchild_create(name, child); + parent->children = g_list_append(parent->children, oc); + } else { + // replace it + HSObjectChild* oc = (HSObjectChild*) elem->data; + oc->child = child; + } +} + +static int child_check_object(HSObjectChild* child, HSObject* obj) { + // return 0 if they are identical + return (child->child == obj) ? 0 : 1; +} + +static void hsobject_unlink_helper(HSObject* parent, GCompareFunc f, const void* data) { + GList* elem = parent->children; + while (elem) { + elem = g_list_find_custom(elem, data, f); + if (elem) { + GList* next = elem->next; + hsobjectchild_destroy((HSObjectChild*)elem->data); + parent->children = g_list_delete_link(parent->children, elem); + elem = next; + } + } +} + +void hsobject_unlink(HSObject* parent, HSObject* child) { + hsobject_unlink_helper(parent, + (GCompareFunc)child_check_object, + child); +} + +void hsobject_unlink_by_name(HSObject* parent, const char* name) { + hsobject_unlink_helper(parent, + (GCompareFunc)child_check_name, + name); +} + +void hsobject_link_rename(HSObject* parent, char* oldname, char* newname) { + if (!strcmp(oldname, newname)) { + return; + } + // remove object with target name + hsobject_unlink_by_name(parent, newname); + GList* elem = g_list_find_custom(parent->children, + oldname, + (GCompareFunc)child_check_name); + HSObjectChild* child = (HSObjectChild*)elem->data; + g_free(child->name); + child->name = g_strdup(newname); +} + +void hsobject_link_rename_object(HSObject* parent, HSObject* child, char* newname) { + // remove occurrences of that object + hsobject_unlink(parent, child); + // link it again (replacing any object with newname) + hsobject_link(parent, child, newname); +} + +HSObject* hsobject_find_child(HSObject* obj, const char* name) { + GList* elem = g_list_find_custom(obj->children, name, + (GCompareFunc)child_check_name); + if (elem) { + return ((HSObjectChild*)(elem->data))->child; + } else { + return NULL; + } +} + +HSAttribute* hsobject_find_attribute(HSObject* obj, const char* name) { + for (int i = 0; i < obj->attribute_count; i++) { + if (!strcmp(name, obj->attributes[i].name)) { + return obj->attributes + i; + } + } + return NULL; +} + +void hsobject_set_attributes_always_callback(HSObject* obj) { + for (int i = 0; i < obj->attribute_count; i++) { + obj->attributes[i].always_callback = true; + } +} + +static void print_child_name(HSObjectChild* child, GString* output) { + g_string_append_printf(output, " %s%c\n", child->name, OBJECT_PATH_SEPARATOR); +} + +void hsattribute_append_to_string(HSAttribute* attribute, GString* output) { + switch (attribute->type) { + case HSATTR_TYPE_BOOL: + if (*(attribute->value.b)) { + g_string_append_printf(output, "true"); + } else { + g_string_append_printf(output, "false"); + } + break; + case HSATTR_TYPE_INT: + g_string_append_printf(output, "%d", *attribute->value.i); + break; + case HSATTR_TYPE_UINT: + g_string_append_printf(output, "%u", *attribute->value.u); + break; + case HSATTR_TYPE_STRING: + g_string_append_printf(output, "%s", (*attribute->value.str)->str); + break; + case HSATTR_TYPE_COLOR: + g_string_append_printf(output, "%s", attribute->unparsed_value->str); + break; + case HSATTR_TYPE_CUSTOM: + attribute->value.custom(attribute->data ? attribute->data + : attribute->object->data, output); + break; + case HSATTR_TYPE_CUSTOM_INT: + g_string_append_printf(output, "%d", + attribute->value.custom_int(attribute->data ? attribute->data + : attribute->object->data)); + break; + } +} + +GString* hsattribute_to_string(HSAttribute* attribute) { + GString* str = g_string_new(""); + hsattribute_append_to_string(attribute, str); + return str; +} + +int attr_command(int argc, char* argv[], GString* output) { + const char* path = (argc < 2) ? "" : argv[1]; + const char* unparsable; + GString* errormsg = g_string_new(""); + HSObject* obj = hsobject_parse_path_verbose(path, &unparsable, errormsg); + HSAttribute* attribute = NULL; + if (strcmp("", unparsable)) { + // if object could not be parsed, try attribute + attribute = hsattribute_parse_path_verbose(path, errormsg); + obj = NULL; + } + if (!obj && !attribute) { + // if nothing was found + g_string_append(output, errormsg->str); + g_string_free(errormsg, true); + return HERBST_INVALID_ARGUMENT; + } else { + g_string_free(errormsg, true); + } + char* new_value = (argc >= 3) ? argv[2] : NULL; + if (obj && new_value) { + g_string_append_printf(output, + "%s: Can not assign value \"%s\" to object \"%s\",", + argv[0], new_value, path); + } else if (obj && !new_value) { + // list children + int childcount = g_list_length(obj->children); + g_string_append_printf(output, "%d children%c\n", childcount, + childcount ? ':' : '.'); + g_list_foreach(obj->children, (GFunc) print_child_name, output); + if (childcount > 0) { + g_string_append_printf(output, "\n"); + } + // list attributes + g_string_append_printf(output, "%zu attributes", obj->attribute_count); + if (obj->attribute_count > 0) { + g_string_append_printf(output, ":\n"); + g_string_append_printf(output, " .---- type\n"); + g_string_append_printf(output, " | .-- writeable\n"); + g_string_append_printf(output, " V V\n"); + } else { + g_string_append_printf(output, ".\n"); + } + for (int i = 0; i < obj->attribute_count; i++) { + HSAttribute* a = obj->attributes + i; + char write = hsattribute_is_read_only(a) ? '-' : 'w'; + char t = hsattribute_type_indicator(a->type); + g_string_append_printf(output, " %c %c %-20s = ", t, write, a->name); + if (a->type == HSATTR_TYPE_STRING) { + g_string_append_c(output, '\"'); + } + hsattribute_append_to_string(a, output); + if (a->type == HSATTR_TYPE_STRING) { + g_string_append_c(output, '\"'); + } + g_string_append_c(output, '\n'); + } + } else if (new_value) { // && (attribute) + return hsattribute_assign(attribute, new_value, output); + } else { // !new_value && (attribute) + hsattribute_append_to_string(attribute, output); + } + return 0; +} + +static void object_append_caption(HSTree tree, GString* output) { + HSObjectChild* oc = (HSObjectChild*) tree; + g_string_append(output, oc->name); +} + +static size_t object_child_count(HSTree tree) { + HSObjectChild* oc = (HSObjectChild*) tree; + return g_list_length(oc->child->children); +} + +static HSTreeInterface object_nth_child(HSTree tree, size_t idx) { + HSObjectChild* oc = (HSObjectChild*) tree; + assert(oc->child); + HSTreeInterface intf = { + /* .nth_child = */ object_nth_child, + /* .child_count = */ object_child_count, + /* .append_caption = */ object_append_caption, + /* .data = */ (HSTree) g_list_nth_data(oc->child->children, idx), + /* .destructor = */ NULL, + }; + return intf; +} + +HSObject* hsobject_by_path(char* path) { + const char* unparsable; + HSObject* obj = hsobject_parse_path(path, &unparsable); + if (!strcmp("", unparsable)) { + return obj; + } else { + // an invalid path was given if it was not parsed entirely + return NULL; + } +} + +HSObject* hsobject_parse_path_verbose(const char* path, const char** unparsable, + GString* output) { + const char* origpath = path; + char* pathdup = strdup(path); + char* curname = pathdup; + const char* lastname = "root"; + char seps[] = { OBJECT_PATH_SEPARATOR, '\0' }; + // replace all separators by null bytes + g_strdelimit(curname, seps, '\0'); + HSObject* obj = hsobject_root(); + HSObject* child; + // skip separator characters + while (*path == OBJECT_PATH_SEPARATOR) { + path++; + curname++; + } + while (strcmp("", path)) { + child = hsobject_find_child(obj, curname); + if (!child) { + if (output) { + g_string_append_printf(output, "Invalid path \"%s\": ", origpath); + g_string_append_printf(output, "No child \"%s\" in object %s\n", + curname, lastname); + } + break; + } + lastname = curname; + obj = child; + // skip the name + path += strlen(curname); + curname += strlen(curname); + // skip separator characters + while (*path == OBJECT_PATH_SEPARATOR) { + path++; + curname++; + } + } + *unparsable = path; + free(pathdup); + return obj; +} + +HSObject* hsobject_parse_path(const char* path, const char** unparsable) { + return hsobject_parse_path_verbose(path, unparsable, NULL); +} + +HSAttribute* hsattribute_parse_path_verbose(const char* path, GString* output) { + GString* object_error = g_string_new(""); + HSAttribute* attr; + const char* unparsable; + HSObject* obj = hsobject_parse_path_verbose(path, &unparsable, object_error); + if (obj == NULL || strchr(unparsable, OBJECT_PATH_SEPARATOR) != NULL) { + // if there is still another path separator + // then unparsable is more than just the attribute name. + g_string_append(output, object_error->str); + attr = NULL; + } else { + // if there is no path remaining separator, then unparsable contains + // the attribute name + attr = hsobject_find_attribute(obj, unparsable); + if (!attr) { + GString* obj_path = g_string_new(path); + g_string_truncate(obj_path, unparsable - path); + g_string_append_printf(output, + "Unknown attribute \"%s\" in object \"%s\".\n", + unparsable, obj_path->str); + g_string_free(obj_path, true); + } + } + g_string_free(object_error, true); + return attr; +} + +HSAttribute* hsattribute_parse_path(const char* path) { + GString* out = g_string_new(""); + HSAttribute* attr = hsattribute_parse_path_verbose(path, out); + if (!attr) { + HSError("Cannot parse %s: %s", path, out->str); + } + g_string_free(out, true); + return attr; +} + +int print_object_tree_command(int argc, char* argv[], GString* output) { + const char* unparsable; + const char* path = (argc < 2) ? "" : argv[1]; + HSObjectChild oc = { + (char*)path, + hsobject_parse_path_verbose(path, &unparsable, output), + }; + if (strcmp("", unparsable)) { + return HERBST_INVALID_ARGUMENT; + } + HSTreeInterface intf = { + /* .nth_child = */ object_nth_child, + /* .child_count = */ object_child_count, + /* .append_caption = */ object_append_caption, + /* .data = */ &oc, + /* .destructor = */ NULL, + }; + tree_print_to(&intf, output); + return 0; +} + +void hsobject_set_attributes(HSObject* obj, HSAttribute* attributes) { + // calculate new size + size_t count; + for (count = 0; attributes[count].name != NULL; count++) + ; + obj->attributes = g_renew(HSAttribute, obj->attributes, count); + obj->attribute_count = count; + memcpy(obj->attributes, attributes, count * sizeof(HSAttribute)); + for (int i = 0; i < count; i++) { + obj->attributes[i].object = obj; + } +} + +int hsattribute_get_command(int argc, const char* argv[], GString* output) { + if (argc < 2) { + return HERBST_NEED_MORE_ARGS; + } + HSAttribute* attr = hsattribute_parse_path_verbose(argv[1], output); + if (!attr) { + return HERBST_INVALID_ARGUMENT; + } + hsattribute_append_to_string(attr, output); + return 0; +} + +int hsattribute_set_command(int argc, char* argv[], GString* output) { + if (argc < 3) { + return HERBST_NEED_MORE_ARGS; + } + HSAttribute* attr = hsattribute_parse_path_verbose(argv[1], output); + if (!attr) { + return HERBST_INVALID_ARGUMENT; + } + return hsattribute_assign(attr, argv[2], output); +} + +bool hsattribute_is_read_only(HSAttribute* attr) { + bool custom = attr->type == HSATTR_TYPE_CUSTOM + || attr->type == HSATTR_TYPE_CUSTOM_INT; + assert(!(custom && attr->on_change)); + if (custom) return attr->change_custom == NULL; + else return attr->on_change == NULL; +} + +int hsattribute_assign(HSAttribute* attr, const char* new_value_str, GString* output) { + if (hsattribute_is_read_only(attr)) { + g_string_append_printf(output, + "Can not write read-only attribute \"%s\"\n", + attr->name); + return HERBST_FORBIDDEN; + } + + bool error = false; + HSAttributeValue new_value, old_value; + bool nothing_to_do = false; + +#define ATTR_DO_ASSIGN_COMPARE(NAME,MEM) \ + do { \ + if (error) { \ + g_string_append_printf(output, \ + "Can not parse " NAME " from \"%s\"", \ + new_value_str); \ + } else { \ + old_value.MEM = *attr->value.MEM; \ + if (old_value.MEM == new_value.MEM) { \ + nothing_to_do = true; \ + } else { \ + *attr->value.MEM = new_value.MEM; \ + } \ + } \ + } while (0); + // change the value and backup the old value + switch (attr->type) { + case HSATTR_TYPE_BOOL: + new_value.b = string_to_bool_error(new_value_str, + *attr->value.b, + &error); + ATTR_DO_ASSIGN_COMPARE("boolean", b); + break; + + case HSATTR_TYPE_INT: + error = (1 != sscanf(new_value_str, "%d", &new_value.i)); + ATTR_DO_ASSIGN_COMPARE("integer", i); + break; + + case HSATTR_TYPE_UINT: + error = (1 != sscanf(new_value_str, "%u", &new_value.u)); + ATTR_DO_ASSIGN_COMPARE("unsigned integer", u); + break; + + case HSATTR_TYPE_STRING: + if (!strcmp(new_value_str, (*attr->value.str)->str)) { + nothing_to_do = true; + } else { + old_value.str = g_string_new((*attr->value.str)->str); + g_string_assign(*attr->value.str, new_value_str); + } + break; + + case HSATTR_TYPE_COLOR: + error = !getcolor_error(new_value_str, &new_value.color); + if (error) { + g_string_append_printf(output, + "\"%s\" is not a valid color.", new_value_str); + break; + } + if (!strcmp(new_value_str, (attr->unparsed_value)->str)) { + nothing_to_do = true; + } else { + old_value.color = *attr->value.color; + *attr->value.color = new_value.color; + } + break; + + case HSATTR_TYPE_CUSTOM: break; + case HSATTR_TYPE_CUSTOM_INT: break; + } + if (error) { + return HERBST_INVALID_ARGUMENT; + } + if (attr->always_callback) { + nothing_to_do = false; // pretend that there was a change + } + if (nothing_to_do) { + return 0; + } + + GString* old_unparsed_value = attr->unparsed_value; + if (attr->unparsed_value) attr->unparsed_value = g_string_new(new_value_str); + + // ask the attribute about the change + GString* errormsg = NULL; + if (attr->on_change) errormsg = attr->on_change(attr); + else errormsg = attr->change_custom(attr, new_value_str); + int exit_status = 0; + if (errormsg && errormsg->len > 0) { + exit_status = HERBST_INVALID_ARGUMENT; + // print the message + if (errormsg->str[errormsg->len - 1] == '\n') { + g_string_truncate(errormsg, errormsg->len - 1); + } + g_string_append_printf(output, + "Can not write attribute \"%s\": %s\n", + attr->name, + errormsg->str); + g_string_free(errormsg, true); + // restore old value + if (old_unparsed_value) { + g_string_free(attr->unparsed_value, true); + attr->unparsed_value = old_unparsed_value; + } + switch (attr->type) { + case HSATTR_TYPE_BOOL: *attr->value.b = old_value.b; break; + case HSATTR_TYPE_INT: *attr->value.i = old_value.i; break; + case HSATTR_TYPE_UINT: *attr->value.u = old_value.u; break; + case HSATTR_TYPE_STRING: + g_string_assign(*attr->value.str, old_value.str->str); + break; + case HSATTR_TYPE_COLOR: + *attr->value.color = old_value.color; break; + break; + case HSATTR_TYPE_CUSTOM: break; + case HSATTR_TYPE_CUSTOM_INT: break; + } + } + // free old_value + switch (attr->type) { + case HSATTR_TYPE_BOOL: break; + case HSATTR_TYPE_INT: break; + case HSATTR_TYPE_UINT: break; + case HSATTR_TYPE_STRING: + g_string_free(old_value.str, true); + break; + case HSATTR_TYPE_COLOR: + g_string_assign(attr->unparsed_value, new_value_str); + break; + case HSATTR_TYPE_CUSTOM: break; + case HSATTR_TYPE_CUSTOM_INT: break; + } + if (old_unparsed_value) { + g_string_free(old_unparsed_value, true); + } + return exit_status; +} + + +int substitute_command(int argc, char* argv[], GString* output) { + // usage: substitute identifier attribute command [args ...] + // 0 1 2 3 + if (argc < 4) { + return HERBST_NEED_MORE_ARGS; + } + char* identifier = argv[1]; + HSAttribute* attribute = hsattribute_parse_path_verbose(argv[2], output); + if (!attribute) { + return HERBST_INVALID_ARGUMENT; + } + GString* attribute_string = hsattribute_to_string(attribute); + char* repl = attribute_string->str; + + (void) SHIFT(argc, argv); // remove command name + (void) SHIFT(argc, argv); // remove identifier + (void) SHIFT(argc, argv); // remove attribute + + int status = call_command_substitute(identifier, repl, argc, argv, output); + g_string_free(attribute_string, true); + return status; +} + +GString* ATTR_ACCEPT_ALL(HSAttribute* attr) { + (void) attr; + return NULL; +} + +int compare_command(int argc, char* argv[], GString* output) { + // usage: compare attribute operator constant + if (argc < 4) { + return HERBST_NEED_MORE_ARGS; + } + HSAttribute* attr = hsattribute_parse_path_verbose(argv[1], output); + if (!attr) { + return HERBST_INVALID_ARGUMENT; + } + char* op = argv[2]; + char* rvalue = argv[3]; + if (attr->type == HSATTR_TYPE_INT + || attr->type == HSATTR_TYPE_UINT + || attr->type == HSATTR_TYPE_CUSTOM_INT) + { + long long l; + long long r; + if (1 != sscanf(rvalue, "%lld", &r)) { + g_string_append_printf(output, + "Can not parse integer from \"%s\"\n", + rvalue); + return HERBST_INVALID_ARGUMENT; + } + switch (attr->type) { + case HSATTR_TYPE_INT: l = *attr->value.i; break; + case HSATTR_TYPE_UINT: l = *attr->value.u; break; + case HSATTR_TYPE_CUSTOM_INT: + l = attr->value.custom_int(attr->data ? attr->data : attr->object->data); + break; + default: return HERBST_UNKNOWN_ERROR; break; + } + struct { + const char* name; + bool result; + } eval[] = { + { "=", l == r }, + { "!=", l != r }, + { "le", l <= r }, + { "lt", l < r }, + { "ge", l >= r }, + { "gt", l > r }, + }; + int result = -1; + for (int i = 0; i < LENGTH(eval); i++) { + if (!strcmp(eval[i].name, op)) { + result = !eval[i].result; // make false -> 1, true -> 0 + } + } + if (result == -1) { + g_string_append_printf(output, "Invalid operator \"%s\"", op); + result = HERBST_INVALID_ARGUMENT; + } + return result; + } else if (attr->type == HSATTR_TYPE_BOOL) { + bool l = *attr->value.b; + bool error = false; + bool r = string_to_bool_error(rvalue, l, &error); + if (error) { + g_string_append_printf(output, "Can not parse boolean from \"%s\"\n", rvalue); + return HERBST_INVALID_ARGUMENT; + } + if (!strcmp("=", op)) return !(l == r); + if (!strcmp("!=", op)) return !(l != r); + g_string_append_printf(output, "Invalid boolean operator \"%s\"", op); + return HERBST_INVALID_ARGUMENT; + } else if (attr->type == HSATTR_TYPE_COLOR) { + HSColor l = *attr->value.color; + HSColor r = getcolor(rvalue); + if (!strcmp("=", op)) return !(l == r); + if (!strcmp("!=", op)) return !(l != r); + g_string_append_printf(output, "Invalid color operator \"%s\"", op); + return HERBST_INVALID_ARGUMENT; + } else { // STRING or CUSTOM + GString* l; + bool free_l = false; + if (attr->type == HSATTR_TYPE_STRING) { + l = *attr->value.str; + } else { // TYPE == CUSTOM + l = g_string_new(""); + attr->value.custom(attr->data ? attr->data : attr->object->data, l); + free_l = true; + } + bool equals = !strcmp(l->str, rvalue); + int status; + if (!strcmp("=", op)) status = !equals; + else if (!strcmp("!=", op)) status = equals; + else status = -1; + if (free_l) { + g_string_free(l, true); + } + if (status == -1) { + g_string_append_printf(output, "Invalid string operator \"%s\"", op); + return HERBST_INVALID_ARGUMENT; + } + return status; + } +} + +char hsattribute_type_indicator(int type) { + switch (type) { + case HSATTR_TYPE_BOOL: return 'b'; + case HSATTR_TYPE_UINT: return 'u'; + case HSATTR_TYPE_INT: return 'i'; + case HSATTR_TYPE_STRING: return 's'; + case HSATTR_TYPE_CUSTOM: return 's'; + case HSATTR_TYPE_CUSTOM_INT:return 'i'; + case HSATTR_TYPE_COLOR: return 'c'; + } + return '?'; +} + +int userattribute_command(int argc, char* argv[], GString* output) { + if (argc < 3) { + return HERBST_NEED_MORE_ARGS; + } + char* type_str = argv[1]; + char* path = argv[2]; + const char* unparsable; + GString* errormsg = g_string_new(""); + HSObject* obj = hsobject_parse_path_verbose(path, &unparsable, errormsg); + if (obj == NULL || strchr(unparsable, OBJECT_PATH_SEPARATOR) != NULL) { + g_string_append(output, errormsg->str); + g_string_free(errormsg, true); + return HERBST_INVALID_ARGUMENT; + } else { + g_string_free(errormsg, true); + } + // check for an already existing attribute + if (hsobject_find_attribute(obj, unparsable)) { + g_string_append_printf(output, "Error: an attribute called \"%s\" already exists\n", + unparsable); + return HERBST_FORBIDDEN; + } + // do not check for children with that name, because they must not start + // with the USER_ATTRIBUTE_PREFIX. + // now create a new attribute named unparsable at obj + const char* prefix = USER_ATTRIBUTE_PREFIX; + if (strncmp(unparsable, prefix, strlen(prefix))) { + g_string_append(output, "Error: the name of user attributes has to "); + g_string_append_printf(output, "start with \"%s\" but yours is \"%s\"\n", + prefix, unparsable); + return HERBST_INVALID_ARGUMENT; + } + HSAttribute* attr = hsattribute_create(obj, unparsable, type_str, output); + if (!attr) { + return HERBST_INVALID_ARGUMENT; + } + attr->user_attribute = true; + return 0; +} + +HSAttribute* hsattribute_create(HSObject* obj, const char* name, char* type_str, + GString* output) +{ + struct { + const char* name; + int type; + } types[] = { + { "bool", HSATTR_TYPE_BOOL }, + { "uint", HSATTR_TYPE_UINT }, + { "int", HSATTR_TYPE_INT }, + { "string", HSATTR_TYPE_STRING }, + { "color", HSATTR_TYPE_COLOR }, + }; + int type = -1; + for (int i = 0; i < LENGTH(types); i++) { + if (!strcmp(type_str, types[i].name)) { + type = types[i].type; + break; + } + } + if (type < 0) { + g_string_append_printf(output, "Unknown attribute type \"%s\"\n", + type_str); + return NULL; + } + size_t count = obj->attribute_count + 1; + obj->attributes = g_renew(HSAttribute, obj->attributes, count); + obj->attribute_count = count; + // initialize object + HSAttribute* attr = obj->attributes + count - 1; + memset(attr, 0, sizeof(*attr)); + attr->object = obj; + attr->type = (HSAttributeType)type; + attr->name = g_strdup(name); + attr->on_change = ATTR_ACCEPT_ALL; + attr->user_attribute = false; + attr->user_data = g_new(HSAttributeValue, 1); + switch (type) { + case HSATTR_TYPE_BOOL: + attr->user_data->b = false; + attr->value.b = &attr->user_data->b; + break; + case HSATTR_TYPE_INT: + attr->user_data->i = 0; + attr->value.i = &attr->user_data->i; + break; + case HSATTR_TYPE_UINT: + attr->user_data->u = 0; + attr->value.u = &attr->user_data->u; + break; + case HSATTR_TYPE_STRING: + attr->user_data->str = g_string_new(""); + attr->value.str = &attr->user_data->str; + break; + case HSATTR_TYPE_COLOR: + attr->user_data->color = getcolor("#000000"); + attr->unparsed_value = g_string_new("#000000"); + attr->value.color = &attr->user_data->color; + default: + break; + } + return attr; +} + +int userattribute_remove_command(int argc, char* argv[], GString* output) { + if (argc < 2) { + return HERBST_NEED_MORE_ARGS; + } + char* path = argv[1]; + HSAttribute* attr = hsattribute_parse_path_verbose(path, output); + if (!attr) { + return HERBST_INVALID_ARGUMENT; + } + if (!attr->user_attribute) { + g_string_append_printf(output, "Can only user-defined attributes, " + "but \"%s\" is not user-defined.\n", + path); + return HERBST_FORBIDDEN; + } + return userattribute_remove(attr) ? 0 : HERBST_UNKNOWN_ERROR; +} + +bool userattribute_remove(HSAttribute* attr) { + HSObject* obj = attr->object; + int idx = attr - obj->attributes; + if (idx < 0 || idx >= obj->attribute_count) { + fprintf(stderr, "Assertion 0 <= idx < count failed.\n"); + return false; + } + hsattribute_free(attr); + // remove it from buf + size_t count = obj->attribute_count - 1; + size_t bytes = (count - idx) * sizeof(HSAttribute); + memmove(obj->attributes + idx, obj->attributes + idx + 1, bytes); + obj->attributes = g_renew(HSAttribute, obj->attributes, count); + obj->attribute_count = count; + return 0; +} + +#define FORMAT_CHAR '%' + +int sprintf_command(int argc, char* argv[], GString* output) { + // usage: sprintf IDENTIFIER FORMAT [Params...] COMMAND [ARGS ...] + if (argc < 4) { + return HERBST_NEED_MORE_ARGS; + } + char* identifier = argv[1]; + char* format = argv[2]; + (void) SHIFT(argc, argv); + (void) SHIFT(argc, argv); + (void) SHIFT(argc, argv); + GString* repl = g_string_new(""); + int nextarg = 0; // next argument to consider + for (int i = 0; format[i] != '\0'; i++) { + if (format[i] == FORMAT_CHAR) { + // FORMAT_CHAR is our format character + // '%' is the printf format character + switch (format[i+1]) { + case FORMAT_CHAR: + g_string_append(repl, "%%"); + break; + + case 's': { + if (nextarg >= (argc - 1)) { + g_string_append_printf(output, + "Error: Too few parameters. A %dth parameter missing. " + "(treating \"%s\" as the command to execute)\n", + nextarg, argv[argc - 1]); + g_string_free(repl, true); + return HERBST_INVALID_ARGUMENT; + } + HSAttribute* attr; + attr = hsattribute_parse_path_verbose(argv[nextarg], output); + if (!attr) { + g_string_free(repl, true); + return HERBST_INVALID_ARGUMENT; + } + GString* gs = hsattribute_to_string(attr); + g_string_append(repl, gs->str); + g_string_free(gs, true); + nextarg++; + break; + } + + default: + g_string_append_printf(output, + "Error: unknown format specifier \'%c\' in format " + "\"%s\" at position %d\n", + format[i+1] ? format[i+1] : '?', format, i); + g_string_free(repl, true); + return HERBST_INVALID_ARGUMENT; + break; + } + i++; + } else { + g_string_append_c(repl, format[i]); + } + } + int cmdc = argc - nextarg; + char** cmdv = argv + nextarg; + int status; + status = call_command_substitute(identifier, repl->str, cmdc, cmdv, output); + g_string_free(repl, true); + return status; +} + +int tmpattribute_command(int argc, char* argv[], GString* output) { + // usage: tmp type IDENTIFIER COMMAND [ARGS...] + if (argc < 4) { + return HERBST_NEED_MORE_ARGS; + } + static int tmpcount = 0; + tmpcount++; + char* name = g_strdup_printf("%stmp%d", USER_ATTRIBUTE_PREFIX, tmpcount); + // attr may change, so only remember the object + HSAttribute* attr = hsattribute_create(g_tmp_object, name, argv[1], output); + if (!attr) { + tmpcount--; + g_free(name); + return HERBST_INVALID_ARGUMENT; + } + HSObject* obj = attr->object; + char* path = g_strdup_printf("%s%c%s", TMP_OBJECT_PATH, + OBJECT_PATH_SEPARATOR, name); + int status = call_command_substitute(argv[2], path, argc - 3, argv + 3, output); + userattribute_remove(hsobject_find_attribute(obj, name)); + g_free(name); + g_free(path); + tmpcount--; + return status; +} + diff -Nru herbstluftwm-0.6.2/src/object.h herbstluftwm-0.7.0/src/object.h --- herbstluftwm-0.6.2/src/object.h 2014-03-26 23:54:22.000000000 +0000 +++ herbstluftwm-0.7.0/src/object.h 2015-10-14 13:27:40.000000000 +0000 @@ -14,8 +14,10 @@ #define USER_ATTRIBUTE_PREFIX "my_" #define TMP_OBJECT_PATH "tmp" +class HSAttribute; + typedef struct HSObject { - struct HSAttribute* attributes; + HSAttribute* attributes; size_t attribute_count; GList* children; // list of HSObjectChild void* data; // user data pointer @@ -25,6 +27,7 @@ // if this is NULL it is the data-pointer of the object typedef void (*HSAttributeCustom)(void* data, GString* output); typedef int (*HSAttributeCustomInt)(void* data); +typedef GString* (*HSAttributeChangeCustom)(HSAttribute* attr, const char* new_value); typedef union HSAttributePointer { bool* b; @@ -34,6 +37,13 @@ HSColor* color; HSAttributeCustom custom; HSAttributeCustomInt custom_int; + HSAttributePointer(bool* b) : b(b) { }; + HSAttributePointer(int* x) : i(x) { }; + HSAttributePointer(unsigned int* x) : u(x) { }; + HSAttributePointer(GString** x) : str(x) { }; + HSAttributePointer(HSColor* x) : color(x) { }; + HSAttributePointer(HSAttributeCustom x) : custom(x) { }; + HSAttributePointer(HSAttributeCustomInt x) : custom_int(x) { }; } HSAttributePointer; typedef union HSAttributeValue { @@ -44,12 +54,9 @@ HSColor color; } HSAttributeValue; -struct HSAttribute; -typedef GString* (*HSAttrCallback)(struct HSAttribute* attr); +typedef GString* (*HSAttrCallback)(HSAttribute* attr); -typedef struct HSAttribute { - HSObject* object; /* the object this attribute is in */ - enum { +enum HSAttributeType { HSATTR_TYPE_BOOL, HSATTR_TYPE_UINT, HSATTR_TYPE_INT, @@ -57,8 +64,13 @@ HSATTR_TYPE_STRING, HSATTR_TYPE_CUSTOM, HSATTR_TYPE_CUSTOM_INT, - } type; /* the datatype */ - char* name; /* name as it is displayed to the user */ +}; + +class HSAttribute { +public: + HSObject* object; /* the object this attribute is in */ + HSAttributeType type; /* the datatype */ + const char* name; /* name as it is displayed to the user */ HSAttributePointer value; GString* unparsed_value; /** if type is not custom: @@ -73,7 +85,7 @@ * treaten read-only * */ HSAttrCallback on_change; - GString* (*change_custom)(struct HSAttribute* attr, const char* new_value); + HSAttributeChangeCustom change_custom; bool user_attribute; /* if this attribute was added by the user */ bool always_callback; /* call on_change/change_custom on earch write, * even if the value did not change */ @@ -81,73 +93,54 @@ * realloc'ing the HSAttribute */ HSAttributeValue* user_data; /* data needed for user attributes */ void* data; /* data which is passed to value.custom and value.custom_int */ -} HSAttribute; -#define ATTRIBUTE_SIMPLE(TYPE, N, MEM, V, CHANGE) \ - { .object = NULL, \ - .type = TYPE, \ - .name = (N), \ - .value = { MEM = V}, \ - .unparsed_value = NULL, \ - .on_change = (CHANGE), \ - .change_custom = NULL, \ - .user_attribute = false, \ - .always_callback = false, \ - .user_data = NULL, \ - .data = NULL, \ - } - - -#define ATTRIBUTE_BOOL(N, V, CHANGE) \ - ATTRIBUTE_SIMPLE(HSATTR_TYPE_BOOL, (N), .b, &(V), (CHANGE)) -#define ATTRIBUTE_INT(N, V, CHANGE) \ - ATTRIBUTE_SIMPLE(HSATTR_TYPE_INT, (N), .i, &(V), (CHANGE)) -#define ATTRIBUTE_UINT(N, V, CHANGE) \ - ATTRIBUTE_SIMPLE(HSATTR_TYPE_UINT, (N), .u, &(V), (CHANGE)) -#define ATTRIBUTE_STRING(N, V, CHANGE) \ - ATTRIBUTE_SIMPLE(HSATTR_TYPE_STRING, (N), .str, &(V), (CHANGE)) - -#define ATTRIBUTE_CUSTOM(N, V, CHANGE) \ - { .object = NULL, \ - .type = HSATTR_TYPE_CUSTOM, \ - .name = (N), \ - .value = { .custom = (V)}, \ - .unparsed_value = NULL, \ - .on_change = NULL, \ - .change_custom = (CHANGE), \ - .always_callback = false, \ - .user_attribute = false, \ - .user_data = NULL, \ - .data = NULL, \ - } -#define ATTRIBUTE_CUSTOM_INT(N, V, CHANGE) \ - { .object = NULL, \ - .type = HSATTR_TYPE_CUSTOM_INT,\ - .name = (N), \ - .value = { .custom_int = (V)},\ - .unparsed_value = NULL, \ - .on_change = NULL, \ - .change_custom = (CHANGE), \ - .always_callback = false, \ - .user_attribute = false, \ - .user_data = NULL, \ - .data = NULL, \ - } -#define ATTRIBUTE_COLOR(N, V, CHANGE) \ - { .object = NULL, \ - .type = HSATTR_TYPE_COLOR, \ - .name = (N), \ - .value = { .color = &(V)}, \ - .unparsed_value = g_string_new(""), \ - .on_change = (CHANGE), \ - .change_custom = NULL, \ - .always_callback = false, \ - .user_attribute = false, \ - .user_data = NULL, \ - .data = NULL, \ - } +#define ATTRIBUTE(N, V, CHANGE) HSAttribute(N, &(V), CHANGE) +#define ATTRIBUTE_STRING(N, V, CHANGE) ATTRIBUTE(N, V, CHANGE) +#define ATTRIBUTE_INT(N, V, CHANGE) ATTRIBUTE(N, V, CHANGE) +#define ATTRIBUTE_UINT(N, V, CHANGE) ATTRIBUTE(N, V, CHANGE) +#define ATTRIBUTE_BOOL(N, V, CHANGE) ATTRIBUTE(N, V, CHANGE) +#define ATTRIBUTE_COLOR(N, V, CHANGE) ATTRIBUTE(N, V, CHANGE) +#define ATTRIBUTE_CUSTOM(N, R, W) HSAttribute(N, R, W) +#define ATTRIBUTE_CUSTOM_INT(N, R, W) HSAttribute(N, R, W) + + // simple attribute + #define HSAttributeSimpleConstructor(ETYPE, CTYPE)\ + HSAttribute(const char* name, CTYPE* v, HSAttrCallback on_change) \ + : object(NULL), type(ETYPE), name(name), value(v), \ + /* all the other attributes: */ \ + unparsed_value(NULL), on_change(on_change), change_custom(NULL), \ + user_attribute(false), always_callback(false), \ + user_data(NULL), data(NULL) + HSAttributeSimpleConstructor(HSATTR_TYPE_BOOL, bool) {}; + HSAttributeSimpleConstructor(HSATTR_TYPE_INT, int) {}; + HSAttributeSimpleConstructor(HSATTR_TYPE_UINT, unsigned int) {}; + HSAttributeSimpleConstructor(HSATTR_TYPE_COLOR, HSColor) { + unparsed_value = g_string_new(""); + }; + HSAttributeSimpleConstructor(HSATTR_TYPE_STRING, GString*) {}; + HSAttribute(const char* name, HSAttributeCustom custom, HSAttributeChangeCustom on_change) + : object(NULL), type(HSATTR_TYPE_CUSTOM), name(name), value(custom), + // all the other attributes: + unparsed_value(NULL), on_change(NULL), change_custom(on_change), + user_attribute(false), always_callback(false), + user_data(NULL), data(NULL) {}; + HSAttribute(const char* name, HSAttributeCustomInt custom, HSAttributeChangeCustom on_change) + : object(NULL), type(HSATTR_TYPE_CUSTOM_INT), name(name), value(custom), + // all the other attributes: + unparsed_value(NULL), on_change(NULL), change_custom(on_change), + user_attribute(false), always_callback(false), + user_data(NULL), data(NULL) {}; + + static HSAttribute LAST() { + return HSAttribute(); + }; +private: + HSAttribute() : value((int*)NULL) { + name = NULL; + }; +}; -#define ATTRIBUTE_LAST { .name = NULL } +#define ATTRIBUTE_LAST HSAttribute::LAST() void object_tree_init(); void object_tree_destroy(); @@ -161,17 +154,17 @@ void hsobject_destroy(HSObject* obj); void hsobject_link(HSObject* parent, HSObject* child, const char* name); void hsobject_unlink(HSObject* parent, HSObject* child); -void hsobject_unlink_by_name(HSObject* parent, char* name); +void hsobject_unlink_by_name(HSObject* parent, const char* name); void hsobject_link_rename(HSObject* parent, char* oldname, char* newname); void hsobject_link_rename_object(HSObject* parent, HSObject* child, char* newname); void hsobject_unlink_and_destroy(HSObject* parent, HSObject* child); HSObject* hsobject_by_path(char* path); -HSObject* hsobject_parse_path(char* path, char** unparsable); -HSObject* hsobject_parse_path_verbose(char* path, char** unparsable, GString* output); +HSObject* hsobject_parse_path(const char* path, const char** unparsable); +HSObject* hsobject_parse_path_verbose(const char* path, const char** unparsable, GString* output); -HSAttribute* hsattribute_parse_path(char* path); -HSAttribute* hsattribute_parse_path_verbose(char* path, GString* output); +HSAttribute* hsattribute_parse_path(const char* path); +HSAttribute* hsattribute_parse_path_verbose(const char* path, GString* output); void hsobject_set_attributes(HSObject* obj, HSAttribute* attributes); @@ -186,17 +179,17 @@ int attr_command(int argc, char* argv[], GString* output); int print_object_tree_command(int argc, char* argv[], GString* output); -int hsattribute_get_command(int argc, char* argv[], GString* output); +int hsattribute_get_command(int argc, const char* argv[], GString* output); int hsattribute_set_command(int argc, char* argv[], GString* output); bool hsattribute_is_read_only(HSAttribute* attr); int hsattribute_assign(HSAttribute* attr, const char* new_value_str, GString* output); void hsattribute_append_to_string(HSAttribute* attribute, GString* output); GString* hsattribute_to_string(HSAttribute* attribute); -void hsobject_complete_children(HSObject* obj, char* needle, char* prefix, +void hsobject_complete_children(HSObject* obj, const char* needle, const char* prefix, GString* output); void hsobject_complete_attributes(HSObject* obj, bool user_only, - char* needle, char* prefix, + const char* needle, const char* prefix, GString* output); int substitute_command(int argc, char* argv[], GString* output); int sprintf_command(int argc, char* argv[], GString* output); @@ -204,7 +197,7 @@ int userattribute_command(int argc, char* argv[], GString* output); int userattribute_remove_command(int argc, char* argv[], GString* output); -HSAttribute* hsattribute_create(HSObject* obj, char* name, char* type_str, +HSAttribute* hsattribute_create(HSObject* obj, const char* name, char* type_str, GString* output); bool userattribute_remove(HSAttribute* attr); int tmpattribute_command(int argc, char* argv[], GString* output); diff -Nru herbstluftwm-0.6.2/src/rules.c herbstluftwm-0.7.0/src/rules.c --- herbstluftwm-0.6.2/src/rules.c 2014-03-26 23:54:22.000000000 +0000 +++ herbstluftwm-0.7.0/src/rules.c 1970-01-01 00:00:00.000000000 +0000 @@ -1,848 +0,0 @@ -/** Copyright 2011-2013 Thorsten Wißmann. All rights reserved. - * - * This software is licensed under the "Simplified BSD License". - * See LICENSE for details */ - -#include "rules.h" -#include "globals.h" -#include "utils.h" -#include "ewmh.h" -#include "clientlist.h" -#include "ipc-protocol.h" -#include "hook.h" -#include "command.h" - -#include "glib-backports.h" -#include "glib-backports.h" -#include -#include -#include - -/// TYPES /// - -typedef struct { - char* name; - bool (*matches)(HSCondition* condition, HSClient* client); -} HSConditionType; - -typedef struct { - char* name; - void (*apply)(HSConsequence* consequence, HSClient* client, - HSClientChanges* changes); -} HSConsequenceType; - -/// DECLARATIONS /// -static int find_condition_type(char* name); -static int find_consequence_type(char* name); -static bool condition_string(HSCondition* rule, char* string); - -/// CONDITIONS /// -#define DECLARE_CONDITION(NAME) \ - static bool NAME(HSCondition* rule, HSClient* client) - -DECLARE_CONDITION(condition_class); -DECLARE_CONDITION(condition_instance); -DECLARE_CONDITION(condition_title); -DECLARE_CONDITION(condition_pid); -DECLARE_CONDITION(condition_maxage); -DECLARE_CONDITION(condition_windowtype); -DECLARE_CONDITION(condition_windowrole); - -/// CONSEQUENCES /// -#define DECLARE_CONSEQUENCE(NAME) \ -static void NAME(HSConsequence* cons, HSClient* client, \ - HSClientChanges* changes) - -DECLARE_CONSEQUENCE(consequence_tag); -DECLARE_CONSEQUENCE(consequence_focus); -DECLARE_CONSEQUENCE(consequence_manage); -DECLARE_CONSEQUENCE(consequence_index); -DECLARE_CONSEQUENCE(consequence_pseudotile); -DECLARE_CONSEQUENCE(consequence_fullscreen); -DECLARE_CONSEQUENCE(consequence_switchtag); -DECLARE_CONSEQUENCE(consequence_ewmhrequests); -DECLARE_CONSEQUENCE(consequence_ewmhnotify); -DECLARE_CONSEQUENCE(consequence_hook); -DECLARE_CONSEQUENCE(consequence_keymask); -DECLARE_CONSEQUENCE(consequence_monitor); - -/// GLOBALS /// - -static HSConditionType g_condition_types[] = { - { "class", condition_class }, - { "instance", condition_instance }, - { "title", condition_title }, - { "pid", condition_pid }, - { "maxage", condition_maxage }, - { "windowtype", condition_windowtype }, - { "windowrole", condition_windowrole }, -}; - -static int g_maxage_type; // index of "maxage" -static time_t g_current_rule_birth_time; // data from rules_apply() to condition_maxage() -static unsigned long long g_rule_label_index; // incremental index of rule label - -static HSConsequenceType g_consequence_types[] = { - { "tag", consequence_tag }, - { "index", consequence_index }, - { "focus", consequence_focus }, - { "switchtag", consequence_switchtag }, - { "manage", consequence_manage }, - { "pseudotile", consequence_pseudotile }, - { "fullscreen", consequence_fullscreen }, - { "ewmhrequests", consequence_ewmhrequests }, - { "ewmhnotify", consequence_ewmhnotify }, - { "hook", consequence_hook }, - { "keymask", consequence_keymask }, - { "monitor", consequence_monitor }, -}; - -static GQueue g_rules = G_QUEUE_INIT; // a list of HSRule* elements - -/// FUNCTIONS /// -// RULES // -void rules_init() { - g_maxage_type = find_condition_type("maxage"); - g_rule_label_index = 0; -} - -void rules_destroy() { - g_queue_foreach(&g_rules, (GFunc)rule_destroy, NULL); - g_queue_clear(&g_rules); -} - -// condition types // -static int find_condition_type(char* name) { - char* cn; - for (int i = 0; i < LENGTH(g_condition_types); i++) { - cn = g_condition_types[i].name; - if (!cn) break; - if (!strcmp(cn, name)) { - return i; - } - } - return -1; -} - -HSCondition* condition_create(int type, char op, char* value, GString* output) { - HSCondition cond; - if (op != '=' && type == g_maxage_type) { - g_string_append_printf(output, - "rule: Condition maxage only supports the = operator\n"); - return NULL; - } - switch (op) { - case '=': - if (type == g_maxage_type) { - cond.value_type = CONDITION_VALUE_TYPE_INTEGER; - if (1 != sscanf(value, "%d", &cond.value.integer)) { - g_string_append_printf(output, - "rule: Can not integer from \"%s\"\n", value); - return NULL; - } - } else { - cond.value_type = CONDITION_VALUE_TYPE_STRING; - cond.value.str = g_strdup(value); - } - break; - - case '~': - cond.value_type = CONDITION_VALUE_TYPE_REGEX; - int status = regcomp(&cond.value.reg.exp, value, REG_EXTENDED); - if (status != 0) { - char buf[ERROR_STRING_BUF_SIZE]; - regerror(status, &cond.value.reg.exp, buf, ERROR_STRING_BUF_SIZE); - g_string_append_printf(output, - "rule: Can not parse value \"%s\" from condition \"%s\": \"%s\"\n", - value, g_condition_types[type].name, buf); - return NULL; - } - cond.value.reg.str = g_strdup(value); - break; - - default: - g_string_append_printf(output, - "rule: Unknown rule condition operation \"%c\"\n", op); - return NULL; - break; - } - - cond.condition_type = type; - // move to heap - HSCondition* ptr = g_new(HSCondition, 1); - *ptr = cond; - return ptr; -} - -static void condition_destroy(HSCondition* cond) { - if (!cond) { - return; - } - // free members - switch(cond->value_type) { - case CONDITION_VALUE_TYPE_STRING: - free(cond->value.str); - break; - case CONDITION_VALUE_TYPE_REGEX: - regfree(&cond->value.reg.exp); - g_free(cond->value.reg.str); - break; - default: - break; - } - - // free cond itself - g_free(cond); -} - -// consequence types // -static int find_consequence_type(char* name) { - char* cn; - for (int i = 0; i < LENGTH(g_consequence_types); i++) { - cn = g_consequence_types[i].name; - if (!cn) break; - if (!strcmp(cn, name)) { - return i; - } - } - return -1; -} - -HSConsequence* consequence_create(int type, char op, char* value, GString* output) { - HSConsequence cons; - switch (op) { - case '=': - cons.value_type = CONSEQUENCE_VALUE_TYPE_STRING; - cons.value.str = g_strdup(value); - break; - - default: - g_string_append_printf(output, - "rule: Unknown rule consequence operation \"%c\"\n", op); - return NULL; - break; - } - - cons.type = type; - // move to heap - HSConsequence* ptr = g_new(HSConsequence, 1); - *ptr = cons; - return ptr; -} - -static void consequence_destroy(HSConsequence* cons) { - switch (cons->value_type) { - case CONSEQUENCE_VALUE_TYPE_STRING: - g_free(cons->value.str); - break; - } - g_free(cons); -} - -static bool rule_label_replace(HSRule* rule, char op, char* value, GString* output) { - switch (op) { - case '=': - if (*value == '\0') { - g_string_append_printf(output, - "rule: Rule label cannot be empty"); - return false; - break; - } - g_free(rule->label); - rule->label = g_strdup(value); - break; - default: - g_string_append_printf(output, - "rule: Unknown rule label operation \"%c\"\n", op); - return false; - break; - } - return true; -} - -// rules parsing // - -HSRule* rule_create() { - HSRule* rule = g_new0(HSRule, 1); - rule->once = false; - rule->birth_time = get_monotonic_timestamp(); - // Add name. Defaults to index number. - rule->label = g_strdup_printf("%llu", g_rule_label_index++); - return rule; -} - -void rule_destroy(HSRule* rule) { - // free conditions - for (int i = 0; i < rule->condition_count; i++) { - condition_destroy(rule->conditions[i]); - } - g_free(rule->conditions); - // free consequences - for (int i = 0; i < rule->consequence_count; i++) { - consequence_destroy(rule->consequences[i]); - } - g_free(rule->consequences); - // free label - g_free(rule->label); - // free rule itself - g_free(rule); -} - -void rule_complete(int argc, char** argv, int pos, GString* output) { - char* needle = (pos < argc) ? argv[pos] : ""; - GString* buf = g_string_sized_new(20); - - // complete against conditions - for (int i = 0; i < LENGTH(g_condition_types); i++) { - g_string_printf(buf, "%s=", g_condition_types[i].name); - try_complete_partial(needle, buf->str, output); - g_string_printf(buf, "%s~", g_condition_types[i].name); - try_complete_partial(needle, buf->str, output); - } - - // complete against consequences - for (int i = 0; i < LENGTH(g_consequence_types); i++) { - g_string_printf(buf, "%s=", g_consequence_types[i].name); - try_complete_partial(needle, buf->str, output); - } - - // complete label - try_complete_partial(needle, "label=", output); - // complete flags - try_complete(needle, "prepend", output); - try_complete(needle, "once", output); - try_complete(needle, "not", output); - try_complete(needle, "!", output); - try_complete(needle, "printlabel", output); - - g_string_free(buf, true); -} - -// Compares the id of two rules. -static gint rule_compare_label(const HSRule* a, const HSRule* b) { - return strcmp(a->label, b->label); -} - -// Looks up rules of a given label and removes them from the queue -static bool rule_find_pop(char* label) { - GList* rule = { NULL }; - bool status = false; // Will be returned as true if any is found - HSRule rule_find = { .label = label }; - while ((rule = g_queue_find_custom(&g_rules, &rule_find, - (GCompareFunc)rule_compare_label))) { - // Check if rule with label exists - if ( rule == NULL ) { - break; - } - status = true; - // If so, clear data - rule_destroy(rule->data); - // Remove and free empty link - g_queue_delete_link(&g_rules, rule); - } - return status; -} - -// List all rules in queue -static void rule_print_append_output(HSRule* rule, GString* output) { - g_string_append_printf(output, "label=%s\t", rule->label); - // Append conditions - for (int i = 0; i < rule->condition_count; i++) { - if (rule->conditions[i]->negated) { // Include flag if negated - g_string_append_printf(output, "not\t"); - } - g_string_append_printf(output, "%s=", - g_condition_types[rule->conditions[i]->condition_type].name); - switch (rule->conditions[i]->value_type) { - case CONDITION_VALUE_TYPE_STRING: - g_string_append_printf(output, "%s\t", - rule->conditions[i]->value.str); - break; - case CONDITION_VALUE_TYPE_REGEX: - g_string_append_printf(output, "%s\t", - rule->conditions[i]->value.reg.str); - break; - default: /* CONDITION_VALUE_TYPE_INTEGER: */ - g_string_append_printf(output, "%i\t", - rule->conditions[i]->value.integer); - break; - } - } - // Append consequences - for (int i = 0; i < rule->consequence_count; i++) { - g_string_append_printf(output, "%s=%s\t", - g_consequence_types[rule->consequences[i]->type].name, - rule->consequences[i]->value.str); - } - // Print new line - g_string_append_c(output, '\n'); -} - -int rule_print_all_command(int argc, char** argv, GString* output) { - // Print entry for each in the queue - g_queue_foreach(&g_rules, (GFunc)rule_print_append_output, output); - return 0; -} - -// parses an arg like NAME=VALUE to res_name, res_operation and res_value -bool tokenize_arg(char* condition, - char** res_name, char* res_operation, char** res_value) { - // ignore two leading dashes - if (condition[0] == '-' && condition[1] == '-') { - condition += 2; - } - - // get name - *res_name = condition; - - - // locate operation - char* op = strpbrk(condition, "=~"); - if (!op) { - return false; - } - *res_operation = *op; - *op = '\0'; // separate string at operation char - - // value is second one (starting after op character) - *res_value = op + 1; - return true; -} - -static void rule_add_condition(HSRule* rule, HSCondition* cond) { - rule->conditions = g_renew(HSCondition*, - rule->conditions, rule->condition_count + 1); - rule->conditions[rule->condition_count] = cond; - rule->condition_count++; -} - -static void rule_add_consequence(HSRule* rule, HSConsequence* cons) { - rule->consequences = g_renew(HSConsequence*, - rule->consequences, rule->consequence_count + 1); - rule->consequences[rule->consequence_count] = cons; - rule->consequence_count++; -} - - -int rule_add_command(int argc, char** argv, GString* output) { - // usage: rule COND=VAL ... then - - if (argc < 2) { - return HERBST_NEED_MORE_ARGS; - } - // temporary data structures - HSRule* rule = rule_create(); - HSCondition* cond; - HSConsequence* cons; - bool printlabel = false; - bool negated = false; - bool prepend = false; - struct { - char* name; - bool* flag; - } flags[] = { - { "prepend",&prepend }, - { "not", &negated }, - { "!", &negated }, - { "once", &rule->once }, - { "printlabel",&printlabel }, - }; - - // parse rule incrementally. always maintain a correct rule in rule - while (SHIFT(argc, argv)) { - char* name; - char* value; - char op; - - // is it a consequence or a condition? - bool consorcond = tokenize_arg(*argv, &name, &op, &value); - int type; - bool flag_found = false; - int flag_index = -1; - - for (int i = 0; i < LENGTH(flags); i++) { - if (!strcmp(flags[i].name, name)) { - flag_found = true; - flag_index = i; - break; - } - } - - if (flag_found) { - *flags[flag_index].flag = ! *flags[flag_index].flag; - } - - else if (consorcond && (type = find_condition_type(name)) >= 0) { - cond = condition_create(type, op, value, output); - if (!cond) { - rule_destroy(rule); - return HERBST_INVALID_ARGUMENT; - } - cond->negated = negated; - negated = false; - rule_add_condition(rule, cond); - } - - else if (consorcond && (type = find_consequence_type(name)) >= 0) { - cons = consequence_create(type, op, value, output); - if (!cons) { - rule_destroy(rule); - return HERBST_INVALID_ARGUMENT; - } - rule_add_consequence(rule, cons); - } - - // Check for a provided label, and replace default index if so - else if (consorcond && (!strcmp(name,"label"))) { - if (!rule_label_replace(rule, op, value, output)) { - rule_destroy(rule); - return HERBST_INVALID_ARGUMENT; - } - } - - else { - // need to hardcode "rule:" here because args are shifted - g_string_append_printf(output, - "rule: Unknown argument \"%s\"\n", *argv); - rule_destroy(rule); - return HERBST_INVALID_ARGUMENT; - } - } - - if (printlabel) { - g_string_append_printf(output, "%s\n", rule->label); - } - - if (prepend) g_queue_push_head(&g_rules, rule); - else g_queue_push_tail(&g_rules, rule); - return 0; -} - -void complete_against_rule_names(int argc, char** argv, int pos, GString* output) { - char* needle; - if (pos >= argc) { - needle = ""; - } else { - needle = argv[pos]; - } - // Complete labels - GList* cur_rule = g_queue_peek_head_link(&g_rules); - while (cur_rule != NULL) { - try_complete(needle, ((HSRule*)cur_rule->data)->label, output); - cur_rule = g_list_next(cur_rule); - } -} - - -int rule_remove_command(int argc, char** argv, GString* output) { - if (argc < 2) { - return HERBST_NEED_MORE_ARGS; - } - - if (!strcmp(argv[1], "--all") || !strcmp(argv[1], "-F")) { - // remove all rules - g_queue_foreach(&g_rules, (GFunc)rule_destroy, NULL); - g_queue_clear(&g_rules); - g_rule_label_index = 0; - return 0; - } - - // Deletes rule with given label - if (!rule_find_pop(argv[1])) { - g_string_append_printf(output, "Couldn't find rule: \"%s\"", argv[1]); - return HERBST_INVALID_ARGUMENT; - } - return 0; -} - -// rules applying // -void client_changes_init(HSClientChanges* changes, HSClient* client) { - memset(changes, 0, sizeof(HSClientChanges)); - changes->tree_index = g_string_new(""); - changes->focus = false; - changes->switchtag = false; - changes->manage = true; - changes->fullscreen = ewmh_is_fullscreen_set(client->window); - changes->keymask = g_string_new(""); -} - -void client_changes_free_members(HSClientChanges* changes) { - if (!changes) return; - if (changes->tag_name) { - g_string_free(changes->tag_name, true); - } - if (changes->tree_index) { - g_string_free(changes->tree_index, true); - } - if (changes->monitor_name) { - g_string_free(changes->monitor_name, true); - } -} - -// apply all rules to a certain client an save changes -void rules_apply(HSClient* client, HSClientChanges* changes) { - GList* cur = g_rules.head; - while (cur) { - HSRule* rule = cur->data; - bool matches = true; // if current condition matches - bool rule_match = true; // if entire rule matches - bool rule_expired = false; - g_current_rule_birth_time = rule->birth_time; - - // check all conditions - for (int i = 0; i < rule->condition_count; i++) { - int type = rule->conditions[i]->condition_type; - - if (!rule_match && type != g_maxage_type) { - // implement lazy AND && - // ... except for maxage - continue; - } - - matches = g_condition_types[type]. - matches(rule->conditions[i], client); - - if (!matches && !rule->conditions[i]->negated - && rule->conditions[i]->condition_type == g_maxage_type) { - // if if not negated maxage does not match anymore - // then it will never match again in the future - rule_expired = true; - } - - if (rule->conditions[i]->negated) { - matches = ! matches; - } - rule_match = rule_match && matches; - } - - if (rule_match) { - // apply all consequences - for (int i = 0; i < rule->consequence_count; i++) { - int type = rule->consequences[i]->type; - g_consequence_types[type]. - apply(rule->consequences[i], client, changes); - } - - } - - // remove it if not wanted or needed anymore - if ((rule_match && rule->once) || rule_expired) { - GList* next = cur->next; - rule_destroy(cur->data); - g_queue_remove_element(&g_rules, cur); - cur = next; - continue; - } - - // try next - cur = cur ? cur->next : NULL; - } -} - -/// CONDITIONS /// -static bool condition_string(HSCondition* rule, char* string) { - if (!rule || !string) { - return false; - } - - int status; - regmatch_t match; - int int_value; - switch (rule->value_type) { - case CONDITION_VALUE_TYPE_STRING: - return !strcmp(string, rule->value.str); - break; - case CONDITION_VALUE_TYPE_REGEX: - status = regexec(&rule->value.reg.exp, string, 1, &match, 0); - // only accept it, if it matches the entire string - if (status == 0 - && match.rm_so == 0 - && match.rm_eo == strlen(string)) { - return true; - } else { - return false; - } - break; - case CONDITION_VALUE_TYPE_INTEGER: - return (1 == sscanf(string, "%d", &int_value) - && int_value == rule->value.integer); - break; - } - return false; -} - -static bool condition_class(HSCondition* rule, HSClient* client) { - GString* window_class = window_class_to_g_string(g_display, client->window); - bool match = condition_string(rule, window_class->str); - g_string_free(window_class, true); - return match; -} - -static bool condition_instance(HSCondition* rule, HSClient* client) { - GString* inst = window_instance_to_g_string(g_display, client->window); - bool match = condition_string(rule, inst->str); - g_string_free(inst, true); - return match; -} - -static bool condition_title(HSCondition* rule, HSClient* client) { - return condition_string(rule, client->title->str); -} - -static bool condition_pid(HSCondition* rule, HSClient* client) { - if (client->pid < 0) { - return false; - } - if (rule->value_type == CONDITION_VALUE_TYPE_INTEGER) { - return rule->value.integer == client->pid; - } else { - char buf[1000]; // 1kb ought to be enough for every int - sprintf(buf, "%d", client->pid); - return condition_string(rule, buf); - } -} - -static bool condition_maxage(HSCondition* rule, HSClient* client) { - time_t diff = get_monotonic_timestamp() - g_current_rule_birth_time; - return (rule->value.integer >= diff); -} - -static bool condition_windowtype(HSCondition* rule, HSClient* client) { - // that only works for atom-type utf8-string, _NET_WM_WINDOW_TYPE is int - // GString* wintype= - // window_property_to_g_string(g_display, client->window, wintype_atom); - // => - // kinda duplicate from src/utils.c:window_properties_to_g_string() - // using different xatom type, and only calling XGetWindowProperty - // once, because we are sure we only need four bytes - long bufsize = 10; - char *buf; - Atom type_ret, wintype; - int format; - unsigned long items, bytes_left; - long offset = 0; - - int status = XGetWindowProperty( - g_display, - client->window, - g_netatom[NetWmWindowType], - offset, - bufsize, - False, - ATOM("ATOM"), - &type_ret, - &format, - &items, - &bytes_left, - (unsigned char**)&buf - ); - // we only need precisely four bytes (one Atom) - // if there are bytes left, something went wrong - if(status != Success || bytes_left > 0 || items < 1 || buf == NULL) { - return false; - } else { - wintype= *(Atom *)buf; - XFree(buf); - } - - for (int i = NetWmWindowTypeFIRST; i <= NetWmWindowTypeLAST; i++) { - // try to find the window type - if (wintype == g_netatom[i]) { - // if found, then treat the window type as a string value, - // which is registered in g_netatom_names[] - return condition_string(rule, g_netatom_names[i]); - } - } - - // if no valid window type has been found, - // it can not match - return false; -} - -static bool condition_windowrole(HSCondition* rule, HSClient* client) { - GString* role = window_property_to_g_string(g_display, client->window, - ATOM("WM_WINDOW_ROLE")); - if (!role) return false; - bool match = condition_string(rule, role->str); - g_string_free(role, true); - return match; -} - -/// CONSEQUENCES /// -void consequence_tag(HSConsequence* cons, - HSClient* client, HSClientChanges* changes) { - if (changes->tag_name) { - g_string_assign(changes->tag_name, cons->value.str); - } else { - changes->tag_name = g_string_new(cons->value.str); - } -} - -void consequence_focus(HSConsequence* cons, HSClient* client, - HSClientChanges* changes) { - changes->focus = string_to_bool(cons->value.str, changes->focus); -} - -void consequence_manage(HSConsequence* cons, HSClient* client, - HSClientChanges* changes) { - changes->manage = string_to_bool(cons->value.str, changes->manage); -} - -void consequence_index(HSConsequence* cons, HSClient* client, - HSClientChanges* changes) { - g_string_assign(changes->tree_index, cons->value.str); -} - -void consequence_pseudotile(HSConsequence* cons, HSClient* client, - HSClientChanges* changes) { - client->pseudotile = string_to_bool(cons->value.str, client->pseudotile); -} - -void consequence_fullscreen(HSConsequence* cons, HSClient* client, - HSClientChanges* changes) { - changes->fullscreen = string_to_bool(cons->value.str, changes->fullscreen); -} - -void consequence_switchtag(HSConsequence* cons, HSClient* client, - HSClientChanges* changes) { - changes->switchtag = string_to_bool(cons->value.str, changes->switchtag); -} - -void consequence_ewmhrequests(HSConsequence* cons, HSClient* client, - HSClientChanges* changes) { - // this is only a flag that is unused during initialization (during - // manage()) and so can be directly changed in the client - client->ewmhrequests = string_to_bool(cons->value.str, client->ewmhrequests); -} - -void consequence_ewmhnotify(HSConsequence* cons, HSClient* client, - HSClientChanges* changes) { - client->ewmhnotify = string_to_bool(cons->value.str, client->ewmhnotify); -} - -void consequence_hook(HSConsequence* cons, HSClient* client, - HSClientChanges* changes) { - GString* winid = g_string_sized_new(20); - g_string_printf(winid, "0x%lx", client->window); - char* hook_str[] = { "rule" , cons->value.str, winid->str }; - hook_emit(LENGTH(hook_str), hook_str); - g_string_free(winid, true); -} - -void consequence_keymask(HSConsequence* cons, - HSClient* client, HSClientChanges* changes) { - if (changes->keymask) { - g_string_assign(changes->keymask, cons->value.str); - } else { - changes->keymask = g_string_new(cons->value.str); - } -} - -void consequence_monitor(HSConsequence* cons, HSClient* client, - HSClientChanges* changes) { - if (changes->monitor_name) { - g_string_assign(changes->monitor_name, cons->value.str); - } else { - changes->monitor_name = g_string_new(cons->value.str); - } -} diff -Nru herbstluftwm-0.6.2/src/rules.cpp herbstluftwm-0.7.0/src/rules.cpp --- herbstluftwm-0.6.2/src/rules.cpp 1970-01-01 00:00:00.000000000 +0000 +++ herbstluftwm-0.7.0/src/rules.cpp 2015-10-14 13:27:40.000000000 +0000 @@ -0,0 +1,811 @@ +/** Copyright 2011-2013 Thorsten Wißmann. All rights reserved. + * + * This software is licensed under the "Simplified BSD License". + * See LICENSE for details */ + +#include "rules.h" +#include "globals.h" +#include "utils.h" +#include "ewmh.h" +#include "clientlist.h" +#include "ipc-protocol.h" +#include "hook.h" +#include "command.h" + +#include "glib-backports.h" +#include "glib-backports.h" +#include +#include +#include + +/// TYPES /// + +typedef struct { + const char* name; + bool (*matches)(HSCondition* condition, HSClient* client); +} HSConditionType; + +typedef struct { + const char* name; + void (*apply)(HSConsequence* consequence, HSClient* client, + HSClientChanges* changes); +} HSConsequenceType; + +/// DECLARATIONS /// +static int find_condition_type(const char* name); +static int find_consequence_type(const char* name); +static bool condition_string(HSCondition* rule, const char* string); + +/// CONDITIONS /// +#define DECLARE_CONDITION(NAME) \ + static bool NAME(HSCondition* rule, HSClient* client) + +DECLARE_CONDITION(condition_class); +DECLARE_CONDITION(condition_instance); +DECLARE_CONDITION(condition_title); +DECLARE_CONDITION(condition_pid); +DECLARE_CONDITION(condition_maxage); +DECLARE_CONDITION(condition_windowtype); +DECLARE_CONDITION(condition_windowrole); + +/// CONSEQUENCES /// +#define DECLARE_CONSEQUENCE(NAME) \ +static void NAME(HSConsequence* cons, HSClient* client, \ + HSClientChanges* changes) + +DECLARE_CONSEQUENCE(consequence_tag); +DECLARE_CONSEQUENCE(consequence_focus); +DECLARE_CONSEQUENCE(consequence_manage); +DECLARE_CONSEQUENCE(consequence_index); +DECLARE_CONSEQUENCE(consequence_pseudotile); +DECLARE_CONSEQUENCE(consequence_fullscreen); +DECLARE_CONSEQUENCE(consequence_switchtag); +DECLARE_CONSEQUENCE(consequence_ewmhrequests); +DECLARE_CONSEQUENCE(consequence_ewmhnotify); +DECLARE_CONSEQUENCE(consequence_hook); +DECLARE_CONSEQUENCE(consequence_keymask); +DECLARE_CONSEQUENCE(consequence_monitor); + +/// GLOBALS /// + +static HSConditionType g_condition_types[] = { + { "class", condition_class }, + { "instance", condition_instance }, + { "title", condition_title }, + { "pid", condition_pid }, + { "maxage", condition_maxage }, + { "windowtype", condition_windowtype }, + { "windowrole", condition_windowrole }, +}; + +static int g_maxage_type; // index of "maxage" +static time_t g_current_rule_birth_time; // data from rules_apply() to condition_maxage() +static unsigned long long g_rule_label_index; // incremental index of rule label + +static HSConsequenceType g_consequence_types[] = { + { "tag", consequence_tag }, + { "index", consequence_index }, + { "focus", consequence_focus }, + { "switchtag", consequence_switchtag }, + { "manage", consequence_manage }, + { "pseudotile", consequence_pseudotile }, + { "fullscreen", consequence_fullscreen }, + { "ewmhrequests", consequence_ewmhrequests }, + { "ewmhnotify", consequence_ewmhnotify }, + { "hook", consequence_hook }, + { "keymask", consequence_keymask }, + { "monitor", consequence_monitor }, +}; + +static GQueue g_rules = G_QUEUE_INIT; // a list of HSRule* elements + +/// FUNCTIONS /// +// RULES // +void rules_init() { + g_maxage_type = find_condition_type("maxage"); + g_rule_label_index = 0; +} + +void rules_destroy() { + g_queue_foreach(&g_rules, (GFunc)rule_destroy, NULL); + g_queue_clear(&g_rules); +} + +// condition types // +static int find_condition_type(const char* name) { + const char* cn; + for (int i = 0; i < LENGTH(g_condition_types); i++) { + cn = g_condition_types[i].name; + if (!cn) break; + if (!strcmp(cn, name)) { + return i; + } + } + return -1; +} + +HSCondition* condition_create(int type, char op, char* value, GString* output) { + HSCondition cond; + if (op != '=' && type == g_maxage_type) { + g_string_append_printf(output, + "rule: Condition maxage only supports the = operator\n"); + return NULL; + } + switch (op) { + case '=': { + if (type == g_maxage_type) { + cond.value_type = CONDITION_VALUE_TYPE_INTEGER; + if (1 != sscanf(value, "%d", &cond.value.integer)) { + g_string_append_printf(output, + "rule: Can not integer from \"%s\"\n", value); + return NULL; + } + } else { + cond.value_type = CONDITION_VALUE_TYPE_STRING; + cond.value.str = g_strdup(value); + } + break; + } + + case '~': { + cond.value_type = CONDITION_VALUE_TYPE_REGEX; + int status = regcomp(&cond.value.reg.exp, value, REG_EXTENDED); + if (status != 0) { + char buf[ERROR_STRING_BUF_SIZE]; + regerror(status, &cond.value.reg.exp, buf, ERROR_STRING_BUF_SIZE); + g_string_append_printf(output, + "rule: Can not parse value \"%s\" from condition \"%s\": \"%s\"\n", + value, g_condition_types[type].name, buf); + return NULL; + } + cond.value.reg.str = g_strdup(value); + break; + } + + default: + g_string_append_printf(output, + "rule: Unknown rule condition operation \"%c\"\n", op); + return NULL; + break; + } + + cond.condition_type = type; + // move to heap + HSCondition* ptr = g_new(HSCondition, 1); + *ptr = cond; + return ptr; +} + +static void condition_destroy(HSCondition* cond) { + if (!cond) { + return; + } + // free members + switch(cond->value_type) { + case CONDITION_VALUE_TYPE_STRING: + free(cond->value.str); + break; + case CONDITION_VALUE_TYPE_REGEX: + regfree(&cond->value.reg.exp); + g_free(cond->value.reg.str); + break; + default: + break; + } + + // free cond itself + g_free(cond); +} + +// consequence types // +static int find_consequence_type(const char* name) { + const char* cn; + for (int i = 0; i < LENGTH(g_consequence_types); i++) { + cn = g_consequence_types[i].name; + if (!cn) break; + if (!strcmp(cn, name)) { + return i; + } + } + return -1; +} + +HSConsequence* consequence_create(int type, char op, char* value, GString* output) { + HSConsequence cons; + switch (op) { + case '=': + cons.value_type = CONSEQUENCE_VALUE_TYPE_STRING; + cons.value.str = g_strdup(value); + break; + + default: + g_string_append_printf(output, + "rule: Unknown rule consequence operation \"%c\"\n", op); + return NULL; + break; + } + + cons.type = type; + // move to heap + HSConsequence* ptr = g_new(HSConsequence, 1); + *ptr = cons; + return ptr; +} + +static void consequence_destroy(HSConsequence* cons) { + switch (cons->value_type) { + case CONSEQUENCE_VALUE_TYPE_STRING: + g_free(cons->value.str); + break; + } + g_free(cons); +} + +static bool rule_label_replace(HSRule* rule, char op, char* value, GString* output) { + switch (op) { + case '=': + if (*value == '\0') { + g_string_append_printf(output, + "rule: Rule label cannot be empty"); + return false; + break; + } + g_free(rule->label); + rule->label = g_strdup(value); + break; + default: + g_string_append_printf(output, + "rule: Unknown rule label operation \"%c\"\n", op); + return false; + break; + } + return true; +} + +// rules parsing // + +HSRule* rule_create() { + HSRule* rule = g_new0(HSRule, 1); + rule->once = false; + rule->birth_time = get_monotonic_timestamp(); + // Add name. Defaults to index number. + rule->label = g_strdup_printf("%llu", g_rule_label_index++); + return rule; +} + +void rule_destroy(HSRule* rule) { + // free conditions + for (int i = 0; i < rule->condition_count; i++) { + condition_destroy(rule->conditions[i]); + } + g_free(rule->conditions); + // free consequences + for (int i = 0; i < rule->consequence_count; i++) { + consequence_destroy(rule->consequences[i]); + } + g_free(rule->consequences); + // free label + g_free(rule->label); + // free rule itself + g_free(rule); +} + +void rule_complete(int argc, char** argv, int pos, GString* output) { + const char* needle = (pos < argc) ? argv[pos] : ""; + GString* buf = g_string_sized_new(20); + + // complete against conditions + for (int i = 0; i < LENGTH(g_condition_types); i++) { + g_string_printf(buf, "%s=", g_condition_types[i].name); + try_complete_partial(needle, buf->str, output); + g_string_printf(buf, "%s~", g_condition_types[i].name); + try_complete_partial(needle, buf->str, output); + } + + // complete against consequences + for (int i = 0; i < LENGTH(g_consequence_types); i++) { + g_string_printf(buf, "%s=", g_consequence_types[i].name); + try_complete_partial(needle, buf->str, output); + } + + // complete label + try_complete_partial(needle, "label=", output); + // complete flags + try_complete(needle, "prepend", output); + try_complete(needle, "once", output); + try_complete(needle, "not", output); + try_complete(needle, "!", output); + try_complete(needle, "printlabel", output); + + g_string_free(buf, true); +} + +// Compares the id of two rules. +static gint rule_compare_label(const HSRule* a, const HSRule* b) { + return strcmp(a->label, b->label); +} + +// Looks up rules of a given label and removes them from the queue +static bool rule_find_pop(char* label) { + GList* rule = { NULL }; + bool status = false; // Will be returned as true if any is found + HSRule rule_find = { 0 }; + rule_find.label = label; + while ((rule = g_queue_find_custom(&g_rules, &rule_find, + (GCompareFunc)rule_compare_label))) { + // Check if rule with label exists + if ( rule == NULL ) { + break; + } + status = true; + // If so, clear data + rule_destroy((HSRule*)rule->data); + // Remove and free empty link + g_queue_delete_link(&g_rules, rule); + } + return status; +} + +// List all rules in queue +static void rule_print_append_output(HSRule* rule, GString* output) { + g_string_append_printf(output, "label=%s\t", rule->label); + // Append conditions + for (int i = 0; i < rule->condition_count; i++) { + if (rule->conditions[i]->negated) { // Include flag if negated + g_string_append_printf(output, "not\t"); + } + g_string_append_printf(output, "%s=", + g_condition_types[rule->conditions[i]->condition_type].name); + switch (rule->conditions[i]->value_type) { + case CONDITION_VALUE_TYPE_STRING: + g_string_append_printf(output, "%s\t", + rule->conditions[i]->value.str); + break; + case CONDITION_VALUE_TYPE_REGEX: + g_string_append_printf(output, "%s\t", + rule->conditions[i]->value.reg.str); + break; + default: /* CONDITION_VALUE_TYPE_INTEGER: */ + g_string_append_printf(output, "%i\t", + rule->conditions[i]->value.integer); + break; + } + } + // Append consequences + for (int i = 0; i < rule->consequence_count; i++) { + g_string_append_printf(output, "%s=%s\t", + g_consequence_types[rule->consequences[i]->type].name, + rule->consequences[i]->value.str); + } + // Print new line + g_string_append_c(output, '\n'); +} + +int rule_print_all_command(int argc, char** argv, GString* output) { + // Print entry for each in the queue + g_queue_foreach(&g_rules, (GFunc)rule_print_append_output, output); + return 0; +} + +// parses an arg like NAME=VALUE to res_name, res_operation and res_value +bool tokenize_arg(char* condition, + char** res_name, char* res_operation, char** res_value) { + // ignore two leading dashes + if (condition[0] == '-' && condition[1] == '-') { + condition += 2; + } + + // get name + *res_name = condition; + + + // locate operation + char* op = strpbrk(condition, "=~"); + if (!op) { + return false; + } + *res_operation = *op; + *op = '\0'; // separate string at operation char + + // value is second one (starting after op character) + *res_value = op + 1; + return true; +} + +static void rule_add_condition(HSRule* rule, HSCondition* cond) { + rule->conditions = g_renew(HSCondition*, + rule->conditions, rule->condition_count + 1); + rule->conditions[rule->condition_count] = cond; + rule->condition_count++; +} + +static void rule_add_consequence(HSRule* rule, HSConsequence* cons) { + rule->consequences = g_renew(HSConsequence*, + rule->consequences, rule->consequence_count + 1); + rule->consequences[rule->consequence_count] = cons; + rule->consequence_count++; +} + + +int rule_add_command(int argc, char** argv, GString* output) { + // usage: rule COND=VAL ... then + + if (argc < 2) { + return HERBST_NEED_MORE_ARGS; + } + // temporary data structures + HSRule* rule = rule_create(); + HSCondition* cond; + HSConsequence* cons; + bool printlabel = false; + bool negated = false; + bool prepend = false; + struct { + const char* name; + bool* flag; + } flags[] = { + { "prepend",&prepend }, + { "not", &negated }, + { "!", &negated }, + { "once", &rule->once }, + { "printlabel",&printlabel }, + }; + + // parse rule incrementally. always maintain a correct rule in rule + while (SHIFT(argc, argv)) { + char* name; + char* value; + char op; + + // is it a consequence or a condition? + bool consorcond = tokenize_arg(*argv, &name, &op, &value); + int type; + bool flag_found = false; + int flag_index = -1; + + for (int i = 0; i < LENGTH(flags); i++) { + if (!strcmp(flags[i].name, name)) { + flag_found = true; + flag_index = i; + break; + } + } + + if (flag_found) { + *flags[flag_index].flag = ! *flags[flag_index].flag; + } + + else if (consorcond && (type = find_condition_type(name)) >= 0) { + cond = condition_create(type, op, value, output); + if (!cond) { + rule_destroy(rule); + return HERBST_INVALID_ARGUMENT; + } + cond->negated = negated; + negated = false; + rule_add_condition(rule, cond); + } + + else if (consorcond && (type = find_consequence_type(name)) >= 0) { + cons = consequence_create(type, op, value, output); + if (!cons) { + rule_destroy(rule); + return HERBST_INVALID_ARGUMENT; + } + rule_add_consequence(rule, cons); + } + + // Check for a provided label, and replace default index if so + else if (consorcond && (!strcmp(name,"label"))) { + if (!rule_label_replace(rule, op, value, output)) { + rule_destroy(rule); + return HERBST_INVALID_ARGUMENT; + } + } + + else { + // need to hardcode "rule:" here because args are shifted + g_string_append_printf(output, + "rule: Unknown argument \"%s\"\n", *argv); + rule_destroy(rule); + return HERBST_INVALID_ARGUMENT; + } + } + + if (printlabel) { + g_string_append_printf(output, "%s\n", rule->label); + } + + if (prepend) g_queue_push_head(&g_rules, rule); + else g_queue_push_tail(&g_rules, rule); + return 0; +} + +void complete_against_rule_names(int argc, char** argv, int pos, GString* output) { + const char* needle; + if (pos >= argc) { + needle = ""; + } else { + needle = argv[pos]; + } + // Complete labels + GList* cur_rule = g_queue_peek_head_link(&g_rules); + while (cur_rule != NULL) { + try_complete(needle, ((HSRule*)cur_rule->data)->label, output); + cur_rule = g_list_next(cur_rule); + } +} + + +int rule_remove_command(int argc, char** argv, GString* output) { + if (argc < 2) { + return HERBST_NEED_MORE_ARGS; + } + + if (!strcmp(argv[1], "--all") || !strcmp(argv[1], "-F")) { + // remove all rules + g_queue_foreach(&g_rules, (GFunc)rule_destroy, NULL); + g_queue_clear(&g_rules); + g_rule_label_index = 0; + return 0; + } + + // Deletes rule with given label + if (!rule_find_pop(argv[1])) { + g_string_append_printf(output, "Couldn't find rule: \"%s\"", argv[1]); + return HERBST_INVALID_ARGUMENT; + } + return 0; +} + +// rules applying // +void client_changes_init(HSClientChanges* changes, HSClient* client) { + memset(changes, 0, sizeof(HSClientChanges)); + changes->tree_index = g_string_new(""); + changes->focus = false; + changes->switchtag = false; + changes->manage = true; + changes->fullscreen = ewmh_is_fullscreen_set(client->window); + changes->keymask = g_string_new(""); +} + +void client_changes_free_members(HSClientChanges* changes) { + if (!changes) return; + if (changes->tag_name) { + g_string_free(changes->tag_name, true); + } + if (changes->tree_index) { + g_string_free(changes->tree_index, true); + } + if (changes->monitor_name) { + g_string_free(changes->monitor_name, true); + } +} + +// apply all rules to a certain client an save changes +void rules_apply(HSClient* client, HSClientChanges* changes) { + GList* cur = g_rules.head; + while (cur) { + HSRule* rule = (HSRule*)cur->data; + bool matches = true; // if current condition matches + bool rule_match = true; // if entire rule matches + bool rule_expired = false; + g_current_rule_birth_time = rule->birth_time; + + // check all conditions + for (int i = 0; i < rule->condition_count; i++) { + int type = rule->conditions[i]->condition_type; + + if (!rule_match && type != g_maxage_type) { + // implement lazy AND && + // ... except for maxage + continue; + } + + matches = g_condition_types[type]. + matches(rule->conditions[i], client); + + if (!matches && !rule->conditions[i]->negated + && rule->conditions[i]->condition_type == g_maxage_type) { + // if if not negated maxage does not match anymore + // then it will never match again in the future + rule_expired = true; + } + + if (rule->conditions[i]->negated) { + matches = ! matches; + } + rule_match = rule_match && matches; + } + + if (rule_match) { + // apply all consequences + for (int i = 0; i < rule->consequence_count; i++) { + int type = rule->consequences[i]->type; + g_consequence_types[type]. + apply(rule->consequences[i], client, changes); + } + + } + + // remove it if not wanted or needed anymore + if ((rule_match && rule->once) || rule_expired) { + GList* next = cur->next; + rule_destroy((HSRule*)cur->data); + g_queue_remove_element(&g_rules, cur); + cur = next; + continue; + } + + // try next + cur = cur ? cur->next : NULL; + } +} + +/// CONDITIONS /// +static bool condition_string(HSCondition* rule, const char* string) { + if (!rule || !string) { + return false; + } + + int status; + regmatch_t match; + int int_value; + switch (rule->value_type) { + case CONDITION_VALUE_TYPE_STRING: + return !strcmp(string, rule->value.str); + break; + case CONDITION_VALUE_TYPE_REGEX: + status = regexec(&rule->value.reg.exp, string, 1, &match, 0); + // only accept it, if it matches the entire string + if (status == 0 + && match.rm_so == 0 + && match.rm_eo == strlen(string)) { + return true; + } else { + return false; + } + break; + case CONDITION_VALUE_TYPE_INTEGER: + return (1 == sscanf(string, "%d", &int_value) + && int_value == rule->value.integer); + break; + } + return false; +} + +static bool condition_class(HSCondition* rule, HSClient* client) { + GString* window_class = window_class_to_g_string(g_display, client->window); + bool match = condition_string(rule, window_class->str); + g_string_free(window_class, true); + return match; +} + +static bool condition_instance(HSCondition* rule, HSClient* client) { + GString* inst = window_instance_to_g_string(g_display, client->window); + bool match = condition_string(rule, inst->str); + g_string_free(inst, true); + return match; +} + +static bool condition_title(HSCondition* rule, HSClient* client) { + return condition_string(rule, client->title->str); +} + +static bool condition_pid(HSCondition* rule, HSClient* client) { + if (client->pid < 0) { + return false; + } + if (rule->value_type == CONDITION_VALUE_TYPE_INTEGER) { + return rule->value.integer == client->pid; + } else { + char buf[1000]; // 1kb ought to be enough for every int + sprintf(buf, "%d", client->pid); + return condition_string(rule, buf); + } +} + +static bool condition_maxage(HSCondition* rule, HSClient* client) { + time_t diff = get_monotonic_timestamp() - g_current_rule_birth_time; + return (rule->value.integer >= diff); +} + +static bool condition_windowtype(HSCondition* rule, HSClient* client) { + int windowtype = ewmh_get_window_type(client->window); + if (windowtype < 0) { + return false; + } else { + // if found, then treat the window type as a string value, + // which is registered in g_netatom_names[] + return condition_string(rule, g_netatom_names[windowtype]); + } + return false; +} + +static bool condition_windowrole(HSCondition* rule, HSClient* client) { + GString* role = window_property_to_g_string(g_display, client->window, + ATOM("WM_WINDOW_ROLE")); + if (!role) return false; + bool match = condition_string(rule, role->str); + g_string_free(role, true); + return match; +} + +/// CONSEQUENCES /// +void consequence_tag(HSConsequence* cons, + HSClient* client, HSClientChanges* changes) { + if (changes->tag_name) { + g_string_assign(changes->tag_name, cons->value.str); + } else { + changes->tag_name = g_string_new(cons->value.str); + } +} + +void consequence_focus(HSConsequence* cons, HSClient* client, + HSClientChanges* changes) { + changes->focus = string_to_bool(cons->value.str, changes->focus); +} + +void consequence_manage(HSConsequence* cons, HSClient* client, + HSClientChanges* changes) { + changes->manage = string_to_bool(cons->value.str, changes->manage); +} + +void consequence_index(HSConsequence* cons, HSClient* client, + HSClientChanges* changes) { + g_string_assign(changes->tree_index, cons->value.str); +} + +void consequence_pseudotile(HSConsequence* cons, HSClient* client, + HSClientChanges* changes) { + client->pseudotile = string_to_bool(cons->value.str, client->pseudotile); +} + +void consequence_fullscreen(HSConsequence* cons, HSClient* client, + HSClientChanges* changes) { + changes->fullscreen = string_to_bool(cons->value.str, changes->fullscreen); +} + +void consequence_switchtag(HSConsequence* cons, HSClient* client, + HSClientChanges* changes) { + changes->switchtag = string_to_bool(cons->value.str, changes->switchtag); +} + +void consequence_ewmhrequests(HSConsequence* cons, HSClient* client, + HSClientChanges* changes) { + // this is only a flag that is unused during initialization (during + // manage()) and so can be directly changed in the client + client->ewmhrequests = string_to_bool(cons->value.str, client->ewmhrequests); +} + +void consequence_ewmhnotify(HSConsequence* cons, HSClient* client, + HSClientChanges* changes) { + client->ewmhnotify = string_to_bool(cons->value.str, client->ewmhnotify); +} + +void consequence_hook(HSConsequence* cons, HSClient* client, + HSClientChanges* changes) { + GString* winid = g_string_sized_new(20); + g_string_printf(winid, "0x%lx", client->window); + const char* hook_str[] = { "rule" , cons->value.str, winid->str }; + hook_emit(LENGTH(hook_str), hook_str); + g_string_free(winid, true); +} + +void consequence_keymask(HSConsequence* cons, + HSClient* client, HSClientChanges* changes) { + if (changes->keymask) { + g_string_assign(changes->keymask, cons->value.str); + } else { + changes->keymask = g_string_new(cons->value.str); + } +} + +void consequence_monitor(HSConsequence* cons, HSClient* client, + HSClientChanges* changes) { + if (changes->monitor_name) { + g_string_assign(changes->monitor_name, cons->value.str); + } else { + changes->monitor_name = g_string_new(cons->value.str); + } +} diff -Nru herbstluftwm-0.6.2/src/settings.c herbstluftwm-0.7.0/src/settings.c --- herbstluftwm-0.6.2/src/settings.c 2014-03-26 23:54:22.000000000 +0000 +++ herbstluftwm-0.7.0/src/settings.c 1970-01-01 00:00:00.000000000 +0000 @@ -1,348 +0,0 @@ -/** Copyright 2011-2013 Thorsten Wißmann. All rights reserved. - * - * This software is licensed under the "Simplified BSD License". - * See LICENSE for details */ - -#include "globals.h" -#include "settings.h" -#include "clientlist.h" -#include "layout.h" -#include "ipc-protocol.h" -#include "utils.h" -#include "ewmh.h" -#include "object.h" - -#include "glib-backports.h" -#include -#include - -#define SET_INT(NAME, DEFAULT, CALLBACK) \ - { .name = (NAME), \ - .value = { .i = (DEFAULT) }, \ - .type = HS_Int, \ - .on_change = (CALLBACK), \ - } - -#define SET_STRING(NAME, DEFAULT, CALLBACK) \ - { .name = (NAME), \ - .value = { .s_init = (DEFAULT) }, \ - .type = HS_String, \ - .on_change = (CALLBACK), \ - } -#define SET_COMPAT(NAME, READ, WRITE) \ - { .name = (NAME), \ - .value = { \ - .compat = { \ - .read = (READ), \ - .write = (WRITE), \ - }, \ - }, \ - .type = HS_Compatiblity, \ - .on_change = NULL, \ - } - - -// often used callbacks: -#define RELAYOUT all_monitors_apply_layout -#define FR_COLORS reset_frame_colors -#define CL_COLORS reset_client_colors -#define LOCK_CHANGED monitors_lock_changed -#define FOCUS_LAYER tag_update_each_focus_layer -#define WMNAME ewmh_update_wmname - -// default settings: -SettingsPair g_settings[] = { - SET_INT( "frame_gap", 5, RELAYOUT ), - SET_INT( "frame_padding", 0, RELAYOUT ), - SET_INT( "window_gap", 0, RELAYOUT ), - SET_INT( "snap_distance", 10, NULL ), - SET_INT( "snap_gap", 5, NULL ), - SET_INT( "mouse_recenter_gap", 0, NULL ), - SET_STRING( "frame_border_active_color", "red", FR_COLORS ), - SET_STRING( "frame_border_normal_color", "blue", FR_COLORS ), - SET_STRING( "frame_border_inner_color", "black", FR_COLORS ), - SET_STRING( "frame_bg_normal_color", "black", FR_COLORS ), - SET_STRING( "frame_bg_active_color", "black", FR_COLORS ), - SET_INT( "frame_bg_transparent", 0, FR_COLORS ), - SET_INT( "frame_transparent_width", 0, FR_COLORS ), - SET_INT( "frame_border_width", 2, FR_COLORS ), - SET_INT( "frame_border_inner_width", 0, FR_COLORS ), - SET_INT( "frame_active_opacity", 100, FR_COLORS ), - SET_INT( "frame_normal_opacity", 100, FR_COLORS ), - SET_INT( "focus_crosses_monitor_boundaries", 1, NULL ), - SET_INT( "always_show_frame", 0, RELAYOUT ), - SET_INT( "default_direction_external_only", 0, NULL ), - SET_INT( "default_frame_layout", 0, FR_COLORS ), - SET_INT( "focus_follows_mouse", 0, NULL ), - SET_INT( "focus_stealing_prevention", 1, NULL ), - SET_INT( "swap_monitors_to_get_tag", 1, NULL ), - SET_INT( "raise_on_focus", 0, NULL ), - SET_INT( "raise_on_focus_temporarily", 0, FOCUS_LAYER ), - SET_INT( "raise_on_click", 1, NULL ), - SET_INT( "gapless_grid", 1, RELAYOUT ), - SET_INT( "smart_frame_surroundings", 0, RELAYOUT ), - SET_INT( "smart_window_surroundings", 0, RELAYOUT ), - SET_INT( "monitors_locked", 0, LOCK_CHANGED), - SET_INT( "auto_detect_monitors", 0, NULL ), - SET_INT( "pseudotile_center_threshold", 10, RELAYOUT ), - SET_INT( "update_dragged_clients", 0, NULL ), - SET_STRING( "tree_style", "*| +`--.", FR_COLORS ), - SET_STRING( "wmname", WINDOW_MANAGER_NAME, WMNAME ), - // settings for compatibility: - SET_COMPAT( "window_border_width", - "theme.tiling.active.border_width", "theme.border_width"), - SET_COMPAT( "window_border_inner_width", - "theme.tiling.active.inner_width", "theme.inner_width"), - SET_COMPAT( "window_border_inner_color", - "theme.tiling.active.inner_color", "theme.inner_color"), - SET_COMPAT( "window_border_active_color", - "theme.tiling.active.color", "theme.active.color"), - SET_COMPAT( "window_border_normal_color", - "theme.tiling.normal.color", "theme.normal.color"), - SET_COMPAT( "window_border_urgent_color", - "theme.tiling.urgent.color", "theme.urgent.color"), -}; - -int g_initial_monitors_locked = 0; -HSObject* g_settings_object; - -static GString* cb_on_change(struct HSAttribute* attr); -static void cb_read_compat(void* data, GString* output); -static GString* cb_write_compat(struct HSAttribute* attr, const char* new_value); - -int settings_count() { - return LENGTH(g_settings); -} - -void settings_init() { - // recreate all strings -> move them to heap - for (int i = 0; i < LENGTH(g_settings); i++) { - if (g_settings[i].type == HS_String) { - g_settings[i].value.str = g_string_new(g_settings[i].value.s_init); - } - if (g_settings[i].type == HS_Int) { - g_settings[i].old_value_i = 1; - } - } - settings_find("monitors_locked")->value.i = g_initial_monitors_locked; - - // create a settings object - g_settings_object = hsobject_create_and_link(hsobject_root(), "settings"); - // ensure everything is nulled that is not explicitely initialized - HSAttribute* attributes = g_new0(HSAttribute, LENGTH(g_settings)+1); - HSAttribute last = ATTRIBUTE_LAST; - attributes[LENGTH(g_settings)] = last; - for (int i = 0; i < LENGTH(g_settings); i++) { - SettingsPair* sp = g_settings + i; - if (sp->type == HS_String) { - HSAttribute cur = - ATTRIBUTE_STRING(sp->name, sp->value.str, cb_on_change); - attributes[i] = cur; - } else if (sp->type == HS_Int) { - HSAttribute cur = - ATTRIBUTE_INT(sp->name, sp->value.i, cb_on_change); - attributes[i] = cur; - } else if (sp->type == HS_Compatiblity) { - HSAttribute cur = - ATTRIBUTE_CUSTOM(sp->name, cb_read_compat, cb_write_compat); - cur.data = sp; - attributes[i] = cur; - } - } - hsobject_set_attributes(g_settings_object, attributes); - g_free(attributes); -} - -void settings_destroy() { - // free all strings - int i; - for (i = 0; i < LENGTH(g_settings); i++) { - if (g_settings[i].type == HS_String) { - g_string_free(g_settings[i].value.str, true); - } - } -} - -static GString* cb_on_change(struct HSAttribute* attr) { - int idx = attr - g_settings_object->attributes; - HSAssert (idx >= 0 || idx < LENGTH(g_settings)); - if (g_settings[idx].on_change) { - g_settings[idx].on_change(); - } - return NULL; -} - -static void cb_read_compat(void* data, GString* output) { - SettingsPair* sp = data; - char* cmd[] = { "attr_get", sp->value.compat.read, NULL }; - HSAssert(0 == hsattribute_get_command(LENGTH(cmd) - 1, cmd, output)); -} - -static GString* cb_write_compat(struct HSAttribute* attr, const char* new_value) { - SettingsPair* sp = attr->data; - GString* out = NULL; - if (0 != settings_set(sp, new_value)) { - out = g_string_new(""); - g_string_append_printf(out, "Can not set %s to \"%s\"\n", sp->name, new_value); - } - return out; -} - -SettingsPair* settings_find(char* name) { - return STATIC_TABLE_FIND_STR(SettingsPair, g_settings, name, name); -} - -char* settings_find_string(char* name) { - SettingsPair* sp = settings_find(name); - if (!sp) return NULL; - HSWeakAssert(sp->type == HS_String); - if (sp->type != HS_String) return NULL; - return sp->value.str->str; -} - -int settings_set_command(int argc, char** argv, GString* output) { - if (argc < 3) { - return HERBST_NEED_MORE_ARGS; - } - SettingsPair* pair = settings_find(argv[1]); - if (!pair) { - if (output != NULL) { - g_string_append_printf(output, - "%s: Setting \"%s\" not found\n", argv[0], argv[1]); - } - return HERBST_SETTING_NOT_FOUND; - } - int ret = settings_set(pair, argv[2]); - if (ret == HERBST_INVALID_ARGUMENT) { - g_string_append_printf(output, - "%s: Invalid value for setting \"%s\"\n", argv[0], argv[1]); - } - return ret; -} - -int settings_set(SettingsPair* pair, const char* value) { - if (pair->type == HS_Int) { - int new_value; - // parse value to int, if possible - if (1 == sscanf(value, "%d", &new_value)) { - if (new_value == pair->value.i) { - // nothing would be changed - return 0; - } - pair->value.i = new_value; - } else { - return HERBST_INVALID_ARGUMENT; - } - } else if (pair->type == HS_String) { - if (!strcmp(pair->value.str->str, value)) { - // nothing would be changed - return 0; - } - g_string_assign(pair->value.str, value); - } else if (pair->type == HS_Compatiblity) { - HSAttribute* attr = hsattribute_parse_path(pair->value.compat.write); - GString* out = g_string_new(""); - int status = hsattribute_assign(attr, value, out); - if (0 != status) { - HSError("Error when assigning: %s\n", out->str); - } - g_string_free(out, true); - return status; - } - // on successful change, call callback - if (pair->on_change) { - pair->on_change(); - } - return 0; -} - -int settings_get(int argc, char** argv, GString* output) { - if (argc < 2) { - return HERBST_NEED_MORE_ARGS; - } - SettingsPair* pair = settings_find(argv[1]); - if (!pair) { - g_string_append_printf(output, - "%s: Setting \"%s\" not found\n", argv[0], argv[1]); - return HERBST_SETTING_NOT_FOUND; - } - if (pair->type == HS_Int) { - g_string_append_printf(output, "%d", pair->value.i); - } else if (pair->type == HS_String) { - g_string_append(output, pair->value.str->str); - } else if (pair->type == HS_Compatiblity) { - HSAttribute* attr = hsattribute_parse_path(pair->value.compat.read); - GString* str = hsattribute_to_string(attr); - g_string_append(output, str->str); - g_string_free(str, true); - } - return 0; -} - -// toggle integer-like values -int settings_toggle(int argc, char** argv, GString* output) { - if (argc < 2) { - return HERBST_NEED_MORE_ARGS; - } - SettingsPair* pair = settings_find(argv[1]); - if (!pair) { - g_string_append_printf(output, - "%s: Setting \"%s\" not found\n", argv[0], argv[1]); - return HERBST_SETTING_NOT_FOUND; - } - if (pair->type == HS_Int) { - if (pair->value.i) { - /* store old value */ - pair->old_value_i = pair->value.i; - pair->value.i = 0; - } else { - /* recover old value */ - pair->value.i = pair->old_value_i; - } - } else { - // only toggle numbers - g_string_append_printf(output, - "%s: Only numbers can be toggled\n", argv[0]); - return HERBST_INVALID_ARGUMENT; - } - // on successful change, call callback - if (pair->on_change) { - pair->on_change(); - } - return 0; -} -static bool memberequals_settingspair(void* pmember, void* needle) { - char* str = *(char**)pmember; - SettingsPair* pair = needle; - if (pair->type == HS_Int) { - return pair->value.i == atoi(str); - } else { - return !strcmp(pair->value.str->str, str); - } -} - -int settings_cycle_value(int argc, char** argv, GString* output) { - if (argc < 3) { - return HERBST_NEED_MORE_ARGS; - } - char* cmd_name = argv[0]; - char* setting_name = argv[1]; // save this before shifting - SettingsPair* pair = settings_find(argv[1]); - if (!pair) { - g_string_append_printf(output, - "%s: Setting \"%s\" not found\n", argv[0], argv[1]); - return HERBST_SETTING_NOT_FOUND; - } - (void)SHIFT(argc, argv); - (void)SHIFT(argc, argv); - char** pcurrent = table_find(argv, sizeof(*argv), argc, 0, - memberequals_settingspair, pair); - int i = pcurrent ? ((INDEX_OF(argv, pcurrent) + 1) % argc) : 0; - int ret = settings_set(pair, argv[i]); - if (ret == HERBST_INVALID_ARGUMENT) { - g_string_append_printf(output, - "%s: Invalid value for setting \"%s\"\n", cmd_name, setting_name); - } - return ret; -} - diff -Nru herbstluftwm-0.6.2/src/settings.cpp herbstluftwm-0.7.0/src/settings.cpp --- herbstluftwm-0.6.2/src/settings.cpp 1970-01-01 00:00:00.000000000 +0000 +++ herbstluftwm-0.7.0/src/settings.cpp 2015-10-14 13:27:40.000000000 +0000 @@ -0,0 +1,367 @@ +/** Copyright 2011-2013 Thorsten Wißmann. All rights reserved. + * + * This software is licensed under the "Simplified BSD License". + * See LICENSE for details */ + +#include "globals.h" +#include "settings.h" +#include "clientlist.h" +#include "layout.h" +#include "ipc-protocol.h" +#include "utils.h" +#include "ewmh.h" +#include "object.h" + +#include "glib-backports.h" +#include +#include + +SettingsPair SET_INT(const char* name, int defaultval, void (*cb)()) +{ + SettingsPair sp; + sp.name = name; + sp.value.i = defaultval; + sp.type = HS_Int; + sp.on_change = (cb); + return sp; +} + +SettingsPair SET_STRING(const char* name, const char* defaultval, void (*cb)()) +{ + SettingsPair sp; + sp.name = name; + sp.value.s_init = defaultval; + sp.type = HS_String; + sp.on_change = (cb); + return sp; +} + +SettingsPair SET_COMPAT(const char* name, const char* read, const char* write) +{ + SettingsPair sp; + sp.name = name; + sp.value.compat.read = read; + sp.value.compat.write = write; + sp.type = HS_Compatiblity; + sp.on_change = NULL; + return sp; +} + +// often used callbacks: +#define RELAYOUT all_monitors_apply_layout +#define FR_COLORS reset_frame_colors +#define CL_COLORS reset_client_colors +#define LOCK_CHANGED monitors_lock_changed +#define FOCUS_LAYER tag_update_each_focus_layer +#define WMNAME ewmh_update_wmname + +// default settings: +SettingsPair g_settings[] = { + SET_INT( "frame_gap", 5, RELAYOUT ), + SET_INT( "frame_padding", 0, RELAYOUT ), + SET_INT( "window_gap", 0, RELAYOUT ), + SET_INT( "snap_distance", 10, NULL ), + SET_INT( "snap_gap", 5, NULL ), + SET_INT( "mouse_recenter_gap", 0, NULL ), + SET_STRING( "frame_border_active_color", "red", FR_COLORS ), + SET_STRING( "frame_border_normal_color", "blue", FR_COLORS ), + SET_STRING( "frame_border_inner_color", "black", FR_COLORS ), + SET_STRING( "frame_bg_normal_color", "black", FR_COLORS ), + SET_STRING( "frame_bg_active_color", "black", FR_COLORS ), + SET_INT( "frame_bg_transparent", 0, FR_COLORS ), + SET_INT( "frame_transparent_width", 0, FR_COLORS ), + SET_INT( "frame_border_width", 2, FR_COLORS ), + SET_INT( "frame_border_inner_width", 0, FR_COLORS ), + SET_INT( "frame_active_opacity", 100, FR_COLORS ), + SET_INT( "frame_normal_opacity", 100, FR_COLORS ), + SET_INT( "focus_crosses_monitor_boundaries", 1, NULL ), + SET_INT( "always_show_frame", 0, RELAYOUT ), + SET_INT( "default_direction_external_only", 0, NULL ), + SET_INT( "default_frame_layout", 0, FR_COLORS ), + SET_INT( "focus_follows_mouse", 0, NULL ), + SET_INT( "focus_stealing_prevention", 1, NULL ), + SET_INT( "swap_monitors_to_get_tag", 1, NULL ), + SET_INT( "raise_on_focus", 0, NULL ), + SET_INT( "raise_on_focus_temporarily", 0, FOCUS_LAYER ), + SET_INT( "raise_on_click", 1, NULL ), + SET_INT( "gapless_grid", 1, RELAYOUT ), + SET_INT( "smart_frame_surroundings", 0, RELAYOUT ), + SET_INT( "smart_window_surroundings", 0, RELAYOUT ), + SET_INT( "monitors_locked", 0, LOCK_CHANGED), + SET_INT( "auto_detect_monitors", 0, NULL ), + SET_INT( "pseudotile_center_threshold", 10, RELAYOUT ), + SET_INT( "update_dragged_clients", 0, NULL ), + SET_STRING( "tree_style", "*| +`--.", FR_COLORS ), + SET_STRING( "wmname", WINDOW_MANAGER_NAME, WMNAME ), + // settings for compatibility: + SET_COMPAT( "window_border_width", + "theme.tiling.active.border_width", "theme.border_width"), + SET_COMPAT( "window_border_inner_width", + "theme.tiling.active.inner_width", "theme.inner_width"), + SET_COMPAT( "window_border_inner_color", + "theme.tiling.active.inner_color", "theme.inner_color"), + SET_COMPAT( "window_border_active_color", + "theme.tiling.active.color", "theme.active.color"), + SET_COMPAT( "window_border_normal_color", + "theme.tiling.normal.color", "theme.normal.color"), + SET_COMPAT( "window_border_urgent_color", + "theme.tiling.urgent.color", "theme.urgent.color"), +}; + +// globals: +int g_initial_monitors_locked = 0; + +// module internals +static HSObject* g_settings_object; + +static GString* cb_on_change(HSAttribute* attr); +static void cb_read_compat(void* data, GString* output); +static GString* cb_write_compat(HSAttribute* attr, const char* new_value); + +int settings_count() { + return LENGTH(g_settings); +} + +void settings_init() { + // recreate all strings -> move them to heap + for (int i = 0; i < LENGTH(g_settings); i++) { + if (g_settings[i].type == HS_String) { + g_settings[i].value.str = g_string_new(g_settings[i].value.s_init); + } + if (g_settings[i].type == HS_Int) { + g_settings[i].old_value_i = 1; + } + } + settings_find("monitors_locked")->value.i = g_initial_monitors_locked; + + // create a settings object + g_settings_object = hsobject_create_and_link(hsobject_root(), "settings"); + // ensure everything is nulled that is not explicitely initialized + HSAttribute* attributes = g_new0(HSAttribute, LENGTH(g_settings)+1); + HSAttribute last = ATTRIBUTE_LAST; + attributes[LENGTH(g_settings)] = last; + for (int i = 0; i < LENGTH(g_settings); i++) { + SettingsPair* sp = g_settings + i; + if (sp->type == HS_String) { + HSAttribute cur = + ATTRIBUTE(sp->name, sp->value.str, cb_on_change); + attributes[i] = cur; + } else if (sp->type == HS_Int) { + HSAttribute cur = + ATTRIBUTE(sp->name, sp->value.i, cb_on_change); + attributes[i] = cur; + } else if (sp->type == HS_Compatiblity) { + HSAttribute cur = + ATTRIBUTE_CUSTOM(sp->name, (HSAttributeCustom)cb_read_compat, cb_write_compat); + cur.data = sp; + attributes[i] = cur; + } + } + hsobject_set_attributes(g_settings_object, attributes); + g_free(attributes); +} + +void settings_destroy() { + // free all strings + int i; + for (i = 0; i < LENGTH(g_settings); i++) { + if (g_settings[i].type == HS_String) { + g_string_free(g_settings[i].value.str, true); + } + } +} + +static GString* cb_on_change(HSAttribute* attr) { + int idx = attr - g_settings_object->attributes; + HSAssert (idx >= 0 || idx < LENGTH(g_settings)); + if (g_settings[idx].on_change) { + g_settings[idx].on_change(); + } + return NULL; +} + +static void cb_read_compat(void* data, GString* output) { + SettingsPair* sp = (SettingsPair*)data; + const char* cmd[] = { "attr_get", sp->value.compat.read, NULL }; + HSAssert(0 == hsattribute_get_command(LENGTH(cmd) - 1, cmd, output)); +} + +static GString* cb_write_compat(HSAttribute* attr, const char* new_value) { + SettingsPair* sp = (SettingsPair*)attr->data; + GString* out = NULL; + if (0 != settings_set(sp, new_value)) { + out = g_string_new(""); + g_string_append_printf(out, "Can not set %s to \"%s\"\n", sp->name, new_value); + } + return out; +} + +SettingsPair* settings_find(const char* name) { + return STATIC_TABLE_FIND_STR(SettingsPair, g_settings, name, name); +} + +SettingsPair* settings_get_by_index(int i) { + if (i < 0 || i >= LENGTH(g_settings)) return NULL; + return g_settings + i; +} + +char* settings_find_string(const char* name) { + SettingsPair* sp = settings_find(name); + if (!sp) return NULL; + HSWeakAssert(sp->type == HS_String); + if (sp->type != HS_String) return NULL; + return sp->value.str->str; +} + +int settings_set_command(int argc, const char** argv, GString* output) { + if (argc < 3) { + return HERBST_NEED_MORE_ARGS; + } + SettingsPair* pair = settings_find(argv[1]); + if (!pair) { + if (output != NULL) { + g_string_append_printf(output, + "%s: Setting \"%s\" not found\n", argv[0], argv[1]); + } + return HERBST_SETTING_NOT_FOUND; + } + int ret = settings_set(pair, argv[2]); + if (ret == HERBST_INVALID_ARGUMENT) { + g_string_append_printf(output, + "%s: Invalid value for setting \"%s\"\n", argv[0], argv[1]); + } + return ret; +} + +int settings_set(SettingsPair* pair, const char* value) { + if (pair->type == HS_Int) { + int new_value; + // parse value to int, if possible + if (1 == sscanf(value, "%d", &new_value)) { + if (new_value == pair->value.i) { + // nothing would be changed + return 0; + } + pair->value.i = new_value; + } else { + return HERBST_INVALID_ARGUMENT; + } + } else if (pair->type == HS_String) { + if (!strcmp(pair->value.str->str, value)) { + // nothing would be changed + return 0; + } + g_string_assign(pair->value.str, value); + } else if (pair->type == HS_Compatiblity) { + HSAttribute* attr = hsattribute_parse_path(pair->value.compat.write); + GString* out = g_string_new(""); + int status = hsattribute_assign(attr, value, out); + if (0 != status) { + HSError("Error when assigning: %s\n", out->str); + } + g_string_free(out, true); + return status; + } + // on successful change, call callback + if (pair->on_change) { + pair->on_change(); + } + return 0; +} + +int settings_get(int argc, char** argv, GString* output) { + if (argc < 2) { + return HERBST_NEED_MORE_ARGS; + } + SettingsPair* pair = settings_find(argv[1]); + if (!pair) { + g_string_append_printf(output, + "%s: Setting \"%s\" not found\n", argv[0], argv[1]); + return HERBST_SETTING_NOT_FOUND; + } + if (pair->type == HS_Int) { + g_string_append_printf(output, "%d", pair->value.i); + } else if (pair->type == HS_String) { + g_string_append(output, pair->value.str->str); + } else if (pair->type == HS_Compatiblity) { + HSAttribute* attr = hsattribute_parse_path(pair->value.compat.read); + GString* str = hsattribute_to_string(attr); + g_string_append(output, str->str); + g_string_free(str, true); + } + return 0; +} + +// toggle integer-like values +int settings_toggle(int argc, char** argv, GString* output) { + if (argc < 2) { + return HERBST_NEED_MORE_ARGS; + } + SettingsPair* pair = settings_find(argv[1]); + if (!pair) { + g_string_append_printf(output, + "%s: Setting \"%s\" not found\n", argv[0], argv[1]); + return HERBST_SETTING_NOT_FOUND; + } + if (pair->type == HS_Int) { + if (pair->value.i) { + /* store old value */ + pair->old_value_i = pair->value.i; + pair->value.i = 0; + } else { + /* recover old value */ + pair->value.i = pair->old_value_i; + } + } else { + // only toggle numbers + g_string_append_printf(output, + "%s: Only numbers can be toggled\n", argv[0]); + return HERBST_INVALID_ARGUMENT; + } + // on successful change, call callback + if (pair->on_change) { + pair->on_change(); + } + return 0; +} +static bool memberequals_settingspair(void* pmember, const void* needle) { + char* str = *(char**)pmember; + const SettingsPair* pair = (const SettingsPair*) needle; + if (pair->type == HS_Int) { + return pair->value.i == atoi(str); + } else if (pair->type == HS_Compatiblity) { + HSAttribute* attr = hsattribute_parse_path(pair->value.compat.read); + GString* attr_str = hsattribute_to_string(attr); + int equals = !strcmp(attr_str->str, str); + g_string_free(attr_str, true); + return equals; + } else { + return !strcmp(pair->value.str->str, str); + } +} + +int settings_cycle_value(int argc, char** argv, GString* output) { + if (argc < 3) { + return HERBST_NEED_MORE_ARGS; + } + char* cmd_name = argv[0]; + char* setting_name = argv[1]; // save this before shifting + SettingsPair* pair = settings_find(argv[1]); + if (!pair) { + g_string_append_printf(output, + "%s: Setting \"%s\" not found\n", argv[0], argv[1]); + return HERBST_SETTING_NOT_FOUND; + } + (void)SHIFT(argc, argv); + (void)SHIFT(argc, argv); + char** pcurrent = (char**)table_find(argv, sizeof(*argv), argc, 0, + memberequals_settingspair, pair); + int i = pcurrent ? ((INDEX_OF(argv, pcurrent) + 1) % argc) : 0; + int ret = settings_set(pair, argv[i]); + if (ret == HERBST_INVALID_ARGUMENT) { + g_string_append_printf(output, + "%s: Invalid value for setting \"%s\"\n", cmd_name, setting_name); + } + return ret; +} + diff -Nru herbstluftwm-0.6.2/src/settings.h herbstluftwm-0.7.0/src/settings.h --- herbstluftwm-0.6.2/src/settings.h 2014-03-26 23:54:22.000000000 +0000 +++ herbstluftwm-0.7.0/src/settings.h 2015-10-14 13:27:40.000000000 +0000 @@ -15,14 +15,14 @@ }; typedef struct { - char* name; + const char* name; union { int i; - char* s_init; + const char* s_init; GString* str; struct { - char* read; // attribute address for reading - char* write; // attribute address for writing + const char* read; // attribute address for reading + const char* write; // attribute address for writing } compat; } value; int old_value_i; @@ -30,17 +30,17 @@ void (*on_change)(); // what to call on change } SettingsPair; -extern SettingsPair g_settings[]; -int g_initial_monitors_locked; +extern int g_initial_monitors_locked; void settings_init(); void settings_destroy(); -SettingsPair* settings_find(char* name); -char* settings_find_string(char* name); +SettingsPair* settings_find(const char* name); +SettingsPair* settings_get_by_index(int i); +char* settings_find_string(const char* name); int settings_set(SettingsPair* pair, const char* value); -int settings_set_command(int argc, char** argv, GString* output); +int settings_set_command(int argc, const char** argv, GString* output); int settings_toggle(int argc, char** argv, GString* output); int settings_cycle_value(int argc, char** argv, GString* output); int settings_count(); diff -Nru herbstluftwm-0.6.2/src/stack.c herbstluftwm-0.7.0/src/stack.c --- herbstluftwm-0.6.2/src/stack.c 2014-03-26 23:54:22.000000000 +0000 +++ herbstluftwm-0.7.0/src/stack.c 1970-01-01 00:00:00.000000000 +0000 @@ -1,417 +0,0 @@ -#include "stack.h" - -#include "clientlist.h" -#include "ewmh.h" -#include "globals.h" -#include "utils.h" - -#include -#include -#include -#include - -static struct HSTreeInterface stack_nth_child(HSTree root, size_t idx); -static size_t stack_child_count(HSTree root); - -char* g_layer_names[LAYER_COUNT] = { - [ LAYER_FULLSCREEN ] = "Fullscreen-Layer" , - [ LAYER_FOCUS ] = "Focus-Layer" , - [ LAYER_NORMAL ] = "Normal Layer" , - [ LAYER_FRAMES ] = "Frame Layer" , -}; - -void stacklist_init() { -} - -void stacklist_destroy() { -} - - -HSStack* stack_create() { - return g_new0(HSStack, 1); -} - -void stack_destroy(HSStack* s) { - for (int i = 0; i < LAYER_COUNT; i++) { - if (s->top[i]) { - HSDebug("Warning: %s of stack %p was not empty on destroy\n", - g_layer_names[i], (void*)s); - } - } - g_free(s); -} - -static HSSlice* slice_create() { - HSSlice* s = g_new0(HSSlice, 1); - s->layer[0] = LAYER_NORMAL; - s->layer_count = 1; - return s; -} - -HSSlice* slice_create_window(Window window) { - HSSlice* s = slice_create(); - s->type = SLICE_WINDOW; - s->data.window = window; - return s; -} - -HSSlice* slice_create_frame(Window window) { - HSSlice* s = slice_create_window(window); - s->layer[0] = LAYER_FRAMES; - return s; -} - - -HSSlice* slice_create_client(HSClient* client) { - HSSlice* s = slice_create(); - s->type = SLICE_CLIENT; - s->data.client = client; - return s; -} - -HSSlice* slice_create_monitor(HSMonitor* monitor) { - HSSlice* s = slice_create(); - s->type = SLICE_MONITOR; - s->data.monitor = monitor; - return s; -} - -void slice_destroy(HSSlice* slice) { - g_free(slice); -} - -HSLayer slice_highest_layer(HSSlice* slice) { - HSLayer highest = LAYER_COUNT; - for (int i = 0; i < slice->layer_count; i++) { - if (slice->layer[i] < highest) { - highest = slice->layer[i]; - } - } - return highest; -} - -void stack_insert_slice(HSStack* s, HSSlice* elem) { - for (int i = 0; i < elem->layer_count; i++) { - int layer = elem->layer[i]; - s->top[layer] = g_list_prepend(s->top[layer], elem); - } - s->dirty = true; -} - -void stack_remove_slice(HSStack* s, HSSlice* elem) { - for (int i = 0; i < elem->layer_count; i++) { - int layer = elem->layer[i]; - s->top[layer] = g_list_remove(s->top[layer], elem); - } - s->dirty = true; -} - -static void slice_append_caption(HSTree root, GString* output) { - HSSlice* slice = (HSSlice*)root; - GString* monitor_name = g_string_new(""); - switch (slice->type) { - case SLICE_WINDOW: - g_string_append_printf(output, "Window 0x%lx", - slice->data.window); - break; - case SLICE_CLIENT: - g_string_append_printf(output, "Client 0x%lx \"%s\"", - slice->data.client->window, - slice->data.client->title->str); - break; - case SLICE_MONITOR: - if (slice->data.monitor->name != NULL) { - g_string_append_printf(monitor_name, " (\"%s\")", - slice->data.monitor->name->str); - } - g_string_append_printf(output, "Monitor %d%s with tag \"%s\"", - monitor_index_of(slice->data.monitor), - monitor_name->str, - slice->data.monitor->tag->name->str); - break; - } - g_string_free(monitor_name, true); -} - -static struct HSTreeInterface slice_nth_child(HSTree root, size_t idx) { - HSSlice* slice = (HSSlice*)root; - assert(slice->type == SLICE_MONITOR); - return stack_nth_child(slice->data.monitor->tag->stack, idx); -} - -static size_t slice_child_count(HSTree root) { - HSSlice* slice = (HSSlice*)root; - if (slice->type == SLICE_MONITOR) { - return stack_child_count(slice->data.monitor->tag->stack); - } else { - return 0; - } -} - -struct TmpLayer { - HSStack* stack; - HSLayer layer; -}; - -static struct HSTreeInterface layer_nth_child(HSTree root, size_t idx) { - struct TmpLayer* l = (struct TmpLayer*) root; - HSSlice* slice = (HSSlice*) g_list_nth_data(l->stack->top[l->layer], idx); - HSTreeInterface intface = { - .nth_child = slice_nth_child, - .child_count = slice_child_count, - .append_caption = slice_append_caption, - .data = slice, - .destructor = NULL, - }; - return intface; -} - -static size_t layer_child_count(HSTree root) { - struct TmpLayer* l = (struct TmpLayer*) root; - return g_list_length(l->stack->top[l->layer]); -} - -static void layer_append_caption(HSTree root, GString* output) { - struct TmpLayer* l = (struct TmpLayer*) root; - g_string_append_printf(output, "%s", g_layer_names[l->layer]); -} - - -static struct HSTreeInterface stack_nth_child(HSTree root, size_t idx) { - struct TmpLayer* l = g_new(struct TmpLayer, 1); - l->stack = (HSStack*) root; - l->layer = (HSLayer) idx; - - HSTreeInterface intface = { - .nth_child = layer_nth_child, - .child_count = layer_child_count, - .append_caption = layer_append_caption, - .data = l, - .destructor = (void (*)(HSTree))g_free, - }; - return intface; -} - -static size_t stack_child_count(HSTree root) { - return LAYER_COUNT; -} - -static void monitor_stack_append_caption(HSTree root, GString* output) { - // g_string_append_printf(*output, "Stack of all monitors"); -} - -int print_stack_command(int argc, char** argv, GString* output) { - struct TmpLayer tl = { - .stack = get_monitor_stack(), - .layer = LAYER_NORMAL, - }; - HSTreeInterface intface = { - .nth_child = layer_nth_child, - .child_count = layer_child_count, - .append_caption = monitor_stack_append_caption, - .data = &tl, - .destructor = NULL, - }; - tree_print_to(&intface, output); - return 0; -} - -int stack_window_count(HSStack* stack, bool real_clients) { - int counter = 0; - stack_to_window_buf(stack, NULL, 0, real_clients, &counter); - return -counter; -} - -/* stack to window buf */ -struct s2wb { - int len; - Window* buf; - int missing; /* number of slices that could not find space in buf */ - bool real_clients; /* whether to include windows that aren't clients */ - HSLayer layer; /* the layer the slice should be added to */ -}; - -static void slice_to_window_buf(HSSlice* s, struct s2wb* data) { - HSTag* tag; - if (slice_highest_layer(s) != data->layer) { - /** slice only is added to its highest layer. - * just skip it if the slice is not shown on this data->layer */ - return; - } - switch (s->type) { - case SLICE_CLIENT: - if (data->len) { - if (data->real_clients) { - data->buf[0] = s->data.client->window; - } else { - data->buf[0] = s->data.client->dec.decwin; - } - data->buf++; - data->len--; - } else { - data->missing++; - } - break; - case SLICE_WINDOW: - if (!data->real_clients) { - if (data->len) { - data->buf[0] = s->data.window; - data->buf++; - data->len--; - } else { - data->missing++; - } - } - break; - case SLICE_MONITOR: - tag = s->data.monitor->tag; - if (!data->real_clients) { - if (data->len) { - data->buf[0] = s->data.monitor->stacking_window; - data->buf++; - data->len--; - } else { - data->missing++; - } - } - int remain_len = 0; /* remaining length */ - stack_to_window_buf(tag->stack, data->buf, data->len, - data->real_clients, &remain_len); - int len_used = data->len - remain_len; - if (remain_len >= 0) { - data->buf += len_used; - data->len = remain_len; - } else { - data->len = 0; - data->missing += -remain_len; - } - break; - } -} - -void stack_to_window_buf(HSStack* stack, Window* buf, int len, - bool real_clients, int* remain_len) { - struct s2wb data = { - .len = len, - .buf = buf, - .missing = 0, - .real_clients = real_clients, - }; - for (int i = 0; i < LAYER_COUNT; i++) { - data.layer = i; - g_list_foreach(stack->top[i], (GFunc)slice_to_window_buf, &data); - } - if (!remain_len) { - // nothing to do - return; - } - if (data.missing == 0) { - *remain_len = data.len; - } else { - *remain_len = -data.missing; - } -} - -void stack_restack(HSStack* stack) { - if (!stack->dirty) { - return; - } - int count = stack_window_count(stack, false); - Window* buf = g_new0(Window, count); - stack_to_window_buf(stack, buf, count, false, NULL); - XRestackWindows(g_display, buf, count); - stack->dirty = false; - ewmh_update_client_list_stacking(); - g_free(buf); -} - -void stack_raise_slide(HSStack* stack, HSSlice* slice) { - for (int i = 0; i < slice->layer_count; i++) { - // remove slice from list - stack->top[slice->layer[i]] = g_list_remove(stack->top[slice->layer[i]], slice); - // and insert it again at the top - stack->top[slice->layer[i]] = g_list_prepend(stack->top[slice->layer[i]], slice); - } - stack->dirty = true; - // TODO: maybe only update the specific range and not the entire stack - // update - stack_restack(stack); -} - -void stack_mark_dirty(HSStack* s) { - s->dirty = true; -} - -void stack_slice_add_layer(HSStack* stack, HSSlice* slice, HSLayer layer) { - for (int i = 0; i < slice->layer_count; i++) { - if (slice->layer[i] == layer) { - /* nothing to do */ - return; - } - } - slice->layer[slice->layer_count] = layer; - slice->layer_count++; - stack->top[layer] = g_list_prepend(stack->top[layer], slice); - stack->dirty = true; -} - -void stack_slice_remove_layer(HSStack* stack, HSSlice* slice, HSLayer layer) { - int i; - for (i = 0; i < slice->layer_count; i++) { - if (slice->layer[i] == layer) { - break; - } - } - /* remove slice from layer in the stack */ - stack->top[layer] = g_list_remove(stack->top[layer], slice); - stack->dirty = true; - if (i >= slice->layer_count) { - HSDebug("remove layer: slice %p not in %s\n", (void*)slice, - g_layer_names[layer]); - return; - } - /* remove layer in slice */ - slice->layer_count--; - size_t len = sizeof(HSLayer) * (slice->layer_count - i); - memmove(slice->layer + i, slice->layer + i + 1, len); -} - -Window stack_lowest_window(HSStack* stack) { - for (int i = LAYER_COUNT - 1; i >= 0; i--) { - GList* last = g_list_last(stack->top[i]); - while (last) { - HSSlice* slice = (HSSlice*)last->data; - Window w = 0; - switch (slice->type) { - case SLICE_CLIENT: - w = slice->data.client->dec.decwin; - break; - case SLICE_WINDOW: - w = slice->data.window; - break; - case SLICE_MONITOR: - w = stack_lowest_window(slice->data.monitor->tag->stack); - break; - } - if (w) { - return w; - } - last = g_list_previous(last); - } - } - // if no window was found - return 0; -} - -bool stack_is_layer_empty(HSStack* s, HSLayer layer) { - return s->top[layer] == NULL; -} - -void stack_clear_layer(HSStack* stack, HSLayer layer) { - while (!stack_is_layer_empty(stack, layer)) { - HSSlice* slice = stack->top[layer]->data; - stack_slice_remove_layer(stack, slice, layer); - stack->dirty = true; - } -} - diff -Nru herbstluftwm-0.6.2/src/stack.cpp herbstluftwm-0.7.0/src/stack.cpp --- herbstluftwm-0.6.2/src/stack.cpp 1970-01-01 00:00:00.000000000 +0000 +++ herbstluftwm-0.7.0/src/stack.cpp 2015-10-14 13:27:40.000000000 +0000 @@ -0,0 +1,418 @@ +#include "stack.h" + +#include "clientlist.h" +#include "ewmh.h" +#include "globals.h" +#include "utils.h" + +#include +#include +#include +#include + +static struct HSTreeInterface stack_nth_child(HSTree root, size_t idx); +static size_t stack_child_count(HSTree root); + +const std::arrayg_layer_names = + ArrayInitializer({ + { LAYER_FOCUS , "Focus-Layer" }, + { LAYER_FULLSCREEN , "Fullscreen-Layer" }, + { LAYER_NORMAL , "Normal Layer" }, + { LAYER_FRAMES , "Frame Layer" }, +}).a; + +void stacklist_init() { +} + +void stacklist_destroy() { +} + + +HSStack* stack_create() { + return g_new0(HSStack, 1); +} + +void stack_destroy(HSStack* s) { + for (int i = 0; i < LAYER_COUNT; i++) { + if (s->top[i]) { + HSDebug("Warning: %s of stack %p was not empty on destroy\n", + g_layer_names[i], (void*)s); + } + } + g_free(s); +} + +static HSSlice* slice_create() { + HSSlice* s = g_new0(HSSlice, 1); + s->layer[0] = LAYER_NORMAL; + s->layer_count = 1; + return s; +} + +HSSlice* slice_create_window(Window window) { + HSSlice* s = slice_create(); + s->type = SLICE_WINDOW; + s->data.window = window; + return s; +} + +HSSlice* slice_create_frame(Window window) { + HSSlice* s = slice_create_window(window); + s->layer[0] = LAYER_FRAMES; + return s; +} + + +HSSlice* slice_create_client(HSClient* client) { + HSSlice* s = slice_create(); + s->type = SLICE_CLIENT; + s->data.client = client; + return s; +} + +HSSlice* slice_create_monitor(HSMonitor* monitor) { + HSSlice* s = slice_create(); + s->type = SLICE_MONITOR; + s->data.monitor = monitor; + return s; +} + +void slice_destroy(HSSlice* slice) { + g_free(slice); +} + +HSLayer slice_highest_layer(HSSlice* slice) { + HSLayer highest = LAYER_COUNT; + for (int i = 0; i < slice->layer_count; i++) { + if (slice->layer[i] < highest) { + highest = slice->layer[i]; + } + } + return highest; +} + +void stack_insert_slice(HSStack* s, HSSlice* elem) { + for (int i = 0; i < elem->layer_count; i++) { + int layer = elem->layer[i]; + s->top[layer] = g_list_prepend(s->top[layer], elem); + } + s->dirty = true; +} + +void stack_remove_slice(HSStack* s, HSSlice* elem) { + for (int i = 0; i < elem->layer_count; i++) { + int layer = elem->layer[i]; + s->top[layer] = g_list_remove(s->top[layer], elem); + } + s->dirty = true; +} + +static void slice_append_caption(HSTree root, GString* output) { + HSSlice* slice = (HSSlice*)root; + GString* monitor_name = g_string_new(""); + switch (slice->type) { + case SLICE_WINDOW: + g_string_append_printf(output, "Window 0x%lx", + slice->data.window); + break; + case SLICE_CLIENT: + g_string_append_printf(output, "Client 0x%lx \"%s\"", + slice->data.client->window, + slice->data.client->title->str); + break; + case SLICE_MONITOR: + if (slice->data.monitor->name != NULL) { + g_string_append_printf(monitor_name, " (\"%s\")", + slice->data.monitor->name->str); + } + g_string_append_printf(output, "Monitor %d%s with tag \"%s\"", + monitor_index_of(slice->data.monitor), + monitor_name->str, + slice->data.monitor->tag->name->str); + break; + } + g_string_free(monitor_name, true); +} + +static struct HSTreeInterface slice_nth_child(HSTree root, size_t idx) { + HSSlice* slice = (HSSlice*)root; + assert(slice->type == SLICE_MONITOR); + return stack_nth_child(slice->data.monitor->tag->stack, idx); +} + +static size_t slice_child_count(HSTree root) { + HSSlice* slice = (HSSlice*)root; + if (slice->type == SLICE_MONITOR) { + return stack_child_count(slice->data.monitor->tag->stack); + } else { + return 0; + } +} + +struct TmpLayer { + HSStack* stack; + HSLayer layer; +}; + +static struct HSTreeInterface layer_nth_child(HSTree root, size_t idx) { + struct TmpLayer* l = (struct TmpLayer*) root; + HSSlice* slice = (HSSlice*) g_list_nth_data(l->stack->top[l->layer], idx); + HSTreeInterface intface = { + /* .nth_child = */ slice_nth_child, + /* .child_count = */ slice_child_count, + /* .append_caption = */ slice_append_caption, + /* .data = */ slice, + /* .destructor = */ NULL, + }; + return intface; +} + +static size_t layer_child_count(HSTree root) { + struct TmpLayer* l = (struct TmpLayer*) root; + return g_list_length(l->stack->top[l->layer]); +} + +static void layer_append_caption(HSTree root, GString* output) { + struct TmpLayer* l = (struct TmpLayer*) root; + g_string_append_printf(output, "%s", g_layer_names[l->layer]); +} + + +static struct HSTreeInterface stack_nth_child(HSTree root, size_t idx) { + struct TmpLayer* l = g_new(struct TmpLayer, 1); + l->stack = (HSStack*) root; + l->layer = (HSLayer) idx; + + HSTreeInterface intface = { + /* .nth_child = */ layer_nth_child, + /* .child_count = */ layer_child_count, + /* .append_caption = */ layer_append_caption, + /* .data = */ l, + /* .destructor = */ (void (*)(HSTree))g_free, + }; + return intface; +} + +static size_t stack_child_count(HSTree root) { + return LAYER_COUNT; +} + +static void monitor_stack_append_caption(HSTree root, GString* output) { + // g_string_append_printf(*output, "Stack of all monitors"); +} + +int print_stack_command(int argc, char** argv, GString* output) { + struct TmpLayer tl = { + /* .stack = */ get_monitor_stack(), + /* .layer = */ LAYER_NORMAL, + }; + HSTreeInterface intface = { + /* .nth_child = */ layer_nth_child, + /* .child_count = */ layer_child_count, + /* .append_caption = */ monitor_stack_append_caption, + /* .data = */ &tl, + /* .destructor = */ NULL, + }; + tree_print_to(&intface, output); + return 0; +} + +int stack_window_count(HSStack* stack, bool real_clients) { + int counter = 0; + stack_to_window_buf(stack, NULL, 0, real_clients, &counter); + return -counter; +} + +/* stack to window buf */ +struct s2wb { + int len; + Window* buf; + int missing; /* number of slices that could not find space in buf */ + bool real_clients; /* whether to include windows that aren't clients */ + HSLayer layer; /* the layer the slice should be added to */ +}; + +static void slice_to_window_buf(HSSlice* s, struct s2wb* data) { + HSTag* tag; + if (slice_highest_layer(s) != data->layer) { + /** slice only is added to its highest layer. + * just skip it if the slice is not shown on this data->layer */ + return; + } + switch (s->type) { + case SLICE_CLIENT: + if (data->len) { + if (data->real_clients) { + data->buf[0] = s->data.client->window; + } else { + data->buf[0] = s->data.client->dec.decwin; + } + data->buf++; + data->len--; + } else { + data->missing++; + } + break; + case SLICE_WINDOW: + if (!data->real_clients) { + if (data->len) { + data->buf[0] = s->data.window; + data->buf++; + data->len--; + } else { + data->missing++; + } + } + break; + case SLICE_MONITOR: + tag = s->data.monitor->tag; + if (!data->real_clients) { + if (data->len) { + data->buf[0] = s->data.monitor->stacking_window; + data->buf++; + data->len--; + } else { + data->missing++; + } + } + int remain_len = 0; /* remaining length */ + stack_to_window_buf(tag->stack, data->buf, data->len, + data->real_clients, &remain_len); + int len_used = data->len - remain_len; + if (remain_len >= 0) { + data->buf += len_used; + data->len = remain_len; + } else { + data->len = 0; + data->missing += -remain_len; + } + break; + } +} + +void stack_to_window_buf(HSStack* stack, Window* buf, int len, + bool real_clients, int* remain_len) { + struct s2wb data = { + /* .len = */ len, + /* .buf = */ buf, + /* .missing = */ 0, + /* .real_clients = */ real_clients, + }; + for (int i = 0; i < LAYER_COUNT; i++) { + data.layer = (HSLayer)i; + g_list_foreach(stack->top[i], (GFunc)slice_to_window_buf, &data); + } + if (!remain_len) { + // nothing to do + return; + } + if (data.missing == 0) { + *remain_len = data.len; + } else { + *remain_len = -data.missing; + } +} + +void stack_restack(HSStack* stack) { + if (!stack->dirty) { + return; + } + int count = stack_window_count(stack, false); + Window* buf = g_new0(Window, count); + stack_to_window_buf(stack, buf, count, false, NULL); + XRestackWindows(g_display, buf, count); + stack->dirty = false; + ewmh_update_client_list_stacking(); + g_free(buf); +} + +void stack_raise_slide(HSStack* stack, HSSlice* slice) { + for (int i = 0; i < slice->layer_count; i++) { + // remove slice from list + stack->top[slice->layer[i]] = g_list_remove(stack->top[slice->layer[i]], slice); + // and insert it again at the top + stack->top[slice->layer[i]] = g_list_prepend(stack->top[slice->layer[i]], slice); + } + stack->dirty = true; + // TODO: maybe only update the specific range and not the entire stack + // update + stack_restack(stack); +} + +void stack_mark_dirty(HSStack* s) { + s->dirty = true; +} + +void stack_slice_add_layer(HSStack* stack, HSSlice* slice, HSLayer layer) { + for (int i = 0; i < slice->layer_count; i++) { + if (slice->layer[i] == layer) { + /* nothing to do */ + return; + } + } + slice->layer[slice->layer_count] = layer; + slice->layer_count++; + stack->top[layer] = g_list_prepend(stack->top[layer], slice); + stack->dirty = true; +} + +void stack_slice_remove_layer(HSStack* stack, HSSlice* slice, HSLayer layer) { + int i; + for (i = 0; i < slice->layer_count; i++) { + if (slice->layer[i] == layer) { + break; + } + } + /* remove slice from layer in the stack */ + stack->top[layer] = g_list_remove(stack->top[layer], slice); + stack->dirty = true; + if (i >= slice->layer_count) { + HSDebug("remove layer: slice %p not in %s\n", (void*)slice, + g_layer_names[layer]); + return; + } + /* remove layer in slice */ + slice->layer_count--; + size_t len = sizeof(HSLayer) * (slice->layer_count - i); + memmove(slice->layer + i, slice->layer + i + 1, len); +} + +Window stack_lowest_window(HSStack* stack) { + for (int i = LAYER_COUNT - 1; i >= 0; i--) { + GList* last = g_list_last(stack->top[i]); + while (last) { + HSSlice* slice = (HSSlice*)last->data; + Window w = 0; + switch (slice->type) { + case SLICE_CLIENT: + w = slice->data.client->dec.decwin; + break; + case SLICE_WINDOW: + w = slice->data.window; + break; + case SLICE_MONITOR: + w = stack_lowest_window(slice->data.monitor->tag->stack); + break; + } + if (w) { + return w; + } + last = g_list_previous(last); + } + } + // if no window was found + return 0; +} + +bool stack_is_layer_empty(HSStack* s, HSLayer layer) { + return s->top[layer] == NULL; +} + +void stack_clear_layer(HSStack* stack, HSLayer layer) { + while (!stack_is_layer_empty(stack, layer)) { + HSSlice* slice = (HSSlice*)stack->top[layer]->data; + stack_slice_remove_layer(stack, slice, layer); + stack->dirty = true; + } +} + diff -Nru herbstluftwm-0.6.2/src/stack.h herbstluftwm-0.7.0/src/stack.h --- herbstluftwm-0.6.2/src/stack.h 2014-03-26 23:54:22.000000000 +0000 +++ herbstluftwm-0.7.0/src/stack.h 2015-10-14 13:27:40.000000000 +0000 @@ -9,17 +9,18 @@ #include #include "glib-backports.h" #include +#include -typedef enum Layer { +enum HSLayer { /* layers on each monitor, from top to bottom */ LAYER_FOCUS, LAYER_FULLSCREEN, LAYER_NORMAL, LAYER_FRAMES, LAYER_COUNT, -} HSLayer; +}; -extern char* g_layer_names[]; +extern const std::array g_layer_names; typedef enum SliceType { SLICE_CLIENT, @@ -29,7 +30,6 @@ struct HSClient; struct HSMonitor; -struct GList; typedef struct HSSlice { HSSliceType type; diff -Nru herbstluftwm-0.6.2/src/tag.c herbstluftwm-0.7.0/src/tag.c --- herbstluftwm-0.6.2/src/tag.c 2014-03-26 23:54:22.000000000 +0000 +++ herbstluftwm-0.7.0/src/tag.c 1970-01-01 00:00:00.000000000 +0000 @@ -1,549 +0,0 @@ -/** Copyright 2011-2013 Thorsten Wißmann. All rights reserved. - * - * This software is licensed under the "Simplified BSD License". - * See LICENSE for details */ - -#include -#include -#include - -#include "tag.h" - -#include "globals.h" -#include "clientlist.h" -#include "ipc-protocol.h" -#include "utils.h" -#include "hook.h" -#include "layout.h" -#include "stack.h" -#include "ewmh.h" -#include "monitor.h" -#include "settings.h" - -bool g_tag_flags_dirty = true; -HSObject* g_tag_object; -HSObject* g_tag_by_name; -int* g_raise_on_focus_temporarily; - -static int tag_rename(HSTag* tag, char* name, GString* output); - -void tag_init() { - g_tags = g_array_new(false, false, sizeof(HSTag*)); - g_raise_on_focus_temporarily = &(settings_find("raise_on_focus_temporarily") - ->value.i); - g_tag_object = hsobject_create_and_link(hsobject_root(), "tags"); - HSAttribute attributes[] = { - ATTRIBUTE_UINT("count", g_tags->len, ATTR_READ_ONLY), - ATTRIBUTE_LAST, - }; - hsobject_set_attributes(g_tag_object, attributes); - g_tag_by_name = hsobject_create_and_link(g_tag_object, "by-name"); -} - -static void tag_free(HSTag* tag) { - if (tag->frame) { - HSClient** buf; - size_t count; - frame_destroy(tag->frame, &buf, &count); - if (count) { - g_free(buf); - } - } - stack_destroy(tag->stack); - hsobject_unlink_and_destroy(g_tag_by_name, tag->object); - g_string_free(tag->name, true); - g_string_free(tag->display_name, true); - g_free(tag); -} - -void tag_destroy() { - int i; - for (i = 0; i < g_tags->len; i++) { - HSTag* tag = g_array_index(g_tags, HSTag*, i); - tag_free(tag); - } - g_array_free(g_tags, true); - hsobject_unlink_and_destroy(g_tag_object, g_tag_by_name); - hsobject_unlink_and_destroy(hsobject_root(), g_tag_object); -} - - -HSTag* find_tag(char* name) { - int i; - for (i = 0; i < g_tags->len; i++) { - if (!strcmp(g_array_index(g_tags, HSTag*, i)->name->str, name)) { - return g_array_index(g_tags, HSTag*, i); - } - } - return NULL; -} - -int tag_index_of(HSTag* tag) { - for (int i = 0; i < g_tags->len; i++) { - if (g_array_index(g_tags, HSTag*, i) == tag) { - return i; - } - } - return -1; -} - -HSTag* get_tag_by_index(int index) { - if (index < 0 || index >= g_tags->len) { - return NULL; - } - return g_array_index(g_tags, HSTag*, index); -} - -HSTag* get_tag_by_index_str(char* index_str, bool skip_visible_tags) { - int index = atoi(index_str); - // index must be treated relative, if it's first char is + or - - bool is_relative = array_find("+-", 2, sizeof(char), &index_str[0]) >= 0; - HSMonitor* monitor = get_current_monitor(); - if (is_relative) { - int current = tag_index_of(monitor->tag); - int delta = index; - index = delta + current; - // ensure index is valid - index = MOD(index, g_tags->len); - if (skip_visible_tags) { - HSTag* tag = g_array_index(g_tags, HSTag*, index); - for (int i = 0; find_monitor_with_tag(tag); i++) { - if (i >= g_tags->len) { - // if we tried each tag then there is no invisible tag - return NULL; - } - index += delta; - index = MOD(index, g_tags->len); - tag = g_array_index(g_tags, HSTag*, index); - } - } - } else { - // if it is absolute, then check index - if (index < 0 || index >= g_tags->len) { - HSDebug("invalid tag index %d\n", index); - return NULL; - } - } - return g_array_index(g_tags, HSTag*, index); -} - -HSTag* find_unused_tag() { - for (int i = 0; i < g_tags->len; i++) { - if (!find_monitor_with_tag(g_array_index(g_tags, HSTag*, i))) { - return g_array_index(g_tags, HSTag*, i); - } - } - return NULL; -} - -static GString* tag_attr_floating(HSAttribute* attr) { - HSTag* tag = container_of(attr->value.b, HSTag, floating); - HSMonitor* m = find_monitor_with_tag(tag); - if (m != NULL) { - monitor_apply_layout(m); - } - return NULL; -} - -static GString* tag_attr_name(HSAttribute* attr) { - HSTag* tag = container_of(attr->value.str, HSTag, display_name); - GString* error = g_string_new(""); - int status = tag_rename(tag, tag->display_name->str, error); - if (status == 0) { - g_string_free(error, true); - return NULL; - } else { - return error; - } -} - -static void sum_up_clientframes(HSFrame* frame, void* data) { - if (frame->type == TYPE_CLIENTS) { - (*(int*)data)++; - } -} - -static int tag_attr_frame_count(void* data) { - HSTag* tag = (HSTag*) data; - int i = 0; - frame_do_recursive_data(tag->frame, sum_up_clientframes, 0, &i); - return i; -} - -static void sum_up_clients(HSFrame* frame, void* data) { - if (frame->type == TYPE_CLIENTS) { - *(int*)data += frame->content.clients.count; - } -} - -static int tag_attr_client_count(void* data) { - HSTag* tag = (HSTag*) data; - int i = 0; - frame_do_recursive_data(tag->frame, sum_up_clients, 0, &i); - return i; -} - - -static int tag_attr_curframe_windex(void* data) { - HSTag* tag = (HSTag*) data; - HSFrame* frame = frame_current_selection_below(tag->frame); - return frame->content.clients.selection; -} - -static int tag_attr_curframe_wcount(void* data) { - HSTag* tag = (HSTag*) data; - HSFrame* frame = frame_current_selection_below(tag->frame); - return frame->content.clients.count; -} - -static int tag_attr_index(void* data) { - HSTag* tag = (HSTag*) data; - return tag_index_of(tag); -} - -HSTag* add_tag(char* name) { - HSTag* find_result = find_tag(name); - if (find_result) { - // nothing to do - return find_result; - } - HSTag* tag = g_new0(HSTag, 1); - tag->stack = stack_create(); - tag->frame = frame_create_empty(NULL, tag); - tag->name = g_string_new(name); - tag->display_name = g_string_new(name); - tag->floating = false; - g_array_append_val(g_tags, tag); - - // create object - tag->object = hsobject_create_and_link(g_tag_by_name, name); - tag->object->data = tag; - HSAttribute attributes[] = { - ATTRIBUTE_STRING("name", tag->display_name, tag_attr_name), - ATTRIBUTE_BOOL( "floating", tag->floating, tag_attr_floating), - ATTRIBUTE_CUSTOM_INT("index", tag_attr_index, ATTR_READ_ONLY), - ATTRIBUTE_CUSTOM_INT("frame_count", tag_attr_frame_count, ATTR_READ_ONLY), - ATTRIBUTE_CUSTOM_INT("client_count", tag_attr_client_count, ATTR_READ_ONLY), - ATTRIBUTE_CUSTOM_INT("curframe_windex",tag_attr_curframe_windex, ATTR_READ_ONLY), - ATTRIBUTE_CUSTOM_INT("curframe_wcount",tag_attr_curframe_wcount, ATTR_READ_ONLY), - ATTRIBUTE_LAST, - }; - hsobject_set_attributes(tag->object, attributes); - - ewmh_update_desktops(); - ewmh_update_desktop_names(); - tag_set_flags_dirty(); - return tag; -} - -int tag_add_command(int argc, char** argv, GString* output) { - if (argc < 2) { - return HERBST_NEED_MORE_ARGS; - } - if (!strcmp("", argv[1])) { - g_string_append_printf(output, - "%s: An empty tag name is not permitted\n", argv[0]); - return HERBST_INVALID_ARGUMENT; - } - HSTag* tag = add_tag(argv[1]); - hook_emit_list("tag_added", tag->name->str, NULL); - return 0; -} - -static int tag_rename(HSTag* tag, char* name, GString* output) { - if (find_tag(name)) { - g_string_append_printf(output, - "Error: Tag \"%s\" already exists\n", name); - return HERBST_TAG_IN_USE; - } - hsobject_link_rename(g_tag_by_name, tag->name->str, name); - g_string_assign(tag->name, name); - g_string_assign(tag->display_name, name); - ewmh_update_desktop_names(); - hook_emit_list("tag_renamed", tag->name->str, NULL); - return 0; -} - -int tag_rename_command(int argc, char** argv, GString* output) { - if (argc < 3) { - return HERBST_NEED_MORE_ARGS; - } - HSTag* tag = find_tag(argv[1]); - if (!tag) { - g_string_append_printf(output, - "%s: Tag \"%s\" not found\n", argv[0], argv[1]); - return HERBST_INVALID_ARGUMENT; - } - return tag_rename(tag, argv[2], output); -} - -int tag_remove_command(int argc, char** argv, GString* output) { - // usage: remove TAG [TARGET] - // it removes an TAG and moves all its wins to TARGET - // if no TARGET is given, current tag is used - if (argc < 2) { - return HERBST_NEED_MORE_ARGS; - } - HSTag* tag = find_tag(argv[1]); - HSTag* target = (argc >= 3) ? find_tag(argv[2]) : get_current_monitor()->tag; - if (!tag) { - g_string_append_printf(output, - "%s: Tag \"%s\" not found\n", argv[0], argv[1]); - return HERBST_INVALID_ARGUMENT; - } else if (!target) { - g_string_append_printf(output, - "%s: Tag \"%s\" not found\n", argv[0], argv[2]); - } else if (tag == target) { - g_string_append_printf(output, - "%s: Cannot merge tag \"%s\" into itself\n", argv[0], argv[1]); - return HERBST_INVALID_ARGUMENT; - } - if (find_monitor_with_tag(tag)) { - g_string_append_printf(output, - "%s: Cannot merge the currently viewed tag\n", argv[0]); - return HERBST_TAG_IN_USE; - } - // prevent dangling tag_previous pointers - all_monitors_replace_previous_tag(tag, target); - // save all these windows - HSClient** buf; - size_t count; - frame_destroy(tag->frame, &buf, &count); - tag->frame = NULL; - int i; - for (i = 0; i < count; i++) { - HSClient* client = buf[i]; - stack_remove_slice(client->tag->stack, client->slice); - client->tag = target; - stack_insert_slice(client->tag->stack, client->slice); - ewmh_window_update_tag(client->window, client->tag); - frame_insert_client(target->frame, buf[i]); - } - HSMonitor* monitor_target = find_monitor_with_tag(target); - if (monitor_target) { - // if target monitor is viewed, then show windows - monitor_apply_layout(monitor_target); - for (i = 0; i < count; i++) { - client_set_visible(buf[i], true); - } - } - g_free(buf); - // remove tag - char* oldname = g_strdup(tag->name->str); - tag_free(tag); - for (i = 0; i < g_tags->len; i++) { - if (g_array_index(g_tags, HSTag*, i) == tag) { - g_array_remove_index(g_tags, i); - break; - } - } - ewmh_update_current_desktop(); - ewmh_update_desktops(); - ewmh_update_desktop_names(); - tag_set_flags_dirty(); - hook_emit_list("tag_removed", oldname, target->name->str, NULL); - g_free(oldname); - return 0; -} - -int tag_set_floating_command(int argc, char** argv, GString* output) { - // usage: floating [[tag] on|off|toggle] - HSTag* tag = get_current_monitor()->tag; - char* action = (argc > 1) ? argv[1] : "toggle"; - if (argc >= 3) { - // if a tag is specified - tag = find_tag(argv[1]); - action = argv[2]; - if (!tag) { - g_string_append_printf(output, - "%s: Tag \"%s\" not found\n", argv[0], argv[1]); - return HERBST_INVALID_ARGUMENT; - } - } - - bool new_value = string_to_bool(action, tag->floating); - - if (!strcmp(action, "status")) { - // just print status - g_string_append(output, tag->floating ? "on" : "off"); - } else { - // assign new value and rearrange if needed - tag->floating = new_value; - - HSMonitor* m = find_monitor_with_tag(tag); - HSDebug("setting tag:%s->floating to %s\n", tag->name->str, tag->floating ? "on" : "off"); - if (m != NULL) { - monitor_apply_layout(m); - } - } - return 0; -} - -static void client_update_tag_flags(void* key, void* client_void, void* data) { - (void) key; - (void) data; - HSClient* client = client_void; - if (client) { - TAG_SET_FLAG(client->tag, TAG_FLAG_USED); - if (client->urgent) { - TAG_SET_FLAG(client->tag, TAG_FLAG_URGENT); - } - } -} - -void tag_force_update_flags() { - g_tag_flags_dirty = false; - // unset all tags - for (int i = 0; i < g_tags->len; i++) { - g_array_index(g_tags, HSTag*, i)->flags = 0; - } - // update flags - clientlist_foreach(client_update_tag_flags, NULL); -} - -void tag_update_flags() { - if (g_tag_flags_dirty) { - tag_force_update_flags(); - } -} - -void tag_set_flags_dirty() { - g_tag_flags_dirty = true; - hook_emit_list("tag_flags", NULL); -} - -void ensure_tags_are_available() { - if (g_tags->len > 0) { - // nothing to do - return; - } - add_tag("default"); -} - -HSTag* find_tag_with_toplevel_frame(HSFrame* frame) { - int i; - for (i = 0; i < g_tags->len; i++) { - HSTag* m = g_array_index(g_tags, HSTag*, i); - if (m->frame == frame) { - return m; - } - } - return NULL; -} - -int tag_move_window_command(int argc, char** argv, GString* output) { - if (argc < 2) { - return HERBST_NEED_MORE_ARGS; - } - HSTag* target = find_tag(argv[1]); - if (!target) { - g_string_append_printf(output, - "%s: Tag \"%s\" not found\n", argv[0], argv[1]); - return HERBST_INVALID_ARGUMENT; - } - tag_move_focused_client(target); - return 0; -} - -int tag_move_window_by_index_command(int argc, char** argv, GString* output) { - if (argc < 2) { - return HERBST_NEED_MORE_ARGS; - } - bool skip_visible = false; - if (argc >= 3 && !strcmp(argv[2], "--skip-visible")) { - skip_visible = true; - } - HSTag* tag = get_tag_by_index_str(argv[1], skip_visible); - if (!tag) { - g_string_append_printf(output, - "%s: Invalid index \"%s\"\n", argv[0], argv[1]); - return HERBST_INVALID_ARGUMENT; - } - tag_move_focused_client(tag); - return 0; -} - -void tag_move_focused_client(HSTag* target) { - HSClient* client = frame_focused_client(get_current_monitor()->tag->frame); - if (client == 0) { - // nothing to do - return; - } - tag_move_client(client, target); -} - -void tag_move_client(HSClient* client, HSTag* target) { - HSTag* tag_source = client->tag; - HSMonitor* monitor_source = find_monitor_with_tag(tag_source); - if (tag_source == target) { - // nothing to do - return; - } - HSMonitor* monitor_target = find_monitor_with_tag(target); - frame_remove_client(tag_source->frame, client); - // insert window into target - frame_insert_client(target->frame, client); - // enfoce it to be focused on the target tag - frame_focus_client(target->frame, client); - stack_remove_slice(client->tag->stack, client->slice); - client->tag = target; - stack_insert_slice(client->tag->stack, client->slice); - ewmh_window_update_tag(client->window, client->tag); - - // refresh things, hide things, layout it, and then show it if needed - if (monitor_source && !monitor_target) { - // window is moved to invisible tag - // so hide it - client_set_visible(client, false); - } - monitor_apply_layout(monitor_source); - monitor_apply_layout(monitor_target); - if (!monitor_source && monitor_target) { - client_set_visible(client, true); - } - if (monitor_target == get_current_monitor()) { - frame_focus_recursive(monitor_target->tag->frame); - } - else if (monitor_source == get_current_monitor()) { - frame_focus_recursive(monitor_source->tag->frame); - } - tag_set_flags_dirty(); -} - -void tag_update_focus_layer(HSTag* tag) { - HSClient* focus = frame_focused_client(tag->frame); - stack_clear_layer(tag->stack, LAYER_FOCUS); - if (focus) { - // enforce raise_on_focus_temporarily if there is at least one - // fullscreen window or if the tag is in tiling mode - if (!stack_is_layer_empty(tag->stack, LAYER_FULLSCREEN) - || *g_raise_on_focus_temporarily - || focus->tag->floating == false) { - stack_slice_add_layer(tag->stack, focus->slice, LAYER_FOCUS); - } - } - HSMonitor* monitor = find_monitor_with_tag(tag); - if (monitor) { - monitor_restack(monitor); - } -} - -void tag_foreach(void (*action)(HSTag*,void*), void* data) { - for (int i = 0; i < g_tags->len; i++) { - HSTag* tag = g_array_index(g_tags, HSTag*, i); - action(tag, data); - } -} - -static void tag_update_focus_layer_helper(HSTag* tag, void* data) { - (void) data; - tag_update_focus_layer(tag); -} -void tag_update_each_focus_layer() { - tag_foreach(tag_update_focus_layer_helper, NULL); -} - -void tag_update_focus_objects() { - hsobject_link(g_tag_object, get_current_monitor()->tag->object, "focus"); -} - diff -Nru herbstluftwm-0.6.2/src/tag.cpp herbstluftwm-0.7.0/src/tag.cpp --- herbstluftwm-0.6.2/src/tag.cpp 1970-01-01 00:00:00.000000000 +0000 +++ herbstluftwm-0.7.0/src/tag.cpp 2015-11-16 13:04:34.000000000 +0000 @@ -0,0 +1,586 @@ +/** Copyright 2011-2013 Thorsten Wißmann. All rights reserved. + * + * This software is licensed under the "Simplified BSD License". + * See LICENSE for details */ + +#include +#include +#include + +#include "tag.h" + +#include "globals.h" +#include "clientlist.h" +#include "ipc-protocol.h" +#include "utils.h" +#include "hook.h" +#include "layout.h" +#include "stack.h" +#include "ewmh.h" +#include "monitor.h" +#include "settings.h" + +static GArray* g_tags; // Array of HSTag* +static bool g_tag_flags_dirty = true; +static HSObject* g_tag_object; +static HSObject* g_tag_by_name; +static int* g_raise_on_focus_temporarily; + +static int tag_rename(HSTag* tag, char* name, GString* output); + +void tag_init() { + g_tags = g_array_new(false, false, sizeof(HSTag*)); + g_raise_on_focus_temporarily = &(settings_find("raise_on_focus_temporarily") + ->value.i); + g_tag_object = hsobject_create_and_link(hsobject_root(), "tags"); + HSAttribute attributes[] = { + ATTRIBUTE("count", g_tags->len, ATTR_READ_ONLY), + ATTRIBUTE_LAST, + }; + hsobject_set_attributes(g_tag_object, attributes); + g_tag_by_name = hsobject_create_and_link(g_tag_object, "by-name"); +} + +static void tag_free(HSTag* tag) { + if (tag->frame) { + HSClient** buf; + size_t count; + frame_destroy(tag->frame, &buf, &count); + if (count) { + g_free(buf); + } + } + stack_destroy(tag->stack); + hsobject_unlink_and_destroy(g_tag_by_name, tag->object); + g_string_free(tag->name, true); + g_string_free(tag->display_name, true); + g_free(tag); +} + +void tag_destroy() { + int i; + for (i = 0; i < g_tags->len; i++) { + HSTag* tag = g_array_index(g_tags, HSTag*, i); + tag_free(tag); + } + g_array_free(g_tags, true); + hsobject_unlink_and_destroy(g_tag_object, g_tag_by_name); + hsobject_unlink_and_destroy(hsobject_root(), g_tag_object); +} + +int tag_get_count() { + return g_tags->len; +} + +HSTag* find_tag(const char* name) { + int i; + for (i = 0; i < g_tags->len; i++) { + if (!strcmp(g_array_index(g_tags, HSTag*, i)->name->str, name)) { + return g_array_index(g_tags, HSTag*, i); + } + } + return NULL; +} + +int tag_index_of(HSTag* tag) { + for (int i = 0; i < g_tags->len; i++) { + if (g_array_index(g_tags, HSTag*, i) == tag) { + return i; + } + } + return -1; +} + +HSTag* get_tag_by_index(int index) { + if (index < 0 || index >= g_tags->len) { + return NULL; + } + return g_array_index(g_tags, HSTag*, index); +} + +HSTag* get_tag_by_index_str(char* index_str, bool skip_visible_tags) { + int index = atoi(index_str); + // index must be treated relative, if it's first char is + or - + bool is_relative = array_find("+-", 2, sizeof(char), &index_str[0]) >= 0; + HSMonitor* monitor = get_current_monitor(); + if (is_relative) { + int current = tag_index_of(monitor->tag); + int delta = index; + index = delta + current; + // ensure index is valid + index = MOD(index, g_tags->len); + if (skip_visible_tags) { + HSTag* tag = g_array_index(g_tags, HSTag*, index); + for (int i = 0; find_monitor_with_tag(tag); i++) { + if (i >= g_tags->len) { + // if we tried each tag then there is no invisible tag + return NULL; + } + index += delta; + index = MOD(index, g_tags->len); + tag = g_array_index(g_tags, HSTag*, index); + } + } + } else { + // if it is absolute, then check index + if (index < 0 || index >= g_tags->len) { + HSDebug("invalid tag index %d\n", index); + return NULL; + } + } + return g_array_index(g_tags, HSTag*, index); +} + +HSTag* find_unused_tag() { + for (int i = 0; i < g_tags->len; i++) { + if (!find_monitor_with_tag(g_array_index(g_tags, HSTag*, i))) { + return g_array_index(g_tags, HSTag*, i); + } + } + return NULL; +} + +static GString* tag_attr_floating(HSAttribute* attr) { + HSTag* tag = container_of(attr->value.b, HSTag, floating); + HSMonitor* m = find_monitor_with_tag(tag); + if (m != NULL) { + monitor_apply_layout(m); + } + return NULL; +} + +static GString* tag_attr_name(HSAttribute* attr) { + HSTag* tag = container_of(attr->value.str, HSTag, display_name); + GString* error = g_string_new(""); + int status = tag_rename(tag, tag->display_name->str, error); + if (status == 0) { + g_string_free(error, true); + return NULL; + } else { + return error; + } +} + +static void sum_up_clientframes(HSFrame* frame, void* data) { + if (frame->type == TYPE_CLIENTS) { + (*(int*)data)++; + } +} + +static int tag_attr_frame_count(void* data) { + HSTag* tag = (HSTag*) data; + int i = 0; + frame_do_recursive_data(tag->frame, sum_up_clientframes, 0, &i); + return i; +} + +static void sum_up_clients(HSFrame* frame, void* data) { + if (frame->type == TYPE_CLIENTS) { + *(int*)data += frame->content.clients.count; + } +} + +static int tag_attr_client_count(void* data) { + HSTag* tag = (HSTag*) data; + int i = 0; + frame_do_recursive_data(tag->frame, sum_up_clients, 0, &i); + return i; +} + + +static int tag_attr_curframe_windex(void* data) { + HSTag* tag = (HSTag*) data; + HSFrame* frame = frame_current_selection_below(tag->frame); + return frame->content.clients.selection; +} + +static int tag_attr_curframe_wcount(void* data) { + HSTag* tag = (HSTag*) data; + HSFrame* frame = frame_current_selection_below(tag->frame); + return frame->content.clients.count; +} + +static int tag_attr_index(void* data) { + HSTag* tag = (HSTag*) data; + return tag_index_of(tag); +} + +static void tag_unlink_id_object(HSTag* tag, void* data) { + (void)data; + hsobject_unlink(g_tag_object, tag->object); +} + +static void tag_link_id_object(HSTag* tag, void* data) { + (void)data; + GString* index_str = g_string_new(""); + int index = tag_index_of(tag); + g_string_printf(index_str, "%d", index); + hsobject_link(g_tag_object, tag->object, index_str->str); + g_string_free(index_str, true); +} + +HSTag* add_tag(const char* name) { + HSTag* find_result = find_tag(name); + if (find_result) { + // nothing to do + return find_result; + } + HSTag* tag = g_new0(HSTag, 1); + tag->stack = stack_create(); + tag->frame = frame_create_empty(NULL, tag); + tag->name = g_string_new(name); + tag->display_name = g_string_new(name); + tag->floating = false; + g_array_append_val(g_tags, tag); + + // create object + tag->object = hsobject_create_and_link(g_tag_by_name, name); + tag->object->data = tag; + HSAttribute attributes[] = { + ATTRIBUTE_STRING("name", tag->display_name, tag_attr_name), + ATTRIBUTE_BOOL( "floating", tag->floating, tag_attr_floating), + ATTRIBUTE_CUSTOM_INT("index", tag_attr_index, ATTR_READ_ONLY), + ATTRIBUTE_CUSTOM_INT("frame_count", tag_attr_frame_count, ATTR_READ_ONLY), + ATTRIBUTE_CUSTOM_INT("client_count", tag_attr_client_count, ATTR_READ_ONLY), + ATTRIBUTE_CUSTOM_INT("curframe_windex",tag_attr_curframe_windex, ATTR_READ_ONLY), + ATTRIBUTE_CUSTOM_INT("curframe_wcount",tag_attr_curframe_wcount, ATTR_READ_ONLY), + ATTRIBUTE_LAST, + }; + hsobject_set_attributes(tag->object, attributes); + tag_link_id_object(tag, NULL); + + ewmh_update_desktops(); + ewmh_update_desktop_names(); + tag_set_flags_dirty(); + return tag; +} + +int tag_add_command(int argc, char** argv, GString* output) { + if (argc < 2) { + return HERBST_NEED_MORE_ARGS; + } + if (!strcmp("", argv[1])) { + g_string_append_printf(output, + "%s: An empty tag name is not permitted\n", argv[0]); + return HERBST_INVALID_ARGUMENT; + } + HSTag* tag = add_tag(argv[1]); + hook_emit_list("tag_added", tag->name->str, NULL); + return 0; +} + +static int tag_rename(HSTag* tag, char* name, GString* output) { + if (find_tag(name)) { + g_string_append_printf(output, + "Error: Tag \"%s\" already exists\n", name); + return HERBST_TAG_IN_USE; + } + hsobject_link_rename(g_tag_by_name, tag->name->str, name); + g_string_assign(tag->name, name); + g_string_assign(tag->display_name, name); + ewmh_update_desktop_names(); + hook_emit_list("tag_renamed", tag->name->str, NULL); + return 0; +} + +int tag_rename_command(int argc, char** argv, GString* output) { + if (argc < 3) { + return HERBST_NEED_MORE_ARGS; + } + if (!strcmp("", argv[2])) { + g_string_append_printf(output, + "%s: An empty tag name is not permitted\n", argv[0]); + return HERBST_INVALID_ARGUMENT; + } + HSTag* tag = find_tag(argv[1]); + if (!tag) { + g_string_append_printf(output, + "%s: Tag \"%s\" not found\n", argv[0], argv[1]); + return HERBST_INVALID_ARGUMENT; + } + return tag_rename(tag, argv[2], output); +} + +static void client_update_tag(void* key, void* client_void, void* data) { + (void) key; + (void) data; + HSClient* client = (HSClient*)client_void; + if (client) { + ewmh_window_update_tag(client->window, client->tag); + } +} + +int tag_remove_command(int argc, char** argv, GString* output) { + // usage: remove TAG [TARGET] + // it removes an TAG and moves all its wins to TARGET + // if no TARGET is given, current tag is used + if (argc < 2) { + return HERBST_NEED_MORE_ARGS; + } + HSTag* tag = find_tag(argv[1]); + HSTag* target = (argc >= 3) ? find_tag(argv[2]) : get_current_monitor()->tag; + if (!tag) { + g_string_append_printf(output, + "%s: Tag \"%s\" not found\n", argv[0], argv[1]); + return HERBST_INVALID_ARGUMENT; + } else if (!target) { + g_string_append_printf(output, + "%s: Tag \"%s\" not found\n", argv[0], argv[2]); + } else if (tag == target) { + g_string_append_printf(output, + "%s: Cannot merge tag \"%s\" into itself\n", argv[0], argv[1]); + return HERBST_INVALID_ARGUMENT; + } + if (find_monitor_with_tag(tag)) { + g_string_append_printf(output, + "%s: Cannot merge the currently viewed tag\n", argv[0]); + return HERBST_TAG_IN_USE; + } + // prevent dangling tag_previous pointers + all_monitors_replace_previous_tag(tag, target); + // save all these windows + HSClient** buf; + size_t count; + frame_destroy(tag->frame, &buf, &count); + tag->frame = NULL; + int i; + for (i = 0; i < count; i++) { + HSClient* client = buf[i]; + stack_remove_slice(client->tag->stack, client->slice); + client->tag = target; + stack_insert_slice(client->tag->stack, client->slice); + ewmh_window_update_tag(client->window, client->tag); + frame_insert_client(target->frame, buf[i]); + } + HSMonitor* monitor_target = find_monitor_with_tag(target); + if (monitor_target) { + // if target monitor is viewed, then show windows + monitor_apply_layout(monitor_target); + for (i = 0; i < count; i++) { + client_set_visible(buf[i], true); + } + } + g_free(buf); + tag_foreach(tag_unlink_id_object, NULL); + // remove tag + char* oldname = g_strdup(tag->name->str); + tag_free(tag); + for (i = 0; i < g_tags->len; i++) { + if (g_array_index(g_tags, HSTag*, i) == tag) { + g_array_remove_index(g_tags, i); + break; + } + } + ewmh_update_current_desktop(); + ewmh_update_desktops(); + ewmh_update_desktop_names(); + clientlist_foreach(client_update_tag, NULL); + tag_update_focus_objects(); + tag_set_flags_dirty(); + hook_emit_list("tag_removed", oldname, target->name->str, NULL); + g_free(oldname); + tag_foreach(tag_link_id_object, NULL); + return 0; +} + +int tag_set_floating_command(int argc, char** argv, GString* output) { + // usage: floating [[tag] on|off|toggle] + HSTag* tag = get_current_monitor()->tag; + const char* action = (argc > 1) ? argv[1] : "toggle"; + if (argc >= 3) { + // if a tag is specified + tag = find_tag(argv[1]); + action = argv[2]; + if (!tag) { + g_string_append_printf(output, + "%s: Tag \"%s\" not found\n", argv[0], argv[1]); + return HERBST_INVALID_ARGUMENT; + } + } + + bool new_value = string_to_bool(action, tag->floating); + + if (!strcmp(action, "status")) { + // just print status + g_string_append(output, tag->floating ? "on" : "off"); + } else { + // assign new value and rearrange if needed + tag->floating = new_value; + + HSMonitor* m = find_monitor_with_tag(tag); + HSDebug("setting tag:%s->floating to %s\n", tag->name->str, tag->floating ? "on" : "off"); + if (m != NULL) { + monitor_apply_layout(m); + } + } + return 0; +} + +static void client_update_tag_flags(void* key, void* client_void, void* data) { + (void) key; + (void) data; + HSClient* client = (HSClient*)client_void; + if (client) { + TAG_SET_FLAG(client->tag, TAG_FLAG_USED); + if (client->urgent) { + TAG_SET_FLAG(client->tag, TAG_FLAG_URGENT); + } + } +} + +void tag_force_update_flags() { + g_tag_flags_dirty = false; + // unset all tags + for (int i = 0; i < g_tags->len; i++) { + g_array_index(g_tags, HSTag*, i)->flags = 0; + } + // update flags + clientlist_foreach(client_update_tag_flags, NULL); +} + +void tag_update_flags() { + if (g_tag_flags_dirty) { + tag_force_update_flags(); + } +} + +void tag_set_flags_dirty() { + g_tag_flags_dirty = true; + hook_emit_list("tag_flags", NULL); +} + +void ensure_tags_are_available() { + if (g_tags->len > 0) { + // nothing to do + return; + } + add_tag("default"); +} + +HSTag* find_tag_with_toplevel_frame(HSFrame* frame) { + int i; + for (i = 0; i < g_tags->len; i++) { + HSTag* m = g_array_index(g_tags, HSTag*, i); + if (m->frame == frame) { + return m; + } + } + return NULL; +} + +int tag_move_window_command(int argc, char** argv, GString* output) { + if (argc < 2) { + return HERBST_NEED_MORE_ARGS; + } + HSTag* target = find_tag(argv[1]); + if (!target) { + g_string_append_printf(output, + "%s: Tag \"%s\" not found\n", argv[0], argv[1]); + return HERBST_INVALID_ARGUMENT; + } + tag_move_focused_client(target); + return 0; +} + +int tag_move_window_by_index_command(int argc, char** argv, GString* output) { + if (argc < 2) { + return HERBST_NEED_MORE_ARGS; + } + bool skip_visible = false; + if (argc >= 3 && !strcmp(argv[2], "--skip-visible")) { + skip_visible = true; + } + HSTag* tag = get_tag_by_index_str(argv[1], skip_visible); + if (!tag) { + g_string_append_printf(output, + "%s: Invalid index \"%s\"\n", argv[0], argv[1]); + return HERBST_INVALID_ARGUMENT; + } + tag_move_focused_client(tag); + return 0; +} + +void tag_move_focused_client(HSTag* target) { + HSClient* client = frame_focused_client(get_current_monitor()->tag->frame); + if (client == 0) { + // nothing to do + return; + } + tag_move_client(client, target); +} + +void tag_move_client(HSClient* client, HSTag* target) { + HSTag* tag_source = client->tag; + HSMonitor* monitor_source = find_monitor_with_tag(tag_source); + if (tag_source == target) { + // nothing to do + return; + } + HSMonitor* monitor_target = find_monitor_with_tag(target); + frame_remove_client(tag_source->frame, client); + // insert window into target + frame_insert_client(target->frame, client); + // enfoce it to be focused on the target tag + frame_focus_client(target->frame, client); + stack_remove_slice(client->tag->stack, client->slice); + client->tag = target; + stack_insert_slice(client->tag->stack, client->slice); + ewmh_window_update_tag(client->window, client->tag); + + // refresh things, hide things, layout it, and then show it if needed + if (monitor_source && !monitor_target) { + // window is moved to invisible tag + // so hide it + client_set_visible(client, false); + } + monitor_apply_layout(monitor_source); + monitor_apply_layout(monitor_target); + if (!monitor_source && monitor_target) { + client_set_visible(client, true); + } + if (monitor_target == get_current_monitor()) { + frame_focus_recursive(monitor_target->tag->frame); + } + else if (monitor_source == get_current_monitor()) { + frame_focus_recursive(monitor_source->tag->frame); + } + tag_set_flags_dirty(); +} + +void tag_update_focus_layer(HSTag* tag) { + HSClient* focus = frame_focused_client(tag->frame); + stack_clear_layer(tag->stack, LAYER_FOCUS); + if (focus) { + // enforce raise_on_focus_temporarily if there is at least one + // fullscreen window or if the tag is in tiling mode + if (!stack_is_layer_empty(tag->stack, LAYER_FULLSCREEN) + || *g_raise_on_focus_temporarily + || focus->tag->floating == false) { + stack_slice_add_layer(tag->stack, focus->slice, LAYER_FOCUS); + } + } + HSMonitor* monitor = find_monitor_with_tag(tag); + if (monitor) { + monitor_restack(monitor); + } +} + +void tag_foreach(void (*action)(HSTag*,void*), void* data) { + for (int i = 0; i < g_tags->len; i++) { + HSTag* tag = g_array_index(g_tags, HSTag*, i); + action(tag, data); + } +} + +static void tag_update_focus_layer_helper(HSTag* tag, void* data) { + (void) data; + tag_update_focus_layer(tag); +} +void tag_update_each_focus_layer() { + tag_foreach(tag_update_focus_layer_helper, NULL); +} + +void tag_update_focus_objects() { + hsobject_link(g_tag_object, get_current_monitor()->tag->object, "focus"); +} + diff -Nru herbstluftwm-0.6.2/src/tag.h herbstluftwm-0.7.0/src/tag.h --- herbstluftwm-0.6.2/src/tag.h 2013-12-14 17:08:13.000000000 +0000 +++ herbstluftwm-0.7.0/src/tag.h 2015-10-14 13:27:40.000000000 +0000 @@ -23,21 +23,18 @@ struct HSObject* object; } HSTag; -// globals -GArray* g_tags; // Array of HSTag* -bool g_tag_flags_dirty; - void tag_init(); void tag_destroy(); // for tags -HSTag* add_tag(char* name); -HSTag* find_tag(char* name); +HSTag* add_tag(const char* name); +HSTag* find_tag(const char* name); int tag_index_of(HSTag* tag); HSTag* find_unused_tag(); HSTag* find_tag_with_toplevel_frame(struct HSFrame* frame); HSTag* get_tag_by_index(int index); HSTag* get_tag_by_index_str(char* index_str, bool skip_visible_tags); +int tag_get_count(); int tag_add_command(int argc, char** argv, GString* output); int tag_rename_command(int argc, char** argv, GString* output); int tag_move_window_command(int argc, char** argv, GString* output); diff -Nru herbstluftwm-0.6.2/src/utils.c herbstluftwm-0.7.0/src/utils.c --- herbstluftwm-0.6.2/src/utils.c 2014-03-26 23:54:22.000000000 +0000 +++ herbstluftwm-0.7.0/src/utils.c 1970-01-01 00:00:00.000000000 +0000 @@ -1,540 +0,0 @@ -/** Copyright 2011-2013 Thorsten Wißmann. All rights reserved. - * - * This software is licensed under the "Simplified BSD License". - * See LICENSE for details */ - -#include "globals.h" -#include "utils.h" -// standard -#include -#include -#include -#include -// gui -#include -#include -#include -#include -#include "glib-backports.h" - -#include -#include - -#if defined(__MACH__) && ! defined(CLOCK_REALTIME) -#include -#include -#endif - -char* g_tree_style; /* the one from layout.c */ - -time_t get_monotonic_timestamp() { - struct timespec ts; -#if defined(__MACH__) && ! defined(CLOCK_REALTIME) // OS X does not have clock_gettime, use clock_get_time - clock_serv_t cclock; - mach_timespec_t mts; - host_get_clock_service(mach_host_self(), CALENDAR_CLOCK, &cclock); - clock_get_time(cclock, &mts); - mach_port_deallocate(mach_task_self(), cclock); - ts.tv_sec = mts.tv_sec; - ts.tv_nsec = mts.tv_nsec; -#else - clock_gettime(CLOCK_REALTIME, &ts); -#endif - return ts.tv_sec; -} - -/// print a printf-like message to stderr and exit -// from dwm.c -void die(const char *errstr, ...) { - va_list ap; - va_start(ap, errstr); - vfprintf(stderr, errstr, ap); - va_end(ap); - exit(EXIT_FAILURE); -} - -// get X11 color from color string -// from dwm.c -unsigned long getcolor(const char *colstr) { - HSColor ret_color; - if (!getcolor_error(colstr, &ret_color)) { - ret_color = 0; - } - return ret_color; -} - -bool getcolor_error(const char *colstr, HSColor* ret_color) { - Colormap cmap = DefaultColormap(g_display, g_screen); - XColor color; - if(!XAllocNamedColor(g_display, cmap, colstr, &color, &color)) { - g_warning("error, cannot allocate color '%s'\n", colstr); - return false; - } - *ret_color = color.pixel; - return true; -} - -// inspired by dwm's gettextprop() -GString* window_property_to_g_string(Display* dpy, Window window, Atom atom) { - GString* result = NULL; - char** list = NULL; - int n = 0; - XTextProperty prop; - - if (0 == XGetTextProperty(dpy, window, &prop, atom)) { - return NULL; - } - // convert text property to a gstring - if (prop.encoding == XA_STRING - || prop.encoding == XInternAtom(dpy, "UTF8_STRING", False)) { - result = g_string_new((char*)prop.value); - } else { - if (XmbTextPropertyToTextList(dpy, &prop, &list, &n) >= Success - && n > 0 && *list) - { - result = g_string_new(*list); - XFreeStringList(list); - } - } - XFree(prop.value); - return result; -} - -GString* window_class_to_g_string(Display* dpy, Window window) { - XClassHint hint; - if (0 == XGetClassHint(dpy, window, &hint)) { - return g_string_new(""); - } - GString* string = g_string_new(hint.res_class ? hint.res_class : ""); - if (hint.res_name) XFree(hint.res_name); - if (hint.res_class) XFree(hint.res_class); - return string; -} - -GString* window_instance_to_g_string(Display* dpy, Window window) { - XClassHint hint; - if (0 == XGetClassHint(dpy, window, &hint)) { - return g_string_new(""); - } - GString* string = g_string_new(hint.res_name ? hint.res_name : ""); - if (hint.res_name) XFree(hint.res_name); - if (hint.res_class) XFree(hint.res_class); - return string; -} - - -bool is_herbstluft_window(Display* dpy, Window window) { - GString* string = window_class_to_g_string(dpy, window); - bool result; - result = !strcmp(string->str, HERBST_FRAME_CLASS); - g_string_free(string, true); - return result; -} - -bool is_window_mapable(Display* dpy, Window window) { - XWindowAttributes wa; - XGetWindowAttributes(dpy, window, &wa); - return (wa.map_state == IsUnmapped); -} -bool is_window_mapped(Display* dpy, Window window) { - XWindowAttributes wa; - XGetWindowAttributes(dpy, window, &wa); - return (wa.map_state == IsViewable); -} - -bool window_has_property(Display* dpy, Window window, char* prop_name) { - // find the properties this window has - int num_properties_ret; - Atom* properties= XListProperties(g_display, window, &num_properties_ret); - - bool atom_found = false; - char* name; - for(int i = 0; i < num_properties_ret; i++) { - name = XGetAtomName(g_display, properties[i]); - if(!strcmp(prop_name, name)) { - atom_found = true; - break; - } - XFree(name); - } - XFree(properties); - - return atom_found; -} - -// duplicates an argument-vector -char** argv_duplicate(int argc, char** argv) { - if (argc <= 0) return NULL; - char** new_argv = malloc(sizeof(char*) * argc); - if (!new_argv) { - die("cannot malloc - there is no memory available\n"); - } - int i; - for (i = 0; i < argc; i++) { - new_argv[i] = g_strdup(argv[i]); - } - return new_argv; -} - -// frees all entries in argument-vector and then the vector itself -void argv_free(int argc, char** argv) { - if (argc <= 0) return; - int i; - for (i = 0; i < argc; i++) { - free(argv[i]); - } - free(argv); -} - - -Rectangle parse_rectangle(char* string) { - Rectangle rect; - int x,y; - unsigned int w, h; - int flags = XParseGeometry(string, &x, &y, &w, &h); - rect.x = (XValue & flags) ? (short int)x : 0; - rect.y = (YValue & flags) ? (short int)y : 0; - rect.width = (WidthValue & flags) ? (unsigned short int)w : 0; - rect.height = (HeightValue & flags) ? (unsigned short int)h : 0; - return rect; -} - -char* strlasttoken(char* str, char* delim) { - char* next = str; - while ((next = strpbrk(str, delim))) { - next++;; - str = next; - } - return str; -} - - -bool string_to_bool(const char* string, bool oldvalue) { - return string_to_bool_error(string, oldvalue, NULL); -} - -bool string_to_bool_error(const char* string, bool oldvalue, bool* error) { - bool val = oldvalue; - if (error) { - error = false; - } - if (!strcmp(string, "on")) { - val = true; - } else if (!strcmp(string, "off")) { - val = false; - } else if (!strcmp(string, "true")) { - val = true; - } else if (!strcmp(string, "false")) { - val = false; - } else if (!strcmp(string, "toggle")) { - val = ! oldvalue; - } else if (error) { - *error = true; - } - return val; -} - -int window_pid(Display* dpy, Window window) { - Atom type; - int format; - unsigned long items, remain; - int* buf; - int status = XGetWindowProperty(dpy, window, - ATOM("_NET_WM_PID"), 0, 1, False, - XA_CARDINAL, &type, &format, - &items, &remain, (unsigned char**)&buf); - if (items == 1 && format == 32 && remain == 0 - && type == XA_CARDINAL && status == Success) { - int value = *buf; - XFree(buf); - return value; - } else { - return -1; - } -} - -void g_queue_remove_element(GQueue* queue, GList* elem) { - if (queue->length <= 0) { - return; - } - bool was_tail = (queue->tail == elem); - GList* before_elem = elem->prev; - - queue->head = g_list_delete_link(queue->head, elem); - queue->length--; - - // reset pointers - if (was_tail) { - queue->tail = before_elem; - } -} - -int array_find(void* buf, size_t elems, size_t size, void* needle) { - for (int i = 0; i < elems; i++) { - if (0 == memcmp((char*)buf + (size * i), needle, size)) { - return i; - } - } - return -1; -} - -void array_reverse(void* void_buf, size_t elems, size_t size) { - char* buf = (char*)void_buf; - char* tmp = malloc(size); - for (int i = 0, j = elems - 1; i < j; i++, j--) { - memcpy(tmp, buf + size * i, size); - memcpy(buf + size * i, buf + size * j, size); - memcpy(buf + size * j, tmp, size); - } - free(tmp); -} - - -/** - * \brief tells if the string needle is identical to the string *pmember - */ -bool memberequals_string(void* pmember, void* needle) { - return !strcmp(*(char**)pmember, (char*)needle); -} - -/** - * \brief tells if the ints pointed by pmember and needle are identical - */ -bool memberequals_int(void* pmember, void* needle) { - return (*(int*)pmember) == (*(int*)needle); -} - -/** - * \brief finds an element in a table (i.e. array of structs) - * - * it consecutively searches from the beginning of the table for a - * table element whose member is equal to needle. It passes a pointer - * to the member and needle to the equals-function consecutively until - * equals returns something != 0. - * - * \param start address of the first element in the table - * \param elem_size offset between two elements - * \param count count of elements in that table - * \param member_offset offset of the member that is used to compare - * \param equals function that tells if the two values are equal - * \param needle second parameter to equals - * \return the found element or NULL - */ -void* table_find(void* start, size_t elem_size, size_t count, - size_t member_offset, MemberEquals equals, void* needle) -{ - char* cstart = start; - while (count > 0) { - /* check the element */ - if (equals(cstart + member_offset, needle)) { - return cstart; - } - /* go to the next element */ - cstart += elem_size; - count--; - } - return NULL; -} - -/** - * \brief emulates a double window border through the border pixmap mechanism - */ -void set_window_double_border(Display *dpy, Window win, int ibw, - unsigned long inner_color, - unsigned long outer_color) -{ - XWindowAttributes wa; - - if (!XGetWindowAttributes(dpy, win, &wa)) - return; - - int bw = wa.border_width; - - if (bw < 2 || ibw >= bw || ibw < 1) - return; - - int width = wa.width; - int height = wa.height; - - unsigned int depth = wa.depth; - - int full_width = width + 2 * bw; - int full_height = height + 2 * bw; - - // the inner border is represented through the following pattern: - // - // ██ ██ - // ██ ██ - // ██ ██ - // ██ ██ - // ██ ██ - // ██ ██ - // ██ ██ - // ██ ██ - // ██ ██ - // ██ ██ - // ██ ██ - // ██████████████████████████ ██ - // - // ██████████████████████████ ██ - - XRectangle rectangles[] = - { - { width, 0, ibw, height + ibw }, - { full_width - ibw, 0, ibw, height + ibw }, - { 0, height, width + ibw, ibw }, - { 0, full_height - ibw, width + ibw, ibw }, - { full_width - ibw, full_height - ibw, ibw, ibw } - }; - - Pixmap pix = XCreatePixmap(dpy, win, full_width, full_height, depth); - GC gc = XCreateGC(dpy, pix, 0, NULL); - - /* outer border */ - XSetForeground(dpy, gc, outer_color); - XFillRectangle(dpy, pix, gc, 0, 0, full_width, full_height); - - /* inner border */ - XSetForeground(dpy, gc, inner_color); - XFillRectangles(dpy, pix, gc, rectangles, LENGTH(rectangles)); - - XSetWindowBorderPixmap(dpy, win, pix); - XFreeGC(dpy, gc); - XFreePixmap(dpy, pix); -} - -static void subtree_print_to(HSTreeInterface* intface, char* indent, - char* rootprefix, GString* output) { - HSTree root = intface->data; - size_t child_count = intface->child_count(root); - if (child_count == 0) { - g_string_append(output, rootprefix); - g_string_append_unichar(output, UTF8_STRING_AT(g_tree_style, 6)); - g_string_append_unichar(output, UTF8_STRING_AT(g_tree_style, 5)); - g_string_append_c(output, ' '); - // append caption - intface->append_caption(root, output); - g_string_append(output, "\n"); - } else { - g_string_append_printf(output, "%s", rootprefix); - g_string_append_unichar(output, UTF8_STRING_AT(g_tree_style, 6)); - g_string_append_unichar(output, UTF8_STRING_AT(g_tree_style, 7)); - // append caption - g_string_append_c(output, ' '); - intface->append_caption(root, output); - g_string_append_c(output, '\n'); - // append children - GString* child_indent = g_string_new(""); - GString* child_prefix = g_string_new(""); - for (size_t i = 0; i < child_count; i++) { - bool last = (i == child_count - 1); - g_string_printf(child_indent, "%s ", indent); - g_string_append_unichar(child_indent, - UTF8_STRING_AT(g_tree_style, last ? 2 : 1)); - g_string_printf(child_prefix, "%s ", indent); - g_string_append_unichar(child_prefix, - UTF8_STRING_AT(g_tree_style, last ? 4 : 3)); - HSTreeInterface child = intface->nth_child(root, i); - subtree_print_to(&child, child_indent->str, - child_prefix->str, output); - if (child.destructor) { - child.destructor(child.data); - } - } - g_string_free(child_indent, true); - g_string_free(child_prefix, true); - - } -} - -void tree_print_to(HSTreeInterface* intface, GString* output) { - GString* root_indicator = g_string_new(""); - g_string_append_unichar(root_indicator, UTF8_STRING_AT(g_tree_style, 0)); - subtree_print_to(intface, " ", root_indicator->str, output); - g_string_free(root_indicator, true); -} - -int min(int a, int b) { - if (a < b) - return a; - return b; -} - -char* posix_sh_escape(char* source) { - size_t count = 0; - int i; - for (i = 0; source[i]; i++) { - int j = LENGTH(ESCAPE_CHARACTERS) - 1; // = strlen(ESCAPE_CHARACTERS) - slow_assert(j == strlen(ESCAPE_CHARACTERS)); - while (j--) { - slow_assert(0 <= j && j < strlen(ESCAPE_CHARACTERS)); - if (source[i] == ESCAPE_CHARACTERS[j]) { - count++; - break; - } - } - } - size_t source_len = i; - // special chars: - if (source[0] == '~') { - count++; - } - // if there is nothing to escape - if (count == 0) return NULL; - char* target = malloc(sizeof(char) * (count + source_len + 1)); - if (!target) { - die("cannot malloc - there is no memory available\n"); - } - - // do the actual escaping - // special chars: - int s = 0; // position in the source - int t = 0; // position in the target - slow_assert(s < strlen(source)); - slow_assert(t < (count + source_len)); - if (source[0] == '~') { - target[t++] = '\\'; - target[t++] = source[s++]; - } - slow_assert(s < strlen(source)); - slow_assert(t < (count + source_len)); - while (source[s]) { - // check if we need to escape the next char - int j = LENGTH(ESCAPE_CHARACTERS) - 1; // = strlen(ESCAPE_CHARACTERS) - slow_assert(s < strlen(source)); - slow_assert(t < (count + source_len)); - while (j--) { - if (source[s] == ESCAPE_CHARACTERS[j]) { - // if source[s] needs to be escape, then put an backslash first - target[t++] = '\\'; - break; - } - } - slow_assert(s < strlen(source)); - slow_assert(t < (count + source_len)); - // put the actual character - target[t++] = source[s++]; - } - slow_assert(s == strlen(source)); - slow_assert(t == (count + source_len)); - // terminate the string - target[t] = '\0'; - return target; -} - -void posix_sh_compress_inplace(char* str) { - int offset = 0; - for (int i = 0; true ; i++) { - if (str[i] == '\\' && str[i + 1] ) { - str[i + offset] = str[i + 1]; - i++; - offset --; - } else { - str[i + offset] = str[i]; - } - if (!str[i]) { - break; - } - } -} - diff -Nru herbstluftwm-0.6.2/src/utils.cpp herbstluftwm-0.7.0/src/utils.cpp --- herbstluftwm-0.6.2/src/utils.cpp 1970-01-01 00:00:00.000000000 +0000 +++ herbstluftwm-0.7.0/src/utils.cpp 2015-10-14 13:27:40.000000000 +0000 @@ -0,0 +1,542 @@ +/** Copyright 2011-2013 Thorsten Wißmann. All rights reserved. + * + * This software is licensed under the "Simplified BSD License". + * See LICENSE for details */ + +#include "globals.h" +#include "utils.h" +// standard +#include +#include +#include +#include +// gui +#include +#include +#include +#include +#include "glib-backports.h" + +#include +#include + +#if defined(__MACH__) && ! defined(CLOCK_REALTIME) +#include +#include +#endif + +// globals +extern char* g_tree_style; /* the one from layout.c */ + +time_t get_monotonic_timestamp() { + struct timespec ts; +#if defined(__MACH__) && ! defined(CLOCK_REALTIME) // OS X does not have clock_gettime, use clock_get_time + clock_serv_t cclock; + mach_timespec_t mts; + host_get_clock_service(mach_host_self(), CALENDAR_CLOCK, &cclock); + clock_get_time(cclock, &mts); + mach_port_deallocate(mach_task_self(), cclock); + ts.tv_sec = mts.tv_sec; + ts.tv_nsec = mts.tv_nsec; +#else + clock_gettime(CLOCK_REALTIME, &ts); +#endif + return ts.tv_sec; +} + +/// print a printf-like message to stderr and exit +// from dwm.c +void die(const char *errstr, ...) { + va_list ap; + va_start(ap, errstr); + vfprintf(stderr, errstr, ap); + va_end(ap); + exit(EXIT_FAILURE); +} + +// get X11 color from color string +// from dwm.c +unsigned long getcolor(const char *colstr) { + HSColor ret_color; + if (!getcolor_error(colstr, &ret_color)) { + ret_color = 0; + } + return ret_color; +} + +bool getcolor_error(const char *colstr, HSColor* ret_color) { + Colormap cmap = DefaultColormap(g_display, g_screen); + XColor color; + if(!XAllocNamedColor(g_display, cmap, colstr, &color, &color)) { + g_warning("error, cannot allocate color '%s'\n", colstr); + return false; + } + *ret_color = color.pixel; + return true; +} + +// inspired by dwm's gettextprop() +GString* window_property_to_g_string(Display* dpy, Window window, Atom atom) { + GString* result = NULL; + char** list = NULL; + int n = 0; + XTextProperty prop; + + if (0 == XGetTextProperty(dpy, window, &prop, atom)) { + return NULL; + } + // convert text property to a gstring + if (prop.encoding == XA_STRING + || prop.encoding == XInternAtom(dpy, "UTF8_STRING", False)) { + result = g_string_new((char*)prop.value); + } else { + if (XmbTextPropertyToTextList(dpy, &prop, &list, &n) >= Success + && n > 0 && *list) + { + result = g_string_new(*list); + XFreeStringList(list); + } + } + XFree(prop.value); + return result; +} + +GString* window_class_to_g_string(Display* dpy, Window window) { + XClassHint hint; + if (0 == XGetClassHint(dpy, window, &hint)) { + return g_string_new(""); + } + GString* string = g_string_new(hint.res_class ? hint.res_class : ""); + if (hint.res_name) XFree(hint.res_name); + if (hint.res_class) XFree(hint.res_class); + return string; +} + +GString* window_instance_to_g_string(Display* dpy, Window window) { + XClassHint hint; + if (0 == XGetClassHint(dpy, window, &hint)) { + return g_string_new(""); + } + GString* string = g_string_new(hint.res_name ? hint.res_name : ""); + if (hint.res_name) XFree(hint.res_name); + if (hint.res_class) XFree(hint.res_class); + return string; +} + + +bool is_herbstluft_window(Display* dpy, Window window) { + GString* string = window_class_to_g_string(dpy, window); + bool result; + result = !strcmp(string->str, HERBST_FRAME_CLASS); + g_string_free(string, true); + return result; +} + +bool is_window_mapable(Display* dpy, Window window) { + XWindowAttributes wa; + XGetWindowAttributes(dpy, window, &wa); + return (wa.map_state == IsUnmapped); +} +bool is_window_mapped(Display* dpy, Window window) { + XWindowAttributes wa; + XGetWindowAttributes(dpy, window, &wa); + return (wa.map_state == IsViewable); +} + +bool window_has_property(Display* dpy, Window window, char* prop_name) { + // find the properties this window has + int num_properties_ret; + Atom* properties= XListProperties(g_display, window, &num_properties_ret); + + bool atom_found = false; + char* name; + for(int i = 0; i < num_properties_ret; i++) { + name = XGetAtomName(g_display, properties[i]); + if(!strcmp(prop_name, name)) { + atom_found = true; + break; + } + XFree(name); + } + XFree(properties); + + return atom_found; +} + +// duplicates an argument-vector +char** argv_duplicate(int argc, char** argv) { + if (argc <= 0) return NULL; + char** new_argv = new char*[argc]; + if (!new_argv) { + die("cannot malloc - there is no memory available\n"); + } + int i; + for (i = 0; i < argc; i++) { + new_argv[i] = g_strdup(argv[i]); + } + return new_argv; +} + +// frees all entries in argument-vector and then the vector itself +void argv_free(int argc, char** argv) { + if (argc <= 0) return; + int i; + for (i = 0; i < argc; i++) { + free(argv[i]); + } + delete[] argv; +} + + +Rectangle parse_rectangle(char* string) { + Rectangle rect; + int x,y; + unsigned int w, h; + int flags = XParseGeometry(string, &x, &y, &w, &h); + rect.x = (XValue & flags) ? (short int)x : 0; + rect.y = (YValue & flags) ? (short int)y : 0; + rect.width = (WidthValue & flags) ? (unsigned short int)w : 0; + rect.height = (HeightValue & flags) ? (unsigned short int)h : 0; + return rect; +} + +const char* strlasttoken(const char* str, const char* delim) { + const char* next = str; + while ((next = strpbrk(str, delim))) { + next++;; + str = next; + } + return str; +} + + +bool string_to_bool(const char* string, bool oldvalue) { + return string_to_bool_error(string, oldvalue, NULL); +} + +bool string_to_bool_error(const char* string, bool oldvalue, bool* error) { + bool val = oldvalue; + if (error) { + *error = false; + } + if (!strcmp(string, "on")) { + val = true; + } else if (!strcmp(string, "off")) { + val = false; + } else if (!strcmp(string, "true")) { + val = true; + } else if (!strcmp(string, "false")) { + val = false; + } else if (!strcmp(string, "toggle")) { + val = ! oldvalue; + } else if (error) { + *error = true; + } + return val; +} + +int window_pid(Display* dpy, Window window) { + Atom type; + int format; + unsigned long items, remain; + int* buf; + int status = XGetWindowProperty(dpy, window, + ATOM("_NET_WM_PID"), 0, 1, False, + XA_CARDINAL, &type, &format, + &items, &remain, (unsigned char**)&buf); + if (items == 1 && format == 32 && remain == 0 + && type == XA_CARDINAL && status == Success) { + int value = *buf; + XFree(buf); + return value; + } else { + return -1; + } +} + +void g_queue_remove_element(GQueue* queue, GList* elem) { + if (queue->length <= 0) { + return; + } + bool was_tail = (queue->tail == elem); + GList* before_elem = elem->prev; + + queue->head = g_list_delete_link(queue->head, elem); + queue->length--; + + // reset pointers + if (was_tail) { + queue->tail = before_elem; + } +} + +int array_find(const void* buf, size_t elems, size_t size, const void* needle) { + for (int i = 0; i < elems; i++) { + if (0 == memcmp((const char*)buf + (size * i), needle, size)) { + return i; + } + } + return -1; +} + +void array_reverse(void* void_buf, size_t elems, size_t size) { + char* buf = (char*)void_buf; + char* tmp = new char[size]; + for (int i = 0, j = elems - 1; i < j; i++, j--) { + memcpy(tmp, buf + size * i, size); + memcpy(buf + size * i, buf + size * j, size); + memcpy(buf + size * j, tmp, size); + } + delete[] tmp; +} + + +/** + * \brief tells if the string needle is identical to the string *pmember + */ +bool memberequals_string(void* pmember, const void* needle) { + return !strcmp(*(char**)pmember, (const char*)needle); +} + +/** + * \brief tells if the ints pointed by pmember and needle are identical + */ +bool memberequals_int(void* pmember, const void* needle) { + return (*(int*)pmember) == (*(const int*)needle); +} + +/** + * \brief finds an element in a table (i.e. array of structs) + * + * it consecutively searches from the beginning of the table for a + * table element whose member is equal to needle. It passes a pointer + * to the member and needle to the equals-function consecutively until + * equals returns something != 0. + * + * \param start address of the first element in the table + * \param elem_size offset between two elements + * \param count count of elements in that table + * \param member_offset offset of the member that is used to compare + * \param equals function that tells if the two values are equal + * \param needle second parameter to equals + * \return the found element or NULL + */ +void* table_find(void* start, size_t elem_size, size_t count, + size_t member_offset, MemberEquals equals, const void* needle) +{ + char* cstart = (char*) start; + while (count > 0) { + /* check the element */ + if (equals(cstart + member_offset, needle)) { + return cstart; + } + /* go to the next element */ + cstart += elem_size; + count--; + } + return NULL; +} + +/** + * \brief emulates a double window border through the border pixmap mechanism + */ +void set_window_double_border(Display *dpy, Window win, int ibw, + unsigned long inner_color, + unsigned long outer_color) +{ + XWindowAttributes wa; + + if (!XGetWindowAttributes(dpy, win, &wa)) + return; + + int bw = wa.border_width; + + if (bw < 2 || ibw >= bw || ibw < 1) + return; + + int width = wa.width; + int height = wa.height; + + unsigned int depth = wa.depth; + + int full_width = width + 2 * bw; + int full_height = height + 2 * bw; + + // the inner border is represented through the following pattern: + // + // ██ ██ + // ██ ██ + // ██ ██ + // ██ ██ + // ██ ██ + // ██ ██ + // ██ ██ + // ██ ██ + // ██ ██ + // ██ ██ + // ██ ██ + // ██████████████████████████ ██ + // + // ██████████████████████████ ██ + + XRectangle rectangles[] = + { + { width, 0, ibw, height + ibw }, + { full_width - ibw, 0, ibw, height + ibw }, + { 0, height, width + ibw, ibw }, + { 0, full_height - ibw, width + ibw, ibw }, + { full_width - ibw, full_height - ibw, ibw, ibw } + }; + + Pixmap pix = XCreatePixmap(dpy, win, full_width, full_height, depth); + GC gc = XCreateGC(dpy, pix, 0, NULL); + + /* outer border */ + XSetForeground(dpy, gc, outer_color); + XFillRectangle(dpy, pix, gc, 0, 0, full_width, full_height); + + /* inner border */ + XSetForeground(dpy, gc, inner_color); + XFillRectangles(dpy, pix, gc, rectangles, LENGTH(rectangles)); + + XSetWindowBorderPixmap(dpy, win, pix); + XFreeGC(dpy, gc); + XFreePixmap(dpy, pix); +} + +static void subtree_print_to(HSTreeInterface* intface, const char* indent, + char* rootprefix, GString* output) { + HSTree root = intface->data; + size_t child_count = intface->child_count(root); + if (child_count == 0) { + g_string_append(output, rootprefix); + g_string_append_unichar(output, UTF8_STRING_AT(g_tree_style, 6)); + g_string_append_unichar(output, UTF8_STRING_AT(g_tree_style, 5)); + g_string_append_c(output, ' '); + // append caption + intface->append_caption(root, output); + g_string_append(output, "\n"); + } else { + g_string_append_printf(output, "%s", rootprefix); + g_string_append_unichar(output, UTF8_STRING_AT(g_tree_style, 6)); + g_string_append_unichar(output, UTF8_STRING_AT(g_tree_style, 7)); + // append caption + g_string_append_c(output, ' '); + intface->append_caption(root, output); + g_string_append_c(output, '\n'); + // append children + GString* child_indent = g_string_new(""); + GString* child_prefix = g_string_new(""); + for (size_t i = 0; i < child_count; i++) { + bool last = (i == child_count - 1); + g_string_printf(child_indent, "%s ", indent); + g_string_append_unichar(child_indent, + UTF8_STRING_AT(g_tree_style, last ? 2 : 1)); + g_string_printf(child_prefix, "%s ", indent); + g_string_append_unichar(child_prefix, + UTF8_STRING_AT(g_tree_style, last ? 4 : 3)); + HSTreeInterface child = intface->nth_child(root, i); + subtree_print_to(&child, child_indent->str, + child_prefix->str, output); + if (child.destructor) { + child.destructor(child.data); + } + } + g_string_free(child_indent, true); + g_string_free(child_prefix, true); + + } +} + +void tree_print_to(HSTreeInterface* intface, GString* output) { + GString* root_indicator = g_string_new(""); + g_string_append_unichar(root_indicator, UTF8_STRING_AT(g_tree_style, 0)); + subtree_print_to(intface, " ", root_indicator->str, output); + g_string_free(root_indicator, true); +} + +int min(int a, int b) { + if (a < b) + return a; + return b; +} + +char* posix_sh_escape(const char* source) { + size_t count = 0; + int i; + for (i = 0; source[i]; i++) { + int j = LENGTH(ESCAPE_CHARACTERS) - 1; // = strlen(ESCAPE_CHARACTERS) + slow_assert(j == strlen(ESCAPE_CHARACTERS)); + while (j--) { + slow_assert(0 <= j && j < strlen(ESCAPE_CHARACTERS)); + if (source[i] == ESCAPE_CHARACTERS[j]) { + count++; + break; + } + } + } + size_t source_len = i; + // special chars: + if (source[0] == '~') { + count++; + } + // if there is nothing to escape + if (count == 0) return NULL; + // TODO migrate to new + char* target = (char*)malloc(sizeof(char) * (count + source_len + 1)); + if (!target) { + die("cannot malloc - there is no memory available\n"); + } + + // do the actual escaping + // special chars: + int s = 0; // position in the source + int t = 0; // position in the target + slow_assert(s < strlen(source)); + slow_assert(t < (count + source_len)); + if (source[0] == '~') { + target[t++] = '\\'; + target[t++] = source[s++]; + } + slow_assert(s < strlen(source)); + slow_assert(t < (count + source_len)); + while (source[s]) { + // check if we need to escape the next char + int j = LENGTH(ESCAPE_CHARACTERS) - 1; // = strlen(ESCAPE_CHARACTERS) + slow_assert(s < strlen(source)); + slow_assert(t < (count + source_len)); + while (j--) { + if (source[s] == ESCAPE_CHARACTERS[j]) { + // if source[s] needs to be escape, then put an backslash first + target[t++] = '\\'; + break; + } + } + slow_assert(s < strlen(source)); + slow_assert(t < (count + source_len)); + // put the actual character + target[t++] = source[s++]; + } + slow_assert(s == strlen(source)); + slow_assert(t == (count + source_len)); + // terminate the string + target[t] = '\0'; + return target; +} + +void posix_sh_compress_inplace(char* str) { + int offset = 0; + for (int i = 0; true ; i++) { + if (str[i] == '\\' && str[i + 1] ) { + str[i + offset] = str[i + 1]; + i++; + offset --; + } else { + str[i + offset] = str[i]; + } + if (!str[i]) { + break; + } + } +} + diff -Nru herbstluftwm-0.6.2/src/utils.h herbstluftwm-0.7.0/src/utils.h --- herbstluftwm-0.6.2/src/utils.h 2014-03-26 23:54:22.000000000 +0000 +++ herbstluftwm-0.7.0/src/utils.h 2015-10-14 13:27:40.000000000 +0000 @@ -12,6 +12,7 @@ #include #include #include "x11-types.h" +#include #define LENGTH(X) (sizeof(X)/sizeof(*X)) #define SHIFT(ARGC, ARGV) (--(ARGC) && ++(ARGV)) @@ -65,7 +66,7 @@ bool string_to_bool_error(const char* string, bool oldvalue, bool* error); bool string_to_bool(const char* string, bool oldvalue); -char* strlasttoken(char* str, char* delim); +const char* strlasttoken(const char* str, const char* delim); time_t get_monotonic_timestamp(); @@ -79,18 +80,28 @@ void g_queue_remove_element(GQueue* queue, GList* elem); // find an element in an array buf with elems elements of size size. -int array_find(void* buf, size_t elems, size_t size, void* needle); +int array_find(const void* buf, size_t elems, size_t size, const void* needle); void array_reverse(void* void_buf, size_t elems, size_t size); +template struct ArrayInitializer { + ArrayInitializer(std::initializer_list > il) { + for (auto i = il.begin(); i != il.end(); i++) { + a[i->first] = i->second; + } + } + + std::array a; +}; + int min(int a, int b); // utils for tables -typedef bool (*MemberEquals)(void* pmember, void* needle); -bool memberequals_string(void* pmember, void* needle); -bool memberequals_int(void* pmember, void* needle); +typedef bool (*MemberEquals)(void* pmember, const void* needle); +bool memberequals_string(void* pmember, const void* needle); +bool memberequals_int(void* pmember, const void* needle); void* table_find(void* start, size_t elem_size, size_t count, - size_t member_offset, MemberEquals equals, void* needle); + size_t member_offset, MemberEquals equals, const void* needle); void set_window_double_border(Display *dpy, Window win, int ibw, unsigned long inner_color, unsigned long outer_color); @@ -123,7 +134,7 @@ // returns an posix sh escaped string or NULL if there is nothing to escape // if a new string is returned, then the caller has to free it -char* posix_sh_escape(char* source); +char* posix_sh_escape(const char* source); // does the reverse action to posix_sh_escape by modifing the string void posix_sh_compress_inplace(char* str); diff -Nru herbstluftwm-0.6.2/src/x11-types.h herbstluftwm-0.7.0/src/x11-types.h --- herbstluftwm-0.6.2/src/x11-types.h 2014-03-26 23:54:22.000000000 +0000 +++ herbstluftwm-0.7.0/src/x11-types.h 2015-10-14 13:27:40.000000000 +0000 @@ -4,12 +4,15 @@ typedef unsigned long HSColor; -typedef struct { +struct Rectangle { int x; int y; int width; int height; -} Rectangle; + Rectangle() {}; + Rectangle(int x, int y, int width, int height) + : x(x), y(y), width(width), height(height) {}; +}; typedef struct { int x; diff -Nru herbstluftwm-0.6.2/src/x11-utils.c herbstluftwm-0.7.0/src/x11-utils.c --- herbstluftwm-0.6.2/src/x11-utils.c 2014-03-26 23:54:22.000000000 +0000 +++ herbstluftwm-0.7.0/src/x11-utils.c 1970-01-01 00:00:00.000000000 +0000 @@ -1,80 +0,0 @@ - - -#include "x11-utils.h" -#include "globals.h" -#include - -#include - -/** - * \brief cut a rect out of the window, s.t. the window has geometry rect and - * a frame of width framewidth remains - */ -void window_cut_rect_hole(Window win, int width, int height, int framewidth) { - // inspired by the xhole.c example - // http://www.answers.com/topic/xhole-c - Display* d = g_display; - GC gp; - int bw = 100; // add a large border, just to be sure the border is visible - int holewidth = width - 2*framewidth; - int holeheight = height - 2*framewidth; - width += 2*bw; - height += 2*bw; - - /* create the pixmap that specifies the shape */ - Pixmap p = XCreatePixmap(d, win, width, height, 1); - gp = XCreateGC(d, p, 0, NULL); - XSetForeground(d, gp, WhitePixel(d, g_screen)); - XFillRectangle(d, p, gp, 0, 0, width, height); - XSetForeground(d, gp, BlackPixel(d, g_screen)); - //int radius = 50; - //XFillArc(d, p, gp, - // width/2 - radius, height/2 - radius, radius, radius, - // 0, 360*64); - XFillRectangle(d, p, gp, bw + framewidth, bw + framewidth, - holewidth, holeheight); - - /* set the pixmap as the new window mask; - the pixmap is slightly larger than the window to allow for the window - border and title bar (as added by the window manager) to be visible */ - XShapeCombineMask(d, win, ShapeBounding, -bw, -bw, p, ShapeSet); - XFreeGC(d, gp); - XFreePixmap(d, p); -} - -void window_make_intransparent(Window win, int width, int height) { - // inspired by the xhole.c example - // http://www.answers.com/topic/xhole-c - Display* d = g_display; - GC gp; - int bw = 100; // add a large border, just to be sure the border is visible - width += 2*bw; - height += 2*bw; - - /* create the pixmap that specifies the shape */ - Pixmap p = XCreatePixmap(d, win, width, height, 1); - gp = XCreateGC(d, p, 0, NULL); - XSetForeground(d, gp, WhitePixel(d, g_screen)); - XFillRectangle(d, p, gp, 0, 0, width, height); - /* set the pixmap as the new window mask; - the pixmap is slightly larger than the window to allow for the window - border and title bar (as added by the window manager) to be visible */ - XShapeCombineMask(d, win, ShapeBounding, -bw, -bw, p, ShapeSet); - XFreeGC(d, gp); - XFreePixmap(d, p); -} - - -Point2D get_cursor_position() { - Point2D point; - Window win, child; - int wx, wy; - unsigned int mask; - if (True != XQueryPointer(g_display, g_root, &win, &child, - &point.x, &point.y, &wx,&wy, &mask)) { - HSWarning("Can not query cursor coordinates via XQueryPointer\n"); - point.x = 0; - point.y = 0; - } - return point; -} diff -Nru herbstluftwm-0.6.2/src/x11-utils.cpp herbstluftwm-0.7.0/src/x11-utils.cpp --- herbstluftwm-0.6.2/src/x11-utils.cpp 1970-01-01 00:00:00.000000000 +0000 +++ herbstluftwm-0.7.0/src/x11-utils.cpp 2015-10-14 13:27:40.000000000 +0000 @@ -0,0 +1,80 @@ + + +#include "x11-utils.h" +#include "globals.h" +#include + +#include + +/** + * \brief cut a rect out of the window, s.t. the window has geometry rect and + * a frame of width framewidth remains + */ +void window_cut_rect_hole(Window win, int width, int height, int framewidth) { + // inspired by the xhole.c example + // http://www.answers.com/topic/xhole-c + Display* d = g_display; + GC gp; + int bw = 100; // add a large border, just to be sure the border is visible + int holewidth = width - 2*framewidth; + int holeheight = height - 2*framewidth; + width += 2*bw; + height += 2*bw; + + /* create the pixmap that specifies the shape */ + Pixmap p = XCreatePixmap(d, win, width, height, 1); + gp = XCreateGC(d, p, 0, NULL); + XSetForeground(d, gp, WhitePixel(d, g_screen)); + XFillRectangle(d, p, gp, 0, 0, width, height); + XSetForeground(d, gp, BlackPixel(d, g_screen)); + //int radius = 50; + //XFillArc(d, p, gp, + // width/2 - radius, height/2 - radius, radius, radius, + // 0, 360*64); + XFillRectangle(d, p, gp, bw + framewidth, bw + framewidth, + holewidth, holeheight); + + /* set the pixmap as the new window mask; + the pixmap is slightly larger than the window to allow for the window + border and title bar (as added by the window manager) to be visible */ + XShapeCombineMask(d, win, ShapeBounding, -bw, -bw, p, ShapeSet); + XFreeGC(d, gp); + XFreePixmap(d, p); +} + +void window_make_intransparent(Window win, int width, int height) { + // inspired by the xhole.c example + // http://www.answers.com/topic/xhole-c + Display* d = g_display; + GC gp; + int bw = 100; // add a large border, just to be sure the border is visible + width += 2*bw; + height += 2*bw; + + /* create the pixmap that specifies the shape */ + Pixmap p = XCreatePixmap(d, win, width, height, 1); + gp = XCreateGC(d, p, 0, NULL); + XSetForeground(d, gp, WhitePixel(d, g_screen)); + XFillRectangle(d, p, gp, 0, 0, width, height); + /* set the pixmap as the new window mask; + the pixmap is slightly larger than the window to allow for the window + border and title bar (as added by the window manager) to be visible */ + XShapeCombineMask(d, win, ShapeBounding, -bw, -bw, p, ShapeSet); + XFreeGC(d, gp); + XFreePixmap(d, p); +} + + +Point2D get_cursor_position() { + Point2D point; + Window win, child; + int wx, wy; + unsigned int mask; + if (True != XQueryPointer(g_display, g_root, &win, &child, + &point.x, &point.y, &wx,&wy, &mask)) { + HSWarning("Can not query cursor coordinates via XQueryPointer\n"); + point.x = 0; + point.y = 0; + } + return point; +} diff -Nru herbstluftwm-0.6.2/version.mk herbstluftwm-0.7.0/version.mk --- herbstluftwm-0.6.2/version.mk 2014-03-27 01:54:17.000000000 +0000 +++ herbstluftwm-0.7.0/version.mk 2016-02-04 17:00:29.000000000 +0000 @@ -1,9 +1,9 @@ # version is major.minor.patchlevel$suffix VERSION_MAJOR = 0 -VERSION_MINOR = 6 +VERSION_MINOR = 7 # patch level -VERSION_PATCH = 2 +VERSION_PATCH = 0 # git version ifneq (,$(wildcard .git)) ifneq (,$(shell which git 2>/dev/null)) diff -Nru herbstluftwm-0.6.2/www/compose.py herbstluftwm-0.7.0/www/compose.py --- herbstluftwm-0.6.2/www/compose.py 2014-03-26 23:54:22.000000000 +0000 +++ herbstluftwm-0.7.0/www/compose.py 2015-06-03 21:27:47.000000000 +0000 @@ -29,6 +29,7 @@ filename = sys.argv[1] name = filename.replace('-content.html', '') +toc = filename.replace('-content.html', '-toc.html') windowtitle = "herbstluftwm" for title, subpages in tabs.iteritems(): @@ -105,10 +106,18 @@ url = basename + ".html",title = title) print "
      " + + print """\
      \ """ +# possibly table of contents: +try: + print open(toc).read() +except IOError: + # no toc file + print "" print open(filename).read() diff -Nru herbstluftwm-0.6.2/www/download.txt herbstluftwm-0.7.0/www/download.txt --- herbstluftwm-0.6.2/www/download.txt 2014-03-26 23:54:22.000000000 +0000 +++ herbstluftwm-0.7.0/www/download.txt 2015-10-14 13:27:40.000000000 +0000 @@ -3,9 +3,10 @@ Install herbstluftwm via your package manager! It is currently available on the following platforms: - * Arch Linux via the - link:https://aur.archlinux.org/packages.php?O=0&K=herbstluftwm[Arch User - Repository] + * Arch Linux via the package in the community repository or the git-version + in the + link:https://aur.archlinux.org/packages.php?O=0&K=herbstluftwm-git[Arch + User Repository] * link:http://packages.debian.org/search?keywords=herbstluftwm[Debian] * link:http://packages.gentoo.org/package/x11-wm/herbstluftwm[Gentoo Linux] * Mac OS X via @@ -14,6 +15,7 @@ * Ubuntu Linux * link:https://apps.fedoraproject.org/packages/herbstluftwm[Fedora] * link:http://www.openbsd.org/cgi-bin/cvsweb/ports/x11/herbstluftwm/[OpenBSD] + * link:http://alpinelinux.org/apk/main/x86_64/herbstluftwm[Alpine Linux] It's confirmed to run on: @@ -24,27 +26,28 @@ the most current released tarball: [options="header"] -|========================================================================================================== -| Version | Date | link:tarballs/MD5SUMS[MD5SUMS] | Tarball -| 0.1 | 2011-10-02 | 284067728e56f... | link:tarballs/herbstluftwm-0.1.tar.gz[tar.gz] -| 0.2 | 2012-01-25 | 1628f236a7086... | link:tarballs/herbstluftwm-0.2.tar.gz[tar.gz] -| 0.3 | 2012-04-12 | 176b82a7b5881... | link:tarballs/herbstluftwm-0.3.tar.gz[tar.gz] -| 0.4 | 2012-08-18 | 698b43bde76f9... | link:tarballs/herbstluftwm-0.4.tar.gz[tar.gz] -| 0.4.1 | 2012-08-30 | 2cf235dd9e0c4... | link:tarballs/herbstluftwm-0.4.1.tar.gz[tar.gz] -| 0.5.0 | 2012-12-31 | ed28cc9d89d5f... | link:tarballs/herbstluftwm-0.5.0.tar.gz[tar.gz] -| 0.5.1 | 2013-01-05 | c6ea924d947df... | link:tarballs/herbstluftwm-0.5.1.tar.gz[tar.gz] -| 0.5.2 | 2013-06-23 | 4cbbe35ac3627... | link:tarballs/herbstluftwm-0.5.2.tar.gz[tar.gz] -| 0.5.3 | 2013-12-24 | 8b7f430c85d22... | link:tarballs/herbstluftwm-0.5.3.tar.gz[tar.gz] -| 0.6.0 | 2014-03-19 | c62caa0c67a25... | link:tarballs/herbstluftwm-0.6.0.tar.gz[tar.gz] -| 0.6.1 | 2014-03-25 | c385fe9e68876... | link:tarballs/herbstluftwm-0.6.1.tar.gz[tar.gz] +|==================================================================================================================== +| Version | Date | link:tarballs/MD5SUMS[MD5SUMS] | Tarball | Signature +| 0.1 | 2011-10-02 | 284067728e56f... | link:tarballs/herbstluftwm-0.1.tar.gz[tar.gz] | +| 0.2 | 2012-01-25 | 1628f236a7086... | link:tarballs/herbstluftwm-0.2.tar.gz[tar.gz] | +| 0.3 | 2012-04-12 | 176b82a7b5881... | link:tarballs/herbstluftwm-0.3.tar.gz[tar.gz] | +| 0.4 | 2012-08-18 | 698b43bde76f9... | link:tarballs/herbstluftwm-0.4.tar.gz[tar.gz] | +| 0.4.1 | 2012-08-30 | 2cf235dd9e0c4... | link:tarballs/herbstluftwm-0.4.1.tar.gz[tar.gz] | +| 0.5.0 | 2012-12-31 | ed28cc9d89d5f... | link:tarballs/herbstluftwm-0.5.0.tar.gz[tar.gz] | +| 0.5.1 | 2013-01-05 | c6ea924d947df... | link:tarballs/herbstluftwm-0.5.1.tar.gz[tar.gz] | +| 0.5.2 | 2013-06-23 | 4cbbe35ac3627... | link:tarballs/herbstluftwm-0.5.2.tar.gz[tar.gz] | +| 0.5.3 | 2013-12-24 | 8b7f430c85d22... | link:tarballs/herbstluftwm-0.5.3.tar.gz[tar.gz] | +| 0.6.0 | 2014-03-19 | c62caa0c67a25... | link:tarballs/herbstluftwm-0.6.0.tar.gz[tar.gz] | +| 0.6.1 | 2014-03-25 | c385fe9e68876... | link:tarballs/herbstluftwm-0.6.1.tar.gz[tar.gz] | +| 0.6.2 | 2014-03-27 | 8bfbbdb16cf88... | link:tarballs/herbstluftwm-0.6.2.tar.gz[tar.gz] | // do not remove this: next version line will be added here -|========================================================================================================== +|==================================================================================================================== -The current development version is available in the -link:http://git.informatik.uni-erlangen.de/?p=re06huxa/herbstluftwm[GitWeb] +The current development version is available on +link:http://github.com/herbstluftwm/herbstluftwm[Github] or can be cloned with: ---- -git clone git://git.informatik.uni-erlangen.de/re06huxa/herbstluftwm +git clone git://github.com/herbstluftwm/herbstluftwm ---- diff -Nru herbstluftwm-0.6.2/www/faq.txt herbstluftwm-0.7.0/www/faq.txt --- herbstluftwm-0.6.2/www/faq.txt 2014-03-26 23:54:22.000000000 +0000 +++ herbstluftwm-0.7.0/www/faq.txt 2015-10-14 14:07:38.000000000 +0000 @@ -1,43 +1,46 @@ Hints and FAQ -------------- +============= -Hint: Don't restart:: - To make changes in your autostart take effect immediately, just type - +herbstclient reload+. There is no need to restart herbstluftwm or X. + - + - Even if you just updated you herbstluftwm-binary, there's no need to restart - anything. Run +herbstclient wmexec+, which does an +exec+(3) on the new - herbstluftwm version. (You also can use +wmexec+ to switch to another window - manager without restarting anything) - -Hint: Use scripts!:: - There are a bunch of scripts coming along with herbstluftwm. Check out the - scripts directory in the sources and the examples directory after installing. - -Hint: Understanding processes:: - To understand the relationship between the different processes that running - in a typical herbstluftwm setup, consider the following diagram: -+ ----- - startx - | f/e - V - ~/.xinitrc - | f/e or exec - IPC-Call V - .- - - -> herbstluftwm __________________ - ." / \ | Symbol | Meaning | - . / \ |--------+---------| - . f/e / \ f/e | A | A forks | - . / \ | | f/e | and | - . V V | V | execs | - . autostart xterm | B | into B | - . | | |________|_________| - . f/e | | f/e - . V V - -- herbstclient $SHELL +Hint: Don't restart +------------------- +To make changes in your autostart take effect immediately, just type ++herbstclient reload+. There is no need to restart herbstluftwm or X. + + +Even if you just updated you herbstluftwm-binary, there's no need to restart +anything. Run +herbstclient wmexec+, which does an +exec+(3) on the new +herbstluftwm version. (You also can use +wmexec+ to switch to another window +manager without restarting anything) + +Hint: Use scripts! +------------------ +There are a bunch of scripts coming along with herbstluftwm. Check out the +scripts directory in the sources and the examples directory after installing. + +Hint: Understanding processes +----------------------------- +To understand the relationship between the different processes that running +in a typical herbstluftwm setup, consider the following diagram: + +---- + startx + | f/e + V + ~/.xinitrc + | f/e or exec +IPC-Call V +.- - - -> herbstluftwm __________________ +." / \ | Symbol | Meaning | +. / \ |--------+---------| +. f/e / \ f/e | A | A forks | +. / \ | | f/e | and | +. V V | V | execs | +. autostart xterm | B | into B | +. | | |________|_________| +. f/e | | f/e +. V V +-- herbstclient $SHELL ---- -+ + As you can see, +herbstclient+ does nothing except sending requests to +herbstluftwm+. Whenever a process performs a fork-and-exec, the following rules apply: @@ -53,74 +56,105 @@ * If a process spawns a window, then the window will spawn delayed. This delay differs from application to application (and from time to time). So a script like -+ + ---- herbstclient spawn xterm herbstclient spawn xev ---- -+ + does *not* guarantee that the +xterm+ window will appear before the +xev+ window! It only guarantees that the +xterm+ is executed before +xev+ will be executed.+ -+ + => If you want to apply some rules only for the next windows, then use a bash-script like the one for <>. -Q: Why is herbstluftwm called herbstluftwm?:: - I liked the name of the e-mail client wanderlust. Unfortunately I am a happy - mutt user, so I needed an other application with a similar name. +Q: Why is herbstluftwm called herbstluftwm? +------------------------------------------- +I liked the name of the e-mail client wanderlust. Unfortunately I am a happy +mutt user, so I needed an other application with a similar name. [[FORK]] -Q: Is herbstluftwm a fork of dwm/musca/wmii/...?:: - No. It was written from scratch, although it borrows some basic XLib function calls - (like updating numlock-state, sending a +WMDelete+-Message to a client, - updating the urgent hints, ...) from dwm. - -Q: If the config is a bash script, does it mean it is called on each keystroke?:: - No, the configuration file is executed once to set internal settings and - keybindings and so on. If a keybinding is registered and its key is pressed, - the according (internal) command directly is called. - -Q: How can I let single clients float?:: - Not at all. You don't need it. You have the power of manual tiling, so - there is no need to place clients manually by dragging. Even better: - You don't even need to place them manually. Use a rule to place special - dialogs automatically when they appear. See the GIMP-Example for a good - example. - -Q: But I use GIMP very often, how can I use it without floating?:: - Load a predefined layout to a gimp tag. Move the GIMP-Tool windows to the - left and right border and put the rest in the center. Add this to your - autostart: -+ +Q: Is herbstluftwm a fork of dwm/musca/wmii/...? +------------------------------------------------ +No. It was written from scratch, although it borrows some basic XLib function calls +(like updating numlock-state, sending a +WMDelete+-Message to a client, +updating the urgent hints, ...) from dwm. + +Q: If the config is a bash script, does it mean it is called on each keystroke? +------------------------------------------------------------------------------- +No, the configuration file is executed once to set internal settings and +keybindings and so on. If a keybinding is registered and its key is pressed, +the according (internal) command directly is called. + +Q: How can I let single clients float? +-------------------------------------- +Not at all. You don't need it. You have the power of manual tiling, so +there is no need to place clients manually by dragging. Even better: +You don't even need to place them manually. Use a rule to place special +dialogs automatically when they appear. See the GIMP-Example for a good +example. + +Q: But I use GIMP very often, how can I use it without floating? +---------------------------------------------------------------- +Load a predefined layout to a gimp tag. Move the GIMP-Tool windows to the +left and right border and put the rest in the center. Add this to your +autostart: + ---- # GIMP # ensure there is a gimp tag hc add gimp hc load gimp ' (split horizontal:0.850000:0 - (split horizontal:0.200000:1 - (clients vertical:0) - (clients grid:0)) - (clients vertical:0)) +(split horizontal:0.200000:1 +(clients vertical:0) +(clients grid:0)) +(clients vertical:0)) ' # load predefined layout # center all other gimp windows on gimp tag hc rule class=Gimp tag=gimp index=01 pseudotile=on hc rule class=Gimp windowrole~'gimp-(image-window|toolbox|dock)' \ - pseudotile=off +pseudotile=off hc rule class=Gimp windowrole=gimp-toolbox focus=off index=00 hc rule class=Gimp windowrole=gimp-dock focus=off index=1 ---- -+ + +Q: What about a layout for Instant Messaging applications (Gajim, Pidgin, …)? +----------------------------------------------------------------------------- +A good layout for Instant Messaging applications looks as follows: One frame on +the left displays the buddy list/roster, consuming ~15% of the monitor space, +while the right side is used for the conversations. This can be configured +easily with herbstluftwm. The following example configures such a layout on tag +'7' and creates the rules to automatically move Gajim's windows to the right +frame: + +---- +hc load 7 '(split horizontal:0.15:1 (clients horizontal:0) (clients grid:4))' +hc rule class="Gajim" tag=7 index=1 +hc rule class="Gajim" windowrole="roster" tag=7 index=0 +---- + +For pidgin, the setup looks similar. In this case the buddy list is on the +right with a width of 20% of the monitor space. In addition to the above, the +buddy list will not receive input focus when it shows up: +---- +imtag=7 # just set the name of the tag here +hc load "$imtag" '(split horizontal:0.800000:0 (clients grid:0) (clients vertical:0))' +hc rule class=Pidgin windowrole=buddy_list tag=$imtag index=1 focus=off +hc rule class=Pidgin ! windowrole=buddy_list tag=$imtag index=0 +---- + [[TEMP_RULES]] -Q: How can I add rules temporarily for some special clients?:: - Add a rule for the clients pid, before the client appears. This script - creates two xterms with different behaviours: -+ +Q: How can I add rules temporarily for some special clients? +------------------------------------------------------------ +Add a rule for the clients pid, before the client appears. This script +creates two xterms with different behaviours: + ---- -#!/bin/bash +#!/usr/bin/env bash # Requirement: bash >= 4.0 (because of the usage of $BASHPID) @@ -141,66 +175,72 @@ spawn_with_rules xterm ---- -Q: Why doesn't a new client receive focus?:: - The reason is the default setting of the +focus+ consequence in the rules. - You can change it by adding this to the link:herbstluftwm.html#RULES[rules] - section in the autostart file: -+ +Q: Why doesn't a new client receive focus? +------------------------------------------ +The reason is the default setting of the +focus+ consequence in the rules. +You can change it by adding this to the link:herbstluftwm.html#RULES[rules] +section in the autostart file: + ---- hc unrule --all # clear rules hc rule focus=on # focus new clients by default ---- -+ + Warning: you need the current git version to get rules! -Q: herbstclient is too long to type it in the shell:: - Use tab-completion! +her<tab>c<tab>+ expands to herbstclient. - There is also a tab-completion for the herbstclient parameters. After - installing herbstluftwm, add this to your .bashrc: -+ +Q: herbstclient is too long to type it in the shell +--------------------------------------------------- +Use tab-completion! +her<tab>c<tab>+ expands to herbstclient. +There is also a tab-completion for the herbstclient parameters. After +installing herbstluftwm, add this to your .bashrc: + ---- source /etc/bash_completion.d/herbstclient-completion ---- -+ + (The tab-completion in zsh works out of the box with most zsh-configurations). -+ + You also can add an alias for herbstclient: -+ + ---- alias hc='herbstclient' ---- -Q: My rules seem to be messed up:: - Clear them with +hc unrule -F+ and start over. It is recommended to do this - in the autostart file. +Q: My rules seem to be messed up +-------------------------------- +Clear them with +hc unrule -F+ and start over. It is recommended to do this +in the autostart file. -Q: I don't like that my mplayervideo/inputdialogs get resized to full framesize:: +Q: I don't like that my mplayervideo/inputdialogs get resized to full framesize +------------------------------------------------------------------------------- Add this to your autostartfile: -+ + ---- hc rule instance= pseudotile=on ---- -+ + You can request the instancename with xprop by clicking on the related window. ++ is the first string in the line +WM_CLASS(STRING)+ (for mplayer that would be +xv+, for firefox dialogs it is +Dialog+). -Q: I set default_frame_layout to my favorite layout but it doesn't work with the root frame/existing frames:: - Existing tags are not affected by a change of that variable (only new - ones), so be sure to set it *before* creating any tags. A current - workaround is to put +hc split vertical 0.5; hc remove+ at the end in your - autostart file. You can also 'cycle_layout' in existing tags. +Q: I set default_frame_layout to my favorite layout but it doesn't work with the root frame/existing frames +----------------------------------------------------------------------------------------------------------- +Existing tags are not affected by a change of that variable (only new +ones), so be sure to set it *before* creating any tags. A current +workaround is to put +hc split vertical 0.5; hc remove+ at the end in your +autostart file. You can also 'cycle_layout' in existing tags. [[PANELS]] -Q: How can I start external panels correctly?:: - The cleanest solution to start the external EWMH panel (like +xfce4-panel+) - from the autostart and manually reserve some space for it. Also start - +herbstclient+ instance that knows when to kill the panel again so that - there aren't multiple instances when reloading the autostart multiple - times. Append the following code to your +bash+ autostart (assuming the - panel needs 31 pixels at the bottom of monitor 0): -+ +Q: How can I start external panels correctly? +--------------------------------------------- +The cleanest solution to start the external EWMH panel (like +xfce4-panel+) +from the autostart and manually reserve some space for it. Also start ++herbstclient+ instance that knows when to kill the panel again so that +there aren't multiple instances when reloading the autostart multiple +times. Append the following code to your +bash+ autostart (assuming the +panel needs 31 pixels at the bottom of monitor 0): + ---- # add an external panel { @@ -220,7 +260,136 @@ } & ---- -Q: I'm using a compositing manager like xcompmgr and get ugly artifacts when switching tags or splitting frames:: - You probably have an old version of herbstluftwm and +frame_bg_transparent+ - enabled. Disable this setting and use +frame_active_opacity+ and/or - +frame_normal_opacity+ instead or upgrade to the current git version. +Q: I'm using a compositing manager like xcompmgr and get ugly artifacts when switching tags or splitting frames +--------------------------------------------------------------------------------------------------------------- +You probably have an old version of herbstluftwm and +frame_bg_transparent+ +enabled. Disable this setting and use +frame_active_opacity+ and/or ++frame_normal_opacity+ instead or upgrade to the current git version. + +Q: How can I keybind a simple "Run" dialog? +------------------------------------------- +Install dmenu and keybind +dmenu_run_hlwm+ by adding the following line to +your autostart file: + +---- +hc keybind $Mod-p spawn dmenu_run_hlwm +---- + +Note that +$Mod-p+ is bound to +pseudotile toggle+ in the default +autostart of herbstluftwm, so you either need to change that binding or +use a different one for +spawn dmenu_run_hlwm+. + + +Q: How can I have a seperate list of tags per monitor? +------------------------------------------------------ + +As a solution: add the desired tags for each monitor and then configure the +keybindings s.t. the i'th key references the i'th tag of that monitor instead +of the i'th of all the tags. You can achieve this by replacing the section +"tags" and "cycle through tags" in the autostart by the following: + +---- +# tags +mon1_names=( 1_{1..5} ) # tag names for monitor 1 +mon2_names=( 2_{1..5} ) # tag names for monitor 2 +fallback_names=( {1..5} ) # tag names for all other monitors +tag_keys=( {1..9} 0 ) + +hc rename default "${mon1_names[0]}" || true +for tag in "${mon1_names[@]}" "${mon2_names[@]}" "${fallback_names[@]}" ; do + hc try silent add "$tag" +done + +for i in ${!tag_keys[@]} ; do + key="${tag_keys[$i]}" + # they kebinding is as follows: the or executes the commands seperated by + # CASE and stops executing them after the first of those succeeds. + # the first command is: check that we are on monitor 3 and can switch to tag "${mon1_names[$i]}" + # if only one of these to steps fail, try the second one (and so one). + # The setup for two monitors is as follows: + hc keybind "$Mod-$key" \ + or CASE and . compare monitors.focus.index = 0 . use "${mon1_names[$i]}" \ + CASE and . compare monitors.focus.index = 1 . use "${mon2_names[$i]}" \ + CASE use "${fallback_names[$i]}" + hc keybind "$Mod-Shift-$key" \ + or CASE and . compare monitors.focus.index = 0 . move "${mon1_names[$i]}" \ + CASE and . compare monitors.focus.index = 1 . move "${mon2_names[$i]}" \ + CASE move "${fallback_names[$i]}" +done + +# cycle through tags +# add additional information in order to cycle only through a monitor's tags +# and not through all tags +define-tag-cycle() { + local n=$# + local tags=( "$@" ) + for i in "${!tags[@]}" ; do + local t="${tags[$i]}" + hc chain , new_attr string tags.by-name."$t".my_previous_tag \ + , set_attr tags.by-name."$t".my_previous_tag "${tags[$(((i - 1 + n) % n))]}" \ + , new_attr string tags.by-name."$t".my_next_tag \ + , set_attr tags.by-name."$t".my_next_tag "${tags[$(((i + 1) % n))]}" + done +} + +define-tag-cycle "${mon1_names[@]}" +define-tag-cycle "${mon2_names[@]}" +define-tag-cycle "${fallback_names[@]}" + +# cycle through tags +# check whether the current tag as a custom "next tag" configured +# if yes, jump to that one, otherwise fall back to ordinary use_index +1 +hc keybind $Mod-period or , substitute NEXT tags.focus.my_next_tag use NEXT \ + , use_index +1 --skip-visible +hc keybind $Mod-comma or , substitute PREV tags.focus.my_previous_tag use PREV \ + , use_index +1 --skip-visible +---- + +You should also set +swap_monitors_to_get_tag+ to 0. Also consider the +following hint for shifting windows between monitors: + +Q: How to navigate between monitors? +------------------------------------ +In order to switch focus between the monitors, use the usual direction based +focusing (the command +focus+). It either focuses a window on the current +monitor or the next monitor if the boundary is reached. + +The analogous behaviour for +shift+ is not implemented yet, so you need to +configure it in your +autostart+. In order to shift windows from monitor to +monitor, replace the usual usage of +shift+ in your autostart by this one: + +---- +hc keybind $Mod-Shift-h or / shift left / \ + chain , lock , shift_to_monitor -l , focus_monitor -l , unlock +hc keybind $Mod-Shift-j or / shift down / \ + chain , lock , shift_to_monitor -d , focus_monitor -d , unlock +hc keybind $Mod-Shift-k or / shift up / \ + chain , lock , shift_to_monitor -u , focus_monitor -u , unlock +hc keybind $Mod-Shift-l or / shift right / \ + chain , lock , shift_to_monitor -r , focus_monitor -r , unlock +---- +(or analogously with arrow keys instead of +hjkl+). Again, this shifts a +window to the next monitor if the monitor boundary is reached. + +[[firstautostart]] +Q: How do I detect whether it is the first time that autostart is executed? +---------------------------------------------------------------------------- +If you want to actually autostart applications on herbstluftwm startup, one +needs to take care that they are not executed on successive reloads. The +following command returns success on the first time, autostart is executed, and +failure on successive calls: +---- +herbstclient silent new_attr bool my_not_first_autostart +---- +It tries to create a new attribute (on the root object). If it is the first +autostart run, then this succeeds. On any successive execution, this command +fails, because the attribute +my_not_first_autostart+ already exists. +An example looks as follows: +---- +if hc silent new_attr bool my_not_first_autostart ; then + /path/to/examples/exec_on_tag.sh web firefox & + pidgin & +fi +---- + + diff -Nru herbstluftwm-0.6.2/www/gentoc.sh herbstluftwm-0.7.0/www/gentoc.sh --- herbstluftwm-0.6.2/www/gentoc.sh 1970-01-01 00:00:00.000000000 +0000 +++ herbstluftwm-0.7.0/www/gentoc.sh 2015-06-03 21:27:47.000000000 +0000 @@ -0,0 +1,12 @@ +#!/usr/bin/env bash + +# if you know a cleaner version then grepping everything out of html +# then you are very welcome to improve this script! + +echo '

      Frequently asked questions

      ' +echo '
        ' +echo '

        Table of contents

        ' +grep '

        \(.*\)

        ,
      • \2
      • ,' +echo '
      ' + diff -Nru herbstluftwm-0.6.2/www/index.txt herbstluftwm-0.7.0/www/index.txt --- herbstluftwm-0.6.2/www/index.txt 2014-03-26 23:54:22.000000000 +0000 +++ herbstluftwm-0.7.0/www/index.txt 2015-10-14 13:27:40.000000000 +0000 @@ -23,10 +23,10 @@ How to get it? -------------- Install it via the package manager, link:download.html[download tarballs], or -clone the git repository: +clone the link:http://github.com/herbstluftwm/herbstluftwm[git repository]: ---- -git clone git://git.cs.fau.de/hlwm +git clone git://github.com/herbstluftwm/herbstluftwm ---- How to use it? diff -Nru herbstluftwm-0.6.2/www/main.css herbstluftwm-0.7.0/www/main.css --- herbstluftwm-0.6.2/www/main.css 2014-03-26 23:54:22.000000000 +0000 +++ herbstluftwm-0.7.0/www/main.css 2015-09-23 12:51:00.000000000 +0000 @@ -355,3 +355,29 @@ background-color: white; } +/* things for table of contents */ +.toc { + list-style: outside none; + border: 1px solid #5D9B4C; + background-color: #E9FEE3; + marker-offset: 0px; + padding: 0.4em; + margin: 1em; +} + +.toc h4 { + font-weight: bold; + margin: 0px; + margin-bottom: 0.3em; +} + +.toc li a { + color: black; +} + +.toc li { + margin-top: 0.1em; + margin-bottom: 0.1em; + padding: 0pt; + padding-left: 1.3em; +} diff -Nru herbstluftwm-0.6.2/www/Makefile herbstluftwm-0.7.0/www/Makefile --- herbstluftwm-0.6.2/www/Makefile 2014-03-26 23:54:22.000000000 +0000 +++ herbstluftwm-0.7.0/www/Makefile 2015-10-14 13:27:40.000000000 +0000 @@ -35,7 +35,12 @@ herbstclient.html: ../doc/herbstclient.txt -tour.html: ../doc/herbstluftwm-tutorial.txt +tutorial.html: ../doc/herbstluftwm-tutorial.txt + +faq-toc.html: faq-content.html ./gentoc.sh + ./gentoc.sh $< > $@ + +faq.html: faq-toc.html clean: rm -f *.html *-content.html