herbstluftwm-0.7.0/0000755000175000001440000000000012654701664014044 5ustar thorstenusersherbstluftwm-0.7.0/version.mk0000644000175000001440000000062312654701655016063 0ustar thorstenusers# version is major.minor.patchlevel$suffix VERSION_MAJOR = 0 VERSION_MINOR = 7 # patch level VERSION_PATCH = 0 # git version ifneq (,$(wildcard .git)) ifneq (,$(shell which git 2>/dev/null)) VERSION_GIT = \ \($(shell git rev-parse --short HEAD)\) endif endif VERSION_SUFFIX = "" SHORTVERSION = $(VERSION_MAJOR).$(VERSION_MINOR).$(VERSION_PATCH)$(VERSION_SUFFIX) VERSION = $(SHORTVERSION)$(VERSION_GIT) herbstluftwm-0.7.0/release.sh0000755000175000001440000000361112533670523016017 0ustar thorstenusers#!/usr/bin/env bash version="$1" if [ -z "$1" ] || [ "$1" = -h ] ; then echo "$0 VERSIONMAJOR.VERSIONMINOR.VERSIONPATCH" echo " Releases the specified version (tagging and tarball creation)" exit 0 fi if git status --porcelain | grep '^ M' ; then echo "You have unstaged changes. Fix them or use git stash" >&2 exit 1 fi IFS=. read -ra versionargs <<< "$version" echo "==> Release commit" echo ":: Patching version.mk" sed -i -e "s/^VERSION_MAJOR.*$/VERSION_MAJOR = ${versionargs[0]}/" \ -e "s/^VERSION_MINOR.*$/VERSION_MINOR = ${versionargs[1]}/" \ -e "s/^VERSION_PATCH.*$/VERSION_PATCH = ${versionargs[2]}/" version.mk echo ":: Patching NEWS" date=$(date +%Y-%m-%d) newheader="Release $version on $date" newunderline="$(echo $newheader | sed 's/./-/g')" headerexp="^Current git version$" # this requires new sed sed -i -e "/$headerexp/,+1s/^[-]*$/$newunderline/" \ -e "s/$headerexp/$newheader/" NEWS echo ":: Commiting changes" git add NEWS version.mk git commit -m "Release $version" echo ":: Tagging commit" git tag -s "v$version" -m "Release $version" echo "==> Tarball" echo ":: Tarball creation" make tar 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] |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" git add www/download.txt git commit -m "www: Add $version tarball" echo echo "Still to do:" echo "1. Add the following line to the MD5SUMS file on the mirror:" md5sum "$tarball" echo "2. Make www files and install them on the remote" echo "3. Push the changes to all public remotes (including --tags)" herbstluftwm-0.7.0/ipc-client/0000755000175000001440000000000012654701664016073 5ustar thorstenusersherbstluftwm-0.7.0/ipc-client/main.c0000644000175000001440000002064412533670523017164 0ustar thorstenusers/** 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 #include #include #include "ipc-client.h" #include "../src/globals.h" #include "../src/ipc-protocol.h" #include "client-utils.h" #define HERBSTCLIENT_VERSION_STRING \ "herbstclient " HERBSTLUFT_VERSION " (built on " __DATE__ ")\n" void print_help(char* command, FILE* file); void init_hook_regex(int argc, char* argv[]); 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; int g_hook_regex_count = 0; int g_hook_count = 1; // count of hooks to wait for, 0 means: forever static void quit_herbstclient(int signal) { // TODO: better solution to quit x connection more softly? fprintf(stderr, "interrupted by signal %d\n", signal); destroy_hook_regex(); exit(EXIT_FAILURE); } void init_hook_regex(int argc, char* argv[]) { g_hook_regex = (regex_t*)malloc(sizeof(regex_t)*argc); assert(g_hook_regex != NULL); int i; // create all regexes for (i = 0; i < argc; i++) { int status = regcomp(g_hook_regex + i, argv[i], REG_NOSUB|REG_EXTENDED); if (status != 0) { char buf[ERROR_STRING_BUF_SIZE]; regerror(status, g_hook_regex + i, buf, ERROR_STRING_BUF_SIZE); fprintf(stderr, "Cannot parse regex \"%s\": ", argv[i]); fprintf(stderr, "%s\n", buf); destroy_hook_regex(); exit(EXIT_FAILURE); } } g_hook_regex_count = argc; } void destroy_hook_regex() { int i; for (i = 0; i < g_hook_regex_count; i++) { regfree(g_hook_regex + i); } free(g_hook_regex); } void print_help(char* command, FILE* file) { // Eventually replace this and the option parsing with some fancy macro // based thing? Is the cost of maintainance really that high? fprintf(file, "Usage: %s [OPTIONS] COMMAND [ARGS ...]\n" " %s [OPTIONS] [--wait|--idle] [FILTER ...]\n", command, command); char* help_string = "Send a COMMAND with optional arguments ARGS to a running " "herbstluftwm instance.\n\n" "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 " "received and printed. The default of COUNT is 1.\n" "\t-q, --quiet: Do not print error messages if herbstclient cannot " "connect to the running herbstluftwm instance.\n" "\t-v, --version: Print the herbstclient version. To get the " "herbstluftwm version, use 'herbstclient version'.\n" "\t-h, --help: Print this help." "\n" "See the man page (herbstclient(1)) for more details.\n"; fputs(help_string, file); } int main_hook(int argc, char* argv[]) { init_hook_regex(argc, argv); Display* display = XOpenDisplay(NULL); if (!display) { if (!g_quiet) { fprintf(stderr, "Cannot open display\n"); } return EXIT_FAILURE; } HCConnection* con = hc_connect_to_display(display); signal(SIGTERM, quit_herbstclient); signal(SIGINT, quit_herbstclient); signal(SIGQUIT, quit_herbstclient); while (1) { bool print_signal = true; int hook_argc; char** hook_argv; if (!hc_next_hook(con, &hook_argc, &hook_argv)) { fprintf(stderr, "Cannot listen for hooks\n"); destroy_hook_regex(); return EXIT_FAILURE; } for (int i = 0; i < argc && i < hook_argc; i++) { if (0 != regexec(g_hook_regex + i, hook_argv[i], 0, NULL, 0)) { // found an regex that did not match // so skip this print_signal = false; break; } } if (print_signal) { 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"); } fflush(stdout); } argv_free(hook_argc, hook_argv); if (print_signal) { // check counter if (g_hook_count == 1) { break; } else if (g_hook_count > 1) { g_hook_count--; } } } hc_disconnect(con); XCloseDisplay(display); destroy_hook_regex(); return 0; } 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'}, {"quiet", 0, 0, 'q'}, {"version", 0, 0, 'v'}, {"help", 0, 0, 'h'}, {0, 0, 0, 0} }; // parse options while (1) { int option_index = 0; int c = getopt_long(argc, argv, "+n0lwc:iqhv", long_options, &option_index); if (c == -1) break; switch (c) { case 'i': g_hook_count = 0; g_wait_for_hook = 1; break; case 'c': g_hook_count = atoi(optarg); printf("setting to %s\n", optarg); break; case 'w': g_wait_for_hook = 1; break; 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; case 'h': print_help(argv[0], stdout); exit(EXIT_SUCCESS); case 'v': fputs(HERBSTCLIENT_VERSION_STRING, stdout); exit(EXIT_SUCCESS); default: exit(EXIT_FAILURE); } } int arg_index = optind; // index of the first-non-option argument if ((argc - arg_index == 0) && !g_wait_for_hook) { // if there are no non-option arguments, and no --idle/--wait, display // the help and exit print_help(argv[0], stderr); exit(EXIT_FAILURE); } // do communication int command_status; if (g_wait_for_hook == 1) { // install signals command_status = main_hook(argc-arg_index, argv+arg_index); } else { GString* output; bool suc = hc_send_command_once(argc-arg_index, argv+arg_index, &output, &command_status); if (!suc) { fprintf(stderr, "Error: Could not send command.\n"); return EXIT_FAILURE; } FILE* file = stdout; // on success, output to stdout if (command_status != 0) { // any error, output to stderr file = stderr; } fputs(output->str, file); if (g_ensure_newline) { if (output->len > 0 && output->str[output->len - 1] != '\n') { fputs("\n", file); } } if (command_status == HERBST_NEED_MORE_ARGS) { // needs more arguments fprintf(stderr, "%s: not enough arguments\n", argv[arg_index]); // first argument == cmd } g_string_free(output, true); } return command_status; } herbstluftwm-0.7.0/ipc-client/ipc-client.c0000644000175000001440000001751712533670523020274 0ustar thorstenusers/** Copyright 2011-2013 Thorsten Wißmann. All rights reserved. * * This software is licensed under the "Simplified BSD License". * See LICENSE for details */ #include "../src/ipc-protocol.h" #include "ipc-client.h" #include "client-utils.h" // standard #include #include #include #include #include // gui #include #include #include #include struct HCConnection { Display* display; bool own_display; // if we have to close it on disconnect Window hook_window; Window client_window; Atom atom_args; Atom atom_output; Atom atom_status; Window root; }; HCConnection* hc_connect() { Display* display = XOpenDisplay(NULL); if (display == NULL) { return NULL; } HCConnection* con = hc_connect_to_display(display); if (con) { con->own_display = true; } return con; } HCConnection* hc_connect_to_display(Display* display) { HCConnection* con = malloc(sizeof(struct HCConnection)); if (!con) { return con; } memset(con, 0, sizeof(HCConnection)); con->display = display; con->root = DefaultRootWindow(con->display); con->atom_args = XInternAtom(con->display, HERBST_IPC_ARGS_ATOM, False); con->atom_output = XInternAtom(con->display, HERBST_IPC_OUTPUT_ATOM, False); con->atom_status = XInternAtom(con->display, HERBST_IPC_STATUS_ATOM, False); return con; } void hc_disconnect(HCConnection* con) { if (con->client_window) { XDestroyWindow(con->display, con->client_window); } if (con->own_display) { XCloseDisplay(con->display); } free(con); } bool hc_create_client_window(HCConnection* con) { if (con->client_window) { return true; } /* ensure that classhint and the command is set when the hlwm-server * receives the XCreateWindowEvent */ XGrabServer(con->display); // create window con->client_window = XCreateSimpleWindow(con->display, con->root, 42, 42, 42, 42, 0, 0, 0); // set wm_class for window XClassHint *hint = XAllocClassHint(); hint->res_name = HERBST_IPC_CLASS; hint->res_class = HERBST_IPC_CLASS; XSetClassHint(con->display, con->client_window, hint); XFree(hint); XSelectInput(con->display, con->client_window, PropertyChangeMask); /* the window has been initialized properly, now allow the server to * receive the event for it */ XUngrabServer(con->display); return true; } bool hc_send_command(HCConnection* con, int argc, char* argv[], GString** ret_out, int* ret_status) { if (!hc_create_client_window(con)) { return false; } // check for running window manager instance // TODO // set arguments XTextProperty text_prop; Xutf8TextListToTextProperty(con->display, argv, argc, XUTF8StringStyle, &text_prop); XSetTextProperty(con->display, con->client_window, &text_prop, con->atom_args); XFree(text_prop.value); // get output int command_status = 0; XEvent event; GString* output = NULL; bool output_received = false, status_received = false; while (!output_received || !status_received) { XNextEvent(con->display, &event); if (event.type != PropertyNotify) { // got an event of wrong type continue; } XPropertyEvent* pe = &(event.xproperty); if (pe->window != con->client_window) { // got an event from wrong window continue; } if (!output_received && pe->atom == con->atom_output) { output = window_property_to_g_string(con->display, con->client_window, con->atom_output); if (!output) { fprintf(stderr, "could not get WindowProperty \"%s\"\n", HERBST_IPC_OUTPUT_ATOM); return false; } output_received = true; } else if (!status_received && pe->atom == con->atom_status) { int *value; Atom type; int format; unsigned long items, bytes; if (Success != XGetWindowProperty(con->display, con->client_window, XInternAtom(con->display, HERBST_IPC_STATUS_ATOM, False), 0, 1, False, XA_ATOM, &type, &format, &items, &bytes, (unsigned char**)&value)) { // if could not get window property fprintf(stderr, "could not get WindowProperty \"%s\"\n", HERBST_IPC_STATUS_ATOM); return false; } command_status = *value; XFree(value); status_received = true; } } *ret_status = command_status; *ret_out = output; return true; } bool hc_send_command_once(int argc, char* argv[], GString** ret_out, int* ret_status) { HCConnection* con = hc_connect(); if (con == NULL) { return false; } bool status = hc_send_command(con, argc, argv, ret_out, ret_status); hc_disconnect(con); return status; } static Window get_hook_window(Display* display) { int *value; // list of ints Atom type; int format; unsigned long items, bytes; int status = XGetWindowProperty(display, DefaultRootWindow(display), XInternAtom(display, HERBST_HOOK_WIN_ID_ATOM, False), 0, 1, False, XA_ATOM, &type, &format, &items, &bytes, (unsigned char**)&value); // only accept exactly one Window id if (status != Success || items != 1) { return 0; } Window win = *value; XFree(value); return win; } bool hc_hook_window_connect(HCConnection* con) { if (con->hook_window) { return true; } con->hook_window = get_hook_window(con->display); if (!con->hook_window) { return false; } long mask = StructureNotifyMask|PropertyChangeMask; XSelectInput(con->display, con->hook_window, mask); return true; } bool hc_next_hook(HCConnection* con, int* argc, char** argv[]) { if (!hc_hook_window_connect(con)) { return false; } // get window to listen at Window win = con->hook_window; // listen on window XEvent next_event; bool received_hook = false; while (!received_hook) { XNextEvent(con->display, &next_event); if (next_event.type == DestroyNotify) { if (next_event.xdestroywindow.window == win) { // hook window was destroyed // so quit idling return false; } } if (next_event.type != PropertyNotify) { fprintf(stderr, "Warning: got other event than PropertyNotify\n"); continue; } XPropertyEvent* pe = &next_event.xproperty; if (pe->state == PropertyDelete) { // just ignore property delete events continue; } if (pe->window != win) { fprintf(stderr, "Warning: expected event from window %u", (unsigned int)win); fprintf(stderr, " but got something from %u\n", (unsigned int)pe->window); continue; } XTextProperty text_prop; XGetTextProperty(con->display, win, &text_prop, pe->atom); char** list_return; int count; if (Success != Xutf8TextPropertyToTextList(con->display, &text_prop, &list_return, &count)) { XFree(text_prop.value); return false; } *argc = count; *argv = argv_duplicate(count, list_return); // has to be freed by caller received_hook = true; // cleanup XFreeStringList(list_return); XFree(text_prop.value); } return true; } herbstluftwm-0.7.0/ipc-client/client-utils.c0000644000175000001440000000314512533670523020651 0ustar thorstenusers #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); } herbstluftwm-0.7.0/ipc-client/ipc-client.h0000644000175000001440000000163712533670523020275 0ustar thorstenusers/** 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 #ifndef __HERBSTLUFT_IPC_CLIENT_H_ #define __HERBSTLUFT_IPC_CLIENT_H_ typedef struct HCConnection HCConnection; HCConnection* hc_connect(); HCConnection* hc_connect_to_display(Display* display); void hc_disconnect(HCConnection* con); /* ensure there is a client window for sending commands */ bool hc_create_client_window(HCConnection* con); bool hc_send_command(HCConnection* con, int argc, char* argv[], GString** ret_out, int* ret_status); bool hc_send_command_once(int argc, char* argv[], GString** ret_out, int* ret_status); bool hc_hook_window_connect(HCConnection* con); bool hc_next_hook(HCConnection* con, int* argc, char** argv[]); #endif herbstluftwm-0.7.0/ipc-client/client-utils.h0000644000175000001440000000053312533670523020654 0ustar thorstenusers #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 herbstluftwm-0.7.0/AUTHORS0000644000175000001440000000147512533670523015116 0ustar thorstenusersAUTHORS ======= Contributors, sorted by the number of commits in descending order: Thorsten Wißmann Florian Bruhin Hans-Peter Deifel Tyler Thomas Hart Bastien Dejean Christoph Egger Gabor Adam Toth Daniel Danner Johannes Schilling Steffen Liebergeld Georg Reinke Christian Dietrich Cheer Xiao Philipp Erhardt Olivier Ramonat Gabor X Toth Corey Richardson Clemens Lang This file has been generated by the following bash script: generate-authors () { cat > AUTHORS < {title}
""".format(title=windowtitle) #====~===~=========~== # Navigation bar #====~===~=========~== print """\ \
""" subpages = tabs[page2tab[name]] if len(subpages) > 1: print '
' for basename, title in subpages.iteritems(): if basename == name: cls = "subpagecur subpage" else: cls = "subpage" print ''.format(cls = cls) print '{title}'.format( 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() print """\ """.format(date=datetime.datetime.now().strftime('%Y-%m-%d at %H:%M:%S %Z')) #====~===~=========~== # Footer #====~===~=========~== print """\
""" # vim: noet herbstluftwm-0.7.0/www/herbstclient.txt0000644000175000001440000000004312533670523020107 0ustar thorstenusersinclude::../doc/herbstclient.txt[] herbstluftwm-0.7.0/www/tutorial.txt0000644000175000001440000000005412533670523017266 0ustar thorstenusersinclude::../doc/herbstluftwm-tutorial.txt[] herbstluftwm-0.7.0/www/gentoc.sh0000755000175000001440000000054112533670523016501 0ustar thorstenusers#!/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 '
' herbstluftwm-0.7.0/www/faq.txt0000644000175000001440000003625212607460652016205 0ustar thorstenusersHints 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 ---- As you can see, +herbstclient+ does nothing except sending requests to +herbstluftwm+. Whenever a process performs a fork-and-exec, the following rules apply: * A child process inherits the environment variables of its parent process. If you change an environment variable (like 'PATH'), then it will stay unchanged in the parent process. + + => If you want to set some environment variables for your complete session (i.e. all processes) then you have to set it in your +~/.xinitrc+. * 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. [[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: ---- # 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)) ' # 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 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: ---- #!/usr/bin/env bash # Requirement: bash >= 4.0 (because of the usage of $BASHPID) spawn_with_rules() {( # this rule also requires, that the client # sets the _NET_WM_PID property herbstclient rule once pid=$BASHPID maxage=10 "${RULES[@]}" exec "$@" ) & } # spawn an xterm with uname info, but not where the focus is RULES=( index='/' focus=off ) spawn_with_rules xterm -e 'uname -a ; read' # spawn an xterm in pseudotile mode RULES=( pseudotile=on focus=on ) 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: ---- 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: ---- 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: 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. [[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): ---- # add an external panel { pids=( ) # reserve some space for the panel on monitor 0 hc pad 0 "" "" 31 # start the panel itself and remember its pid xfce4-panel -d --sm-client-disable & pids+=( $! ) # or start another panel: # mypanel & # pids+=( $! ) # wait until the panels should be stopped herbstclient -w '(quit_panel|reload)' # stopp all started panels kill ${pids[@]} } & ---- 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 ---- herbstluftwm-0.7.0/www/migration.txt0000644000175000001440000000003012533670523017406 0ustar thorstenusersinclude::../MIGRATION[] herbstluftwm-0.7.0/www/.gitignore0000644000175000001440000000007712533670523016657 0ustar thorstenusers*.html # ignore all images *.png *.jpeg *.jpg herbstluftwm.svg herbstluftwm-0.7.0/www/index.txt0000644000175000001440000000445512607454114016541 0ustar thorstenusersWhat is it? ----------- herbstluftwm is a manual tiling window manager for X11 using Xlib and Glib. Its main features can be described with: * the layout is based on splitting frames into subframes which can be split again or can be filled with windows (similar to link:http://i3wm.org/[i3]/ link:http://aerosuidae.net/musca.html[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 link:http://xmonad.org/[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 link:http://wmii.suckless.org/[wmii]/ link:http://aerosuidae.net/musca.html[musca]) How to get it? -------------- Install it via the package manager, link:download.html[download tarballs], or clone the link:http://github.com/herbstluftwm/herbstluftwm[git repository]: ---- git clone git://github.com/herbstluftwm/herbstluftwm ---- How to use it? --------------- If you are new to herbstluftwm, the link:tutorial.html[tutorial] is the best place to start. There are manpages for link:herbstluftwm.html[herbstluftwm] and link:herbstclient.html[herbstclient] in the +doc/+ directory. They also can be seen link:herbstluftwm.html[online]. Support ------- You are welcome to join the IRC channel +#herbstluftwm+ on +irc.freenode.net+. For further bug reporting or patch submission contact the mailing list: ---- hlwm@lists.herbstluftwm.org ---- You can subscribe by sending a mail with the subject +subscribe+ to +hlwm-request@lists.herbstluftwm.org+ or by using the link:https://lists.schokokeks.org/mailman/listinfo.cgi/hlwm[Mailman web interface]. Screenshots ----------- // these screenshots are not in the git repo, so they must exist in the target // directory image:img/irc-layout-tab-thumb.png["scr",link="img/irc-layout-tab.png"] image:img/herbstluftwm-autumncolors-thumb.png["scr",link="img/herbstluftwm-autumncolors.png"] image:img/hlwm-panelicons-0-thumb.png["scr",link="img/hlwm-panelicons-0.png"] image:img/hlwm-panelicons-1-thumb.png["scr",link="img/hlwm-panelicons-1.png"] image:img/hlwm-pseudotile-1-thumb.png["scr",link="img/hlwm-pseudotile-1.png"] // vim: nowrap herbstluftwm-0.7.0/www/download.txt0000644000175000001440000000556312607454114017242 0ustar thorstenusersGet it! ------- Install herbstluftwm via your package manager! It is currently available on the following platforms: * 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 link:http://trac.macports.org/browser/trunk/dports/x11/herbstluftwm/Portfile[MacPorts] * FreeBSD * 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: * Cygwin * Many other Linux distributions If your distribution is not supported you can build a package on your own using the most current released tarball: [options="header"] |==================================================================================================================== | 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 on link:http://github.com/herbstluftwm/herbstluftwm[Github] or can be cloned with: ---- git clone git://github.com/herbstluftwm/herbstluftwm ---- herbstluftwm-0.7.0/www/news.txt0000644000175000001440000000002312533670523016373 0ustar thorstenusersinclude::../NEWS[] herbstluftwm-0.7.0/www/Makefile0000644000175000001440000000153412607454114016324 0ustar thorstenusers include ../version.mk include ../config.mk include ../colors.mk .PHONY: all clean install TARGET = herbstluftwm.org:www/ ASCIIDOCFLAGS = -s TXT = $(wildcard *.txt) INSTALLFILES = $(TXT:.txt=.html) \ herbstluftwm.html \ herbstclient.html \ herbstluftwm.svg \ main.css all: $(INSTALLFILES) %-content.html: %.txt $(call colorecho,DOC,$@) $(ASCIIDOC) $(ASCIIDOCFLAGS) -o $@ $< %.html: %-content.html compose.py ./compose.py $< > $@ herbstluftwm.svg: ../share/herbstluftwm.svg cp $^ $@ news.html: ../NEWS migration.html: ../MIGRATION herbstluftwm.html: ../doc/herbstluftwm.txt herbstclient.html: ../doc/herbstclient.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 install: all rsync -v $(INSTALLFILES) $(TARGET) herbstluftwm-0.7.0/MIGRATION0000644000175000001440000000415312533670523015316 0ustar thorstenusersherbstluftwm migration guide ============================ This guide describes the list of incompatible changes between the different herbstluftwm releases and how to migrate scripts and configuration. 0.5.3 to current git version ---------------------------- // Will be activated as soon as the patches are fixed and re-reverted //The tag attributes curframe_windex and curframe_wcount have been removed, they //are replaced by the more general attributes frames.focus.windex //frames.focus.wcount. The behaviour of swap_monitors_to_get_tag = 0 has changed. If swap_monitors_to_get_tag is set to 0, then focus the other monitor if the desired tag is shown on another monitor. Previously nothing has been done. 0.5.1 to 0.5.2 -------------- The herbstclient completion options changed for bash. If you manually setup the completion -- e.g. for an alias like hc -- then update the complete line as follows: complete -F _herbstclient_complete -o nospace herbstclient # or hc 0.4.1 to 0.5.0 -------------- herbstclient now returns the exitstatus 9 (HERBST_NEED_MORE_ARGS) instead of 3 (HERBST_INVALID_ARGUMENT) if a command needs more arguments. If an error occurs, it now outputs an error message to stderr. The padding arguments have been removed from add_monitor and a monitor name has been added instead. If you add a monitor with padding, instead of 'hc add_monitor 1024x768+0+0 1 2 3 4' now use 'hc add_monitor 1024x768+0+0; hc pad N 1 2 3 4'. As a result from adding monitor names, the output of list_monitors and stack changes a bit for named monitors. list_padding only outputs the padding for one monitor now. The setting focus_follows_shift has been removed. The behaviour now is set like it were set to 1; the focus always follows the shift now. 0.3 to 0.4 ---------- The setting window_gap is now called frame_gap. Simply replace all occurrences of window_gap by frame_gap in your old scripts (and autostart). 0.2 to 0.3 ---------- The setting ignore_class is removed, because this also can be done by rules. You can replace a line like 'set ignore_class "$foo"' in your autostart by 'rule class~"$foo" manage=off' // vim: tw=80 herbstluftwm-0.7.0/.gitignore0000644000175000001440000000062712533670523016034 0ustar thorstenusers # ignore objects *.o # ignore dependency files *.d # ignore backups/vim's files *~ .*.swp # ignore binaries herbstluftwm herbstclient # ignore generated doc doc/herbstclient.1 doc/herbstclient.html doc/herbstluftwm.1 doc/herbstluftwm.html doc/herbstluftwm-tutorial.7 doc/herbstluftwm-tutorial.html # ignore build dirs build/ etc/ # ignore tag files src/tags src/TAGS ipc-client/tags ipc-client/TAGS herbstluftwm-0.7.0/doc/0000755000175000001440000000000012654701664014611 5ustar thorstenusersherbstluftwm-0.7.0/doc/herbstluftwm-tutorial.html0000644000175000001440000007471212654701664022101 0ustar thorstenusers herbstluftwm-tutorial(7)

Description

This tutorial explains how to create a basic herbstluftwm setup and introduces the major herbstluftwm features. This tutorial neither covers all features nor specifies the mentioned features entirely; see herbstluftwm(1) for a compact and more complete description.

This tutorial covers these topics:

Basic installation

This describes two alternate installation methods. In any case, you also have to install the dependencies. Beside the standard libraries (GLib, XLib) which are found on nearly any system, you should install dzen2 (as current as possible) which is needed by the default panel.sh.

Via the package manager

You always should prefer installing herbstluftwm via your package manager on your system. It should be called herbstluftwm.

After installing it, the default configuration file has to be copied to your home directory:

mkdir -p ~/.config/herbstluftwm
cp /etc/xdg/herbstluftwm/autostart ~/.config/herbstluftwm/

You also should activate the tab completion for herbstclient. In case of bash, you can either activate the tab completion in general or source the herbstclient-completion from the bash_completion.d directory in your bashrc. In case of zsh the tab-completion normally is activated already (if not, consider activating it).

Directly from git

If there is no package for your platform or if you want to use the current git version, then you can pull directly from the main repository:

git clone git://github.com/herbstluftwm/herbstluftwm
cd herbstluftwm
make # build the binaries

# install files
mkdir -p ~/bin
# you also have to put $HOME/bin to your path, e.g. by:
echo 'export PATH=$PATH:$HOME/bin' >> ~/.bashrc # or to your zshrc, etc...
ln -s `pwd`/herbstluftwm ~/bin/
ln -s `pwd`/herbstclient ~/bin/

# copy the configuration
mkdir -p ~/.config/herbstluftwm/
cp share/autostart ~/.config/herbstluftwm/
cp share/panel.sh ~/.config/herbstluftwm/
  • If you are using bash, then source the bash completion file in your ~/.bashrc

    source path-to/herbstluftwm/share/herbstclient-completion
  • If you are using zsh, then copy the share/_herbstclient file to the appropriate zsh-completion directory.

Each time there is an update, you have to do the following steps in your herbstluftwm directory:

git pull
make

Configure herbstluftwm as your window manager

As usual you can define herbstluftwm as your window manager by either selecting herbstluftwm in your login manager or by starting it in your ~/.xinitrc, mostly by writing to your xinitrc (or .xsession on some systems):

# start herbstluftwm in locked mode (it will be unlocked at the end of your
# autostart)
exec herbstluftwm --locked

After logging in the next time, you will get a default herbstluftwm session.

First start

After starting herbstluftwm, the screen is surrounded by a green frame initially, which indicates that there is only one large frame. A frame is a container where actual windows can be placed or which can be split into two frames.

Start an xterm by pressing Alt-Return, which will fill your entire screen.

Using the client

The only way to communicate to herbstluftwm is by using the client application called herbstclient. Its usual syntax is: herbstclient COMMAND [ARGUMENTS]. This calls a certain COMMAND within your running herbstluftwm instance. This causes some effect (which depends on the given COMMAND and ARGUMENTS), produces some output which is printed by herbstclient and lets herbstclient exit with a exit-code (e.g. 0 for success) like many other UNIX tools:

    shell              COMMANDS,
       ╲ COMMAND,      ARGUMENTS
        ╲ ARGUMENTS ╭────────────╮
         ╲          │            │
          V         │            V
         herbstclient         herbstluftwm
          ╱         ^            │
         ╱ output,  │            │
        ╱ exit-code ╰────────────╯
       V               output,
 shell/terminal       exit-code

The most simple command only prints the herbstluftwm version:

$ # lines prefixed with $ describes what to type, other lines describe the
$ # typical output
$ # Type: her<tab>c<tab> ve<tab>
$ herbstclient version
herbstluftwm 0.4.1 (built on Aug 30 2012)
$ herbstclient set window_border_active_color red
$ # now the window border turned red

The configuration of herbstluftwm only is done by calling commands via herbstclient. So the only configuration file is the autostart which is placed at ~/.config/herbstluftwm/ and which is a sequence of those herbstclient calls.

Open it in your favourite text editor and replace the Mod-line by this to use the Super-key (or also called Windows-key) as the main modifier:

# Mod=Mod1 # use alt as the main modifier
Mod=Mod4 # use Super as the main modifier

After saving the autostart file, you have to reload the configuration:

# the following line is identical to directly calling:
# ~/.config/herbstluftwm/autostart
herbstclient reload

Now you may notice that the red border color of your terminal turned green again, because the color is set in the default autostart. That’s the typical configuration workflow:

  1. Try some new settings in the command line

  2. Add them to the autostart file

  3. Press Mod-Shift-r which calls the reload command or directly execute the autostart file from your shell to get the error messages if something went wrong.

To learn more about herbstluftwm, just go through the man page line by line and check using the herbstluftwm(1) man page what it does. For a quick introduction to the central paradigms, continue reading this.

Tiling

Initially there is one frame. Each frame has one of the two following possible types:

  1. It serves as a container for windows, i.e. it can hold zero up to arbitrarily many windows. Launch several more terminals to see what happens: If there are multiple windows in one frame, they are aligned below each other. To change this layout algorithm, press Mod-space to cycle all the available layouting algorithms for the focused frame.

  2. A frame also can be split into two subframes, which can be aligned next to or below each other. Press Mod-o to split to an horizontal alignment. To navigate to the fresh frame right of the old one press Mod-l. Press Mod-u to split vertically. The intuitive navigation is:

          ⎧ h (or ←) ⎫                 ⎧ left
          ⎪ j (or ↓) ⎪   means         ⎪ down
    Mod + ⎨ k (or ↑) ⎬  ═══════> focus ⎨ up
          ⎩ l (or →) ⎭                 ⎩ right

    To undo splitting, you can remove a frame via Mod-r. To shift some window from one frame to one of its neighbours, use the same keyboard shortcut while holding the Shift key pressed. It is not possible to resize single windows, only to resize frames. The according keyboard shortcut is the same while holding Control pressed. All in all it is:

                        ⎧ h (or ←) ⎫                          ⎧ left
          ⎧         ⎫   ⎪ j (or ↓) ⎪  means  ⎧ focus frame  ⎫ ⎪ down
    Mod + ⎨ Shift   ⎬ + ⎨ k (or ↑) ⎬  ═════> ⎨ move window  ⎬ ⎨ up
          ⎩ Control ⎭   ⎩ l (or →) ⎭         ⎩ resize frame ⎭ ⎩ right

With this, you can define a custom layout. It can be printed via the layout command:

$ herbstclient layout
╾─┐ horizontal 50% selection=1
  ├─┐ vertical 70% selection=0
  │ ├─╼ vertical: 0x1400009
  │ └─╼ vertical:
  └─╼ max: 0x1a00009 [FOCUS]

Just play with it a bit to how it works. You also can permanently save the layout using the dump command:

$ herbstclient dump
(split horizontal:0.500000:1
    (split vertical:0.700000:0
        (clients vertical:0 0x1400009)
        (clients vertical:0))
    (clients max:0 0x1a00009))
$ layout=$(herbstclient dump)

And after some changes you can rewind to the original layout with the load command:

$ herbstclient load "$layout"       # mind the quotes!

Tags (or workspaces or virtual desktops or ….)

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.

Monitors

The notion of a monitor in herbstluftwm is treated much more abstract and general than in other window managers: A monitor just is a rectangular part of your screen which shows exactly one tag on it.

Initially there is only one large monitor ranging over your entire screen:

$ herbstclient list_monitors
0: 1440x900+0+0 with tag "1" [FOCUS]

The output shows that there is only one monitor with index 0 at position +0+0 of size 1440x900 showing tag 1. In most cases, the herbstluftwm monitors will match the list of physical monitors. So to add another physical monitor, you have to perform several steps:

  1. Enable it, such that it shows a part of your screen. You can use xrandr, xinerama or any other tool you like.

  2. Register it in herbstluftwm: Lets assume your new monitor has the resolution 1024x768 and is right of your main screen, then you can activate it via:

    $ herbstclient set_monitors 1440x900+0+0 1024x768+1440+0

    Alternatively, if xinerama works for your setup, simply run:

    $ herbstclient detect_monitors

For even more automation, you can enable the setting auto_detect_monitors. For more advanced examples, look at the q3terminal.sh example script, which implements a drop-down-terminal like monitor where you can put any application you like.


herbstluftwm-0.7.0/doc/herbstluftwm.txt0000644000175000001440000016713012607454114020101 0ustar thorstenusersherbstluftwm(1) =============== :doctype: manpage :man version: {herbstluftwmversion} NAME ---- herbstluftwm - a manual tiling window manager for X SYNOPSIS -------- *herbstluftwm* ['OPTION' ...] DESCRIPTION ----------- Starts the *herbstluftwm* window manager on 'DISPLAY'. It also listens for calls from link:herbstclient.html[*herbstclient*(1)] and executes them. The list of available <> is listed below. 'OPTION' can be: *-c*, *--autostart* 'PATH':: use 'PATH' as autostart file instead of the one in '$XDG_CONFIG_HOME' *-v*, *--version*:: print version and exit *-l*, *--locked*:: Initially set the monitors_locked setting to 1 *--verbose*:: print verbose information to stderr This manual documents the scripting and configuration interface. For a more verbose introduction see link:tutorial.html[*herbstluftwm-tutorial*(7)]. TILING ALGORITHM ---------------- The basic tiling concept is that the layout is represented by a binary tree. On startup you see one big frame across the entire screen. A frame fulfills exactly one of the following conditions: [[LIST_LAYOUT_ALGORITHMS]] . Frame contains windows: + It shows some clients and arranges them. The current layout algorithms are: * 0: 'vertical' - clients are placed below each other * 1: 'horizontal' - clients are placed next to each other * 2: 'max' - all clients are maximized in this frame * 3: 'grid' - clients are arranged in an almost quadratic grid . Frame is split into subframes: + It is split into exactly two *subframes* in a configurable 'fraction' either in a vertical or horizontal way. So it produces two *frames* which fulfill the same conditions (new frames always are about to contain *windows*). If you split a frame that already contains windows, the windows are inherited by the first new child frame. If a new window appears, it is put in the currently focused frame. Only the leaves of the frame tree can be focused. A frame can be removed, it is then merged with its neighbour frame. Due to the layout structure of a binary tree, each frame (i.e. node in binary tree) has exactly one neighbour. The analogy to a binary tree is explained the best way with a small example: On startup you have a simple binary tree, with one frame that can contain clients: C When splitting it (e.g. with the command 'split vertical 0.5') you will get this: V / \ C C You also can split the left frame horizontally and you will get: V / \ H C / \ C C If you change the focus to the client on the right and remove this frame, it will be merged with the left subtree and you will get: H / \ C C The 'layout' command prints the current layout of all tags as a tree. [[FRAME_INDEX]] FRAME INDEX ----------- The exact position of a frame in the layout tree may be described by its *index* which is just a string of characters. The lookup algorithm starts at the root frame and selects one of its two subtrees according to the each character in the index. The characters are interpreted as follows: * +0+: select the first subtree * +1+: select the second subtree * +.+: select the subtree having the focus * +/+: select the subtree not having the focus Thus an empty string refers to the root frame, and "00" refers to the first subtree of the first subtree of the root frame. As a special case, the string "@" always refers to the currently focused frame. TAGS ---- Tags are very similar to workspaces, virtual desktops or window groups. Each tag has one layout. There is a list of tags. You can add or remove tags dynamically. [[MONITORS]] MONITORS -------- Monitors in *herbstluftwm* are totally independent of the actual physical screens. This means you can for example split your screen in two virtual monitors to view two tags at once on a big screen. Each monitor displays exactly one tag on a specified rectangle on the screen. Each monitor may have a name, which can be set via *add_monitor* and *rename_monitor*. It can be unset with the *rename_monitor* command. A monitor name is an arbitrary non-empty string which must not start with +++, +-+ or any digit. A monitor can be referenced in different ways: * by its absolute index as listed in the *list_monitors* command. * by its relative index: a +++ or +-+ followed by a delta, e.g.: +3 * by its relative position to the focused monitor. +-l+ denotes the monitor left of the focused monitor, +-r+ right of, +-u+ above of, and +-d+ below of, respectively. * by "" (an empty string) which represents the current monitor. * by its name. [[COMMANDS]] COMMANDS -------- // TODO examples in fixed font in html output *herbstluftwm* is controlled by internal commands, which can be executed via link:herbstclient.html[*herbstclient*(1)] or via keybindings. quit:: Quits herbstluftwm. reload:: Executes the autostart file. version:: Prints the version of the running herbstluftwm instance. echo ['ARGS' ...]:: Prints all given 'ARGS' separated by a single space and a newline afterwards. true:: Ignores all arguments and always returns success, i.e. 0. false:: Ignores all arguments and always returns failure, i.e. 1. list_commands:: Lists all available commands. [[list_monitors]]list_monitors:: List currently configured monitors with their index, area (as rectangle), name (if named) and currently viewed tag. list_rules:: Lists all active rules. Each line consists of all the parameters the rule was called with, plus its label, separated by tabs. list_keybinds:: Lists all bound keys with their associated command. Each line consists of one key combination and the command with its parameters separated by tabs. WARNING: Tabs within command parameters are not escaped! lock:: Increases the 'monitors_locked' setting. Use this if you want to do multiple window actions at once (i.e. without repainting between the single steps). See also: *unlock* unlock:: Decreases the 'monitors_locked' setting. If 'monitors_locked' is changed to 0, then all monitors are repainted again. See also: *lock* keybind 'KEY' 'COMMAND' ['ARGS ...']:: Adds a key binding. When 'KEY' is pressed, the internal 'COMMAND' (with its 'ARGS') is executed. A key binding is a (possibly empty) list of modifiers (Mod1, Mod2, Mod3, Mod4, Mod5, Alt, Super, Control/Ctrl, Shift) and one key (see keysymdef.h for a list of keys). Modifiers and the key are concatenated with '-' or '+' as separator. If there is already a binding for this 'KEY', it will be overwritten. Examples: * keybind Mod4+Ctrl+q quit * keybind Mod1-i toggle always_show_frame * keybind Mod1-Shift-space cycle_layout -1 keyunbind 'KEY'|*-F*|*--all*:: Removes the key binding for 'KEY'. The syntax for 'KEY' is defined in *keybind*. If *-F* or *--all* is given, then all key bindings will be removed. mousebind 'BUTTON' 'ACTION' ['COMMAND' ...]:: Adds a mouse binding for the floating mode. When 'BUTTON' is pressed, the specified 'ACTION' will be performed. 'BUTTON' has a similar syntax to the 'KEY' argument of keybind: It consists of a list of modifiers (separated by '-' or '+', valid modifiers are listed in the description of 'keybind') and exactly one button name: * +B1+ or +Button1+ * +B2+ or +Button2+ * +B3+ or +Button3+ * +B4+ or +Button4+ * +B5+ or +Button5+ :: 'ACTION' must be one of the following actions: * +move+: Moves the window by dragging the cursor. * +resize+: Resizes the window by dragging a corner. * +zoom+: Resizes the window into all four directions while keeping the center of the window constant. * +call+: Only calls the specified 'COMMAND' while +client.dragged+ links to the client on which the 'BUTTON' has been performed. :: While an 'ACTION' is performed, +client.dragged+ is the client which is dragged. E.g.: * +mousebind Mod1-Button3 zoom+ * +mousebind Mod1-B4 call substitute WID clients.dragged.winid spawn transset-df --inc -i WID 0.05+ * +mousebind Mod1-B5 call substitute WID clients.dragged.winid spawn transset-df --dec -i WID -m 0.2 0.05+ mouseunbind:: Removes all mouse bindings. spawn 'EXECUTABLE' ['ARGS ...']:: Spawns an 'EXECUTABLE' with its 'ARGS'. For details see 'man 3 execvp'. Example: * spawn xterm -e man 3 execvp wmexec ['WINDOWMANAGER' ['ARGS ...']]:: Executes the 'WINDOWMANAGER' with its 'ARGS'. This is useful to switch the window manager in the running session without restarting the session. If no or an invalid 'WINDOWMANAGER' is given, then herbstluftwm is restarted. For details see 'man 3 execvp'. Example: * wmexec openbox chain 'SEPARATOR' ['COMMANDS' ...]:: chain expects a 'SEPARATOR' and a list of 'COMMANDS' with arguments. The commands have to be separated by the specified 'SEPARATOR'. The 'SEPARATOR' can by any word and only is recognized as the separator between commands if it exactly matches 'SEPARATOR'. "chain" outputs the appended outputs of all commands and returns the exit code of the last executed command. Examples are: * Create a tag called "foo" and directly use it: + chain , add foo , use foo * Rotate the layout clockwise: + chain .-. lock .-. rotate .-. rotate .-. rotate .-. unlock :: Counterexamples are: * This will only create a tag called "foo,": + chain , add foo, use foo * Separator "." defined, but "," is used: + chain . add foo , use foo and 'SEPARATOR' ['COMMANDS' ...]:: "and" behaves like the chain command but only executes the specified 'COMMANDS' while the commands return the exit code 0. or 'SEPARATOR' ['COMMANDS' ...]:: "or" behaves like the chain command but only executes the specified 'COMMANDS' until one command returns the exit code 0. ! 'COMMAND':: "!" executes the provided command, but inverts its return value. If the provided command returns a nonzero, "!" returns a 0, if the command returns a zero, "!" returns a 1. try 'COMMAND':: "try" executes the provided command, prints its output, but always returns success, i.e. 0. silent 'COMMAND':: "silent" executes the provided command, but discards its output and only returns its exit code. focus_nth 'INDEX':: Focuses the nth window in a frame. The first window has 'INDEX' 0. If 'INDEX' is negative or greater than the last window index, then the last window is focused. cycle ['DELTA']:: Cycles the selection within the current frame by 'DELTA'. If 'DELTA' is omitted, 'DELTA' = 1 will be used. 'DELTA' can be negative; 'DELTA' = -1 means: cycle in the opposite direction by 1. cycle_all [*--skip-invisible*] ['DIRECTION']:: Cycles through all windows and frames on the current tag. 'DIRECTION' = 1 means forward, 'DIRECTION' = -1 means backward, 'DIRECTION' = 0 has no effect. 'DIRECTION' defaults to 1. If there are multiple windows within on frame, then it acts similar to the 'cycle' command. (The 'cycle_all' command focuses the next/previous leave in the 'layout' tree.). If *--skip-invisible* is given, then this only cycles through all visible windows and skips invisible windows in the max layout. The focused window is raised. cycle_frame ['DIRECTION']:: Cycles through all frames on the current tag. 'DIRECTION' = 1 means forward, 'DIRECTION' = -1 means backward, 'DIRECTION' = 0 has no effect. 'DIRECTION' defaults to 1. cycle_layout ['DELTA' ['LAYOUTS' ...]]:: Cycles the layout algorithm in the current frame by 'DELTA'. 'DELTA' defaults to 1. You can find a <> above. If a list of 'LAYOUTS' is given, cycle_layout will cycle through those instead of the default layout algorithm list. Each layout name should occur at most once. Example: * cycle_layout -1 * cycle_layout 1 vertical grid set_layout 'LAYOUT':: Sets the layout algorithm in the current frame to 'LAYOUT'. For the list of layouts, check the <>. close 'WINID':: Closes the specified window gracefully or the focused window if none is given explicitly. See the <> how to reference a certain window. close_or_remove:: 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 + * 'top' * 'bottom' (= 'vertical') * 'left', * 'right' (= 'horizontal') * 'explode' * 'auto' (split along longest side) + It specifies which of the two halves will be empty after the split. The other half will be occupied by the currently focused frame. After splitting, the originally focuse frame will stay focused. One special 'ALIGN' mode is 'explode', which splits the frame in such a way that the window sizes and positions are kept as much as possible. If no 'FRACTION' is given to 'explode' mode an optimal fraction is picked automatically. Example: * split explode * split bottom 0.5 * split horiz 0.3 * split vertical 0.5 * split h focus ['-i'|'-e'] 'DIRECTION':: Moves the focus from current frame to the next frame or client in 'DIRECTION' which is in: + * l[eft] * r[ight] * u[p] * d[own] // small hack to fix indentation of "If ..." :: If '-i' (internal) is given or default_direction_external_only is unset, then the next client in 'DIRECTION' can also be within the same frame. If there is no client within this frame or '-e' (external) is given, then the next frame in specified 'DIRECTION' 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 'DIRECTION'. From there the focus goes back to the leaf. + + Example: The focus is at frame A. After executing 'focus right' focus will 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 └─────┘└─────┘ ---- :: If the currently focused client is floated, then the next floating window in the specified direction is focused and raised. :: If 'focus_crosses_monitor_boundaries' is set and no client or frame is found in the specified 'DIRECTION', then the next monitor in that 'DIRECTION' is focused. focus_edge ['-i'|'-e'] 'DIRECTION':: Focuses the window on the edge of the tag in the specified 'DIRECTION'. The 'DIRECTIONS' and '-e' behave as specified at the 'focus' command. + + If '-i' (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. + raise 'WINID':: Raises the specified window. See the <> on how to reference a certain window. Its result is only visible in floating mode. TIP: The 'WINID' also can specify an unmanaged window, although the completion for the raise command does not list the IDs of unmanaged windows. jumpto 'WINID':: Puts the focus to the specified window. See the <> on how to reference a certain window. bring 'WINID':: Moves the specified window to the current frame and focuses it. See the <> on how to reference a certain window. resize 'DIRECTION' 'FRACTIONDELTA':: Changes the next fraction in specified 'DIRECTION' by 'FRACTIONDELTA'. 'DIRECTION' behaves as specified at the 'focus' command. You should not omit the sign '-' or '+', because in future versions, the behaviour may change if the sign is omitted. Example: * resize right +0.05 * resize down -0.1 shift_edge ['-i'|'-e'] 'DIRECTION':: Shifts the focused window to the the edge of a tag in the specified 'DIRECTION'. The 'DIRECTIONS' behave as specified at the 'focus' command and '-i' and '-e' behave as specified at the 'focus_edge' command. shift ['-i'|'-e'] 'DIRECTION':: Shifts the focused window to the next frame in the specified 'DIRECTION'. The 'DIRECTIONS' and '-i'|'-e' behave as specified at the 'focus' command. If the focused client is floated instead of being tiled, then client is shifted to the next window or screen edge. shift_to_monitor 'MONITOR':: Moves the focused window to the tag on the specified 'MONITOR'. remove:: Removes focused frame and merges its windows to its neighbour frame. rotate:: Rotates the layout on the focused tag counterclockwise by 90 degrees. This only manipulates the alignment of frames, not the content of them. set 'NAME' 'VALUE':: Sets the specified setting 'NAME' to 'VALUE'. All <> are listed in the <>. get 'NAME':: Prints the value of setting 'NAME'. All <> are listed in the <>. toggle 'NAME':: Toggles the setting 'NAME' if it's an integer setting: If its value is unequal to 0, it becomes 0; else its previous value (which was unequal to 0) is restored. cycle_value 'NAME' 'VALUES' ...:: Cycles value of the setting 'NAME' through 'VALUES': I.e. it searches the first occurrence of the current value in 'VALUES' and changes the value to the next in the list or to the first one if the end is reached or current value wasn't found. Example: * cycle_value frame_gap 0 5 10 15 * cycle_value frame_bg_normal_color red green blue cycle_monitor ['DELTA']:: Cycles monitor focused by 'DELTA'. 'DELTA' defaults to 1. focus_monitor 'MONITOR':: Puts focus to the specified monitor. add 'TAG':: Creates a new empty tag named 'TAG'. use 'TAG':: Switches the focused monitor to specified 'TAG'. use_index 'INDEX' [*--skip-visible*]:: Switches the focused monitor to the 'TAG' with the specified 'INDEX'. If 'INDEX' starts with +++ or +-+, then 'INDEX' is treated relative to the current 'TAG'. If *--skip-visible* is passed and 'INDEX' is relative, then tags that are already visible on a monitor are skipped. E.g. this cycles backwards through the tags: * use_index -1 --skip-visible use_previous:: Switches the focused monitor to the previously viewed tag. merge_tag 'TAG' ['TARGET']:: Removes tag named 'TAG' and moves all its windows to tag 'TARGET'. If 'TARGET' is omitted, the focused tag will be used. rename 'OLDTAG' 'NEWTAG':: Renames tag named 'OLDTAG' to 'NEWTAG'. move 'TAG':: Moves the focused window to the tag named 'TAG'. move_index 'INDEX' [*--skip-visible*]:: Moves the focused window to the tag specified by 'INDEX'. Analogical to the argument for *use_index*: If 'INDEX' starts with +++ or +-+, then it is treated relative. If *--skip-visible* is passed with a relative index, then already visible tags are skipped. lock_tag ['MONITOR']:: Lock the tag switching on the specified monitor. If no argument is given, the currently focused monitor is used. When the tag switching is disabled for a monitor, the commands *use* and *use_index* have no effect when executed there. When 'swap_monitors_to_get_tag' is enabled, switching to a tag which is located on a locked monitor, switches to that monitor instead of stealing it from there. The lock state of a monitor is indicated by "[LOCKED]" in the *list_monitors* output. unlock_tag ['MONITOR']:: Re-enables the tag switching on the specified monitor. If no argument is given, the currently focused monitor is used. This is the reverse operation to *lock_tag* and has no further side effects but removing this lock. disjoin_rects 'RECTS' ...:: Takes a list of rectangles and splits them into smaller pieces until all rectangles are disjoint, the result rectangles are printed line by line. This command does not modify the current list of monitors! So this can be useful in combination with the set_monitors command. * E.g. +disjoin_rects 600x400+0+0 600x400+300+250+ prints this: + ---- 300x150+300+250 600x250+0+0 300x150+0+250 300x150+600+250 600x250+300+400 ---- + * In the above example two monitors are split into 5 monitors, which graphically means: + ---- ┌──────┐ ┌──────┐ │ │ └──────┘ │ ┌───┼───┐ ┌─┐┌───┐┌──┐ │ │ │ │ disjoin │ ││ ││ │ └──┼───┘ │ ─────────> └─┘└───┘└──┘ │ │ ┌───────┐ └───────┘ └───────┘ ---- set_monitors 'RECTS' ...:: Sets the list of monitors *exactly* to the list of given rectangles: * The i'th existing monitor is moved to the i'th given 'RECT' * New monitors are created if there are more 'RECTS' then monitors * Existing monitors are deleted if there are more monitors then 'RECTS' detect_monitors '-l'|'--list'|'--no-disjoin':: Sets the list of monitors to the available Xinerama monitors. If the Xinerama extension is missing, it will fall back to one monitor across the entire screen. If the detected monitors overlap, the will be split into more monitors that are disjoint but cover the same area using +disjoin_rects+. + If '-l' or '--list' is passed, the list of rectangles of detected pyhsical monitors is printed. So +hc detect_monitors+ is equivalent to the bash command +hc set_monitors $(hc disjoin_rects $(hc detect_monitors -l))+. add_monitor 'RECT' ['TAG' ['NAME']]:: Adds a monitor on the specified rectangle 'RECT' and displays 'TAG' on it. 'TAG' currently must not be displayed on any other monitor. 'RECT' is a string of the form 'WxH±X±Y'. If no or an empty 'TAG' is given, then any free tag will be chosen. If a 'NAME' is given, you can reference to this monitor by its name instead of using an index. Example: * add_monitor 1024x768-20+0 mynewtag main remove_monitor 'MONITOR':: Removes the specified monitor. move_monitor 'MONITOR' 'RECT' ['PADUP' ['PADRIGHT' ['PADDOWN' ['PADLEFT']]]]:: Moves the specified monitor to rectangle 'RECT'. 'RECT' is defined as in 'add_monitor'. If no or an empty pad is given, it is not changed. raise_monitor ['MONITOR']:: Raises the specified monitor or the current one if 'MONITOR' is omitted. rename_monitor 'MONITOR' 'NAME':: (Re)names an already existing monitor. If 'NAME' is empty, it removes the monitor's name. stack:: Prints the stack of monitors with the visible tags and their layers as a tree. The order of the printed stack is top to bottom. The style is configured by the 'tree_style' setting. monitor_rect [[-p] 'MONITOR']:: Prints the rectangle of the specified monitor in the format: *X Y W H* + If no 'MONITOR' or 'cur' is given, then the current monitor is used. If '-p' is supplied, then the remaining rect without the pad around this monitor is printed. pad 'MONITOR' ['PADUP' ['PADRIGHT' ['PADDOWN' ['PADLEFT']]]]:: Sets the pad of specified monitor to the specified padding. If no or an empty padding is given, it is not changed. list_padding ['MONITOR']:: Lists the padding of the specified monitor, or the currently focused monitor if no monitor is given. layout ['TAG' ['INDEX']]:: Prints the layout of frame with 'INDEX' on 'TAG', in a nice tree style. Its style is defined by the 'tree_style' setting. If no 'TAG' is given, the current tag is used. If no 'INDEX' is given, the root frame is used. To specify 'INDEX' without specifying 'TAG' (i.e. use current tag), pass an empty string as 'TAG'. + An example output is: + ---- ╾─┐ horizontal 50% selection=1 ├─╼ vertical: 0xe00009 └─┐ vertical 50% selection=0 ├─╼ vertical: 0xa00009 [FOCUS] └─╼ vertical: 0x1000009 ---- dump ['TAG' ['INDEX']]:: Prints the same information as the 'layout' command but in a machine readable format. Its output can be read back with the 'load' command. + An example output (formatted afterwards) is: + ---- (split horizontal:0.500000:1 (clients vertical:0 0xe00009) (split vertical:0.500000:1 (clients vertical:0 0xa00009) (clients vertical:0 0x1000009))) ---- load ['TAG'] 'LAYOUT':: Loads a given 'LAYOUT' description to specified 'TAG' or current tag if no 'TAG' is given. CAUTION: 'LAYOUT' is exactly one parameter. If you are calling it manually from your shell or from a script, quote it properly! complete 'POSITION' ['COMMAND' 'ARGS ...']:: Prints the result of tab completion for the partial 'COMMAND' with optional 'ARGS'. You usually do not need this, because there is already tab completion for bash. Example: * complete 0 m + prints all commands beginning with m * complete 1 toggle fra + prints all settings beginning with fra that can be toggled complete_shell 'POSITION' ['COMMAND' 'ARGS ...']:: Behaves like *complete* with the following extras, useful for completion on posix shells: * Escape sequences are removed in 'COMMAND' and 'ARGS'. * A space is appended to each full completion result. * Special characters will be escaped in the output. emit_hook 'ARGS ...':: Emits a custom hook to all idling herbstclients. tag_status ['MONITOR']:: Print a tab separated list of all tags for the specified 'MONITOR' index. If no 'MONITOR' index is given, the focused monitor is used. Each tag name is prefixed with one char, which indicates its state: * *.* the tag is empty * *:* the tag is not empty * *+* the tag is viewed on the specified 'MONITOR', but this monitor is not focused. * *#* the tag is viewed on the specified 'MONITOR' and it is focused. * *-* the tag is viewed on a different 'MONITOR', but this monitor is not focused. * *%* the tag is viewed on a different 'MONITOR' and it is focused. * *!* the tag contains an urgent window WARNING: If you use a tab in one of the tag names, then tag_status is probably quite useless for you. floating [['TAG'] *on*|*off*|*toggle*|*status*]:: Changes the current tag to floating/tiling mode on specified 'TAG' or prints it current status. If no 'TAG' is given, the current tag is used. If no argument is given, floating mode is toggled. If *status* is given, then *on* or *off* is printed, depending of the floating state of 'TAG'. rule [\[--]'FLAG'|\[--]'LABEL'|\[--]'CONDITION'|\[--]'CONSEQUENCE' ...]:: Defines a rule which will be applied to all new clients. Its behaviour is described in the <>. unrule 'LABEL'|*--all*|*-F*:: Removes all rules named 'LABEL'. If --all or -F is passed, then all rules are removed. fullscreen [*on*|*off*|*toggle*]:: Sets or toggles the fullscreen state of the focused client. If no argument is given, fullscreen mode is toggled. pseudotile [*on*|*off*|*toggle*]:: Sets or toggles the pseudotile state of the focused client. If a client is pseudotiled, then in tiling mode the client is only moved but not resized - the client size will stay the floating size. The only reason to resize the client is to ensure that it fits into its tile. If no argument is given, pseudotile mode is toggled. object_tree ['PATH']:: Prints the tree of objects. If the object path 'PATH' is given, only the subtree starting at 'PATH' is printed. See the <> for more details. attr ['PATH' ['NEWVALUE']:: Prints the children and attributes of the given object addressed by 'PATH'. If 'PATH' is an attribute, then print the attribute value. If 'NEWVALUE' is given, assign 'NEWVALUE' to the attribute given by 'PATH'. See the <> for more details. get_attr 'ATTRIBUTE':: Print the value of the specified 'ATTRIBUTE' as described in the <>. set_attr 'ATTRIBUTE' 'NEWVALUE':: Assign 'NEWVALUE' to the specified 'ATTRIBUTE' as described in the <>. new_attr [*bool*|*color*|*int*|*string*|*uint*] 'PATH':: Creates a new attribute with the name and in the object specified by 'PATH'. Its type is specified by the first argument. The attribute name has to begin with +my_+. remove_attr 'PATH':: Removes the user defined attribute 'PATH'. substitute 'IDENTIFIER' 'ATTRIBUTE' 'COMMAND' ['ARGS' ...]:: Replaces all exact occurrences of 'IDENTIFIER' in 'COMMAND' and its 'ARGS' by the value of the 'ATTRIBUTE'. Note that the 'COMMAND' also is replaced by the attribute value if it equals 'IDENTIFIER'. The replaced command with its arguments then is executed. Example: * +substitute MYTITLE clients.focus.title echo MYTITLE+ + + Prints the title of the currently focused window. sprintf 'IDENTIFIER' 'FORMAT' ['ATTRIBUTES' ...] 'COMMAND' ['ARGS' ...]:: Replaces all exact occurrences of 'IDENTIFIER' in 'COMMAND' and its 'ARGS' by the string specified by 'FORMAT'. Each +%s+ in 'FORMAT' stands for the value of the next attribute in 'ATTRIBUTES', similar to the *printf*(1) command. The replaced command with its arguments then is executed. Examples: * +sprintf STR title=%s clients.focus.title echo STR+ + + Prints the title of the currently focused window prepended by +title=+. * +sprintf X tag=%s tags.focus.name rule once X+ + + Moves the next client that appears to the tag that is currently focused. * +sprintf X %s/%s tags.focus.index tags.count echo X+ + + Tells which tag is focused and how many tags there are * +sprintf l somelongstring echo l l l+ + + Prints +somelongstring+ three times, separated by spaces. mktemp [*bool*|*int*|*string*|*uint*] 'IDENTIFIER' 'COMMAND' ['ARGS' ...]:: Creates a temporary attribute with the given type and replaces all occurrences of 'IDENTIFIER' in 'COMMAND' and 'ARGS' by by the path of the temporary attribute. The replaced command with its arguments is executed then. The exit status of 'COMMAND' is returned. compare 'ATTRIBUTE' 'OPERATOR' 'VALUE':: Compares the value of 'ATTRIBUTE' with 'VALUE' using the comparation method 'OPERATOR'. If the comparation succeeds, it returns 0, else 1. The operators are: - *=*: __ATTRIBUTE__'s value equals 'VALUE' - *!=*: __ATTRIBUTE__'s value does not equal 'VALUE' - *le*: __ATTRIBUTE__'s value \<= 'VALUE' - *lt*: __ATTRIBUTE__'s value < 'VALUE' - *ge*: __ATTRIBUTE__'s value >= 'VALUE' - *gt*: __ATTRIBUTE__'s value > 'VALUE' :: The 'OPERATORs' *le*,*lt*,*ge*,*gt* can only be used if 'ATTRIBUTE' 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: + ---- substitute FC tags.focus.frame_count \ compare tags.focus.client_count gt FC ---- + It returns success if there are more clients on the focused tag than frames. getenv 'NAME':: Gets the value of the environment variable 'NAME'. setenv 'NAME' 'VALUE':: Set the value of the environment variable 'NAME' to 'VALUE'. unsetenv 'NAME':: Unsets the environment variable 'NAME'. [[SETTINGS]] SETTINGS -------- Settings configure the behaviour of herbstluftwm and can be controlled via the 'set', 'get' and 'toggle' commands. There are two types of settings: Strings and integer values. An integer value is set, if its value is 1 or another value unequal to 0. An integer value is unset, if its value is 0. frame_gap (Integer):: The gap between frames in the tiling mode. frame_padding (Integer):: The padding within a frame in the tiling mode, i.e. the space between the border of a frame and the windows within it. window_gap (Integer):: The gap between windows within one frame in the tiling mode. snap_distance (Integer):: If a client is dragged in floating mode, then it snaps to neighbour clients if the distance between them is smaller then snap_distance. snap_gap (Integer):: Specifies the remaining gap if a dragged client snaps to an edge in floating mode. If snap_gap is set to 0, no gap will remain. mouse_recenter_gap (Integer):: Specifies the gap around a monitor. If the monitor is selected and the mouse position would be restored into this gap, it is set to the center of the monitor. This is useful, when the monitor was left via mouse movement, but is reselected by keyboard. If the gap is 0 (default), the mouse is never recentered. frame_border_active_color (String/Color):: The border color of a focused frame. frame_border_normal_color (String/Color):: The border color of an unfocused frame. frame_border_inner_color (String/Color):: The color of the inner border of a frame. frame_bg_active_color (String/Color):: The fill color of a focused frame. frame_bg_normal_color (String/Color):: The fill color of an unfocused frame (It is only visible if always_show_frame is set). frame_bg_transparent (Integer):: If set, the background of frames are transparent. That means a rectangle is cut out frome the inner such that only the frame border and a stripe of width 'frame_transparent_width' can be seen. Use 'frame_active_opacity' and 'frame_normal_opacity' for real transparency. frame_transparent_width (Integer):: Specifies the width of the remaining frame colored with 'frame_bg_active_color' if 'frame_bg_transparent' is set. frame_border_width (Integer):: Border width of a frame. frame_border_inner_width (Integer):: The width of the inner border of a frame. Must be less than frame_border_width, since it does not add to the frame border width but is a part of it. focus_crosses_monitor_boundaries (Integer):: If set, the +focus+ command crosses monitor boundaries. If there is no client in the direction given to +focus+, then the monitor in the specified direction is focused. raise_on_focus (Integer):: If set, a window is raised if it is focused. The value of this setting is only used in floating mode. raise_on_focus_temporarily (Integer):: If set, a window is raised temporarily if it is focused on its tag. Temporarily in this case means that the window will return to its previous stacking position if another window is focused. raise_on_click (Integer):: If set, a window is raised if it is clicked. The value of this setting is only noticed in floating mode. window_border_width (Integer):: Border width of a window. window_border_inner_width (Integer):: The width of the inner border of a window. Must be less than window_border_width, since it does not add to the window border width but is a part of it. window_border_active_color (String/Color):: Border color of a focused window. window_border_normal_color (String/Color):: Border color of an unfocused window. window_border_urgent_color (String/Color):: Border color of an unfocused but urgent window. window_border_inner_color (String/Color):: Color of the inner border of a window. always_show_frame (Integer):: If set, all frames are displayed. If unset, only frames with focus or with windows in it are displayed. frame_active_opacity (Integer):: Focused frame opacity in percent. Requires a running compositing manager to take actual effect. frame_normal_opacity (Integer):: Unfocused frame opacity in percent. Requires a running compositing manager to take actual effect. default_frame_layout (Integer):: Index of the frame layout, which is used if a new frame is created (by split or on a new tag). For a list of valid indices and their meanings, check the <>. default_direction_external_only (Integer):: This setting controls the behaviour of focus and shift if no '-e' or '-i' argument is given. if set, then focus and shift changes the focused frame even if there are other clients in this frame in the specified 'DIRECTION'. Else, a client within current frame is selected if it is in the specified 'DIRECTION'. gapless_grid (Integer):: This setting affects the size of the last client in a frame that is arranged by grid layout. If set, then the last client always fills the gap within this frame. If unset, then the last client has the same size as all other clients in this frame. smart_frame_surroundings (Integer):: If set, frame borders and gaps will be removed when there's no ambiguity regarding the focused frame. smart_window_surroundings (Integer):: If set, window borders and gaps will be removed and minimal when there's no ambiguity regarding the focused window. This minimal window decoration can be configured by the +theme.minimal+ object. focus_follows_mouse (Integer):: 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. focus_stealing_prevention (Integer):: If set, only pagers and taskbars are allowed to change the focus. If unset, all applications can request a focus change. monitors_locked (Integer):: If greater than 0, then the clients on all monitors aren't moved or resized anymore. If it is set to 0, then the arranging of monitors is enabled again, and all monitors are rearranged if their content has changed in the meantime. You should not change this setting manually due to concurrency issues; use the commands *lock* and *unlock* instead. swap_monitors_to_get_tag (Integer):: If set: If you want to view a tag, that already is viewed on another monitor, then the monitor contents will be swapped and you see the wanted tag on the focused monitor. If not set, the other monitor is focused if it shows the desired tag. auto_detect_monitors (Integer):: If set, detect_monitors is automatically executed every time a monitor is connected, disconnected or resized. tree_style (String):: It contains the chars that are used to print a nice ascii tree. It must contain at least 8 characters. e.g. ++X|:#+*-.++ produces a tree like: + ---- X-.root #-. child 0 | #-* child 01 | +-* child 02 +-. child 1 : #-* child 10 : +-* child 01 ---- + Useful values for 'tree_style' are: +╾│ ├└╼─┐+ or +-| |'--.+ or +╾│ ├╰╼─╮+. wmname (String):: It controls the value of the +_NET_WM_NAME+ property on the root window, which specifies the name of the running window manager. The value of this setting is not updated if the actual +_NET_WM_NAME+ property on the root window is changed externally. Example usage: * cycle_value wmname herbstluftwm LG3D pseudotile_center_threshold (Int):: If greater than 0, it specifies the least distance between a centered pseudotile window and the border of the frame or tile it is assigned to. If this distance is lower than 'pseudotile_center_threshold', it is aligned to the top left of the client's tile. update_dragged_clients (Int):: If set, a client's window content is resized immediately during resizing it with the mouse. If unset, the client's content is resized after the mouse button are released. [[RULES]] RULES ----- Rules are used to change default properties for certain clients when they appear. Each rule matches against a certain subset of all clients and defines a set of properties for them (called __CONSEQUENCE__s). A rule can be defined with this command: +rule+ [\[--]'FLAG'|\[--]'LABEL'|\[--]'CONDITION'|\[--]'CONSEQUENCE' ...] Each rule consists of a list of __FLAG__s, __CONDITION__s, __CONSEQUENCE__s and, optionally, a 'LABEL'. (each of them can be optionally prefixed with two dashes (+--+) to provide a more *iptables*(8)-like feeling). Each rule can be given a custom label by specifying the 'LABEL' property: * +[--]label+='VALUE' If multiple labels are specified, the last one in the list will be applied. If no label is given, then the rule will be given an integer name that represents the index of the rule since the last 'unrule -F' command (which is triggered in the default autostart). TIP: Rule labels default to an incremental index. These default labels are unique, unless you assign a different rule a custom integer 'LABEL'. Default labels can be captured with the 'printlabel' flag. If a new client appears, herbstluftwm tries to apply each rule to this new client as follows: If each 'CONDITION' of this rule matches against this client, then every 'CONSEQUENCE' is executed. (If there are no conditions given, then this rule is executed for each client) Each 'CONDITION' consists of a 'property' name, an operator and a 'value'. Valid operators are: * +~+ matches if client's 'property' is matched by the regex 'value'. * +=+ matches if client's 'properly' string is equal to 'value'. Valid 'properties' are: +instance+:: the first entry in client's +WM_CLASS+. +class+:: the second entry in client's +WM_CLASS+. +title+:: client's window title. +pid+:: the client's process id (Warning: the pid is not available for every client. This only matches if the client sets _NET_WM_PID to the pid itself). +maxage+:: matches if the age of the rule measured in seconds does not exceed 'value'. This condition only can be used with the +=+ operator. If maxage already is exceeded (and never will match again), then this rule is removed. (With this you can build rules that only live for a certain time.) +windowtype+:: matches the _NET_WM_WINDOW_TYPE property of a window. +windowrole+:: matches the WM_WINDOW_ROLE property of a window if it is set by the window. Each 'CONSEQUENCE' consists of a 'NAME'='VALUE' pair. Valid 'NAMES' are: +tag+:: moves the client to tag 'VALUE'. +monitor+:: moves the client to the tag on monitor 'VALUE'. If the tag consequence was also specified, and switchtag is set for the client, move the client to that tag, then display that tag on monitor 'VALUE'. If the tag consequence was specified, but switchtag was not, ignore this consequence. +focus+:: decides whether the client gets the input focus on his tag. The default is *off*. 'VALUE' can be *on*, *off* or *toggle*. +switchtag+:: if +focus+ is activated and the client is put to a not focused tag, then +switchtag+ tells whether the client's tag will be shown or not. If the tag is shown on any monitor but is not focused, the client's tag only is brought to the current monitor if *swap_monitors_to_get_tag* is activated. 'VALUE' can be *on*, *off* or *toggle*. +manage+:: decides whether the client will be managed or not. The default is *on*. 'VALUE' can be *on*, *off* or *toggle*. +index+:: moves the window to a specified index in the tree. 'VALUE' is a <>. +pseudotile+:: sets the pseudotile state of the client. 'VALUE' can be *on*, *off* or *toggle*. +ewmhrequests+:: sets whether the window state (the fullscreen state and the demands attention flag) can be changed by the application via ewmh itself. This does not affect the initial fullscreen state requested by the window. 'VALUE' can be *on*, *off* or *toggle*, it defaults to *on*. +ewmhnotify+:: sets whether hlwm should let the client know about EMWH changes (currently only the fullscreen state). If this is set, applications do not change to their fullscreen-mode while still being fullscreen. 'VALUE' can be *on*, *off* or *toggle*, it defaults to *on*. +fullscreen+:: sets the fullscreen flag of the client. 'VALUE' can be *on*, *off* or *toggle*. +hook+:: emits the custom hook +rule+ 'VALUE' 'WINID' when this rule is triggered by a new window with the id 'WINID'. This consequence can be used multiple times, which will cause a hook to be emitted for each occurrence of a hook consequence. +keymask+:: Sets the keymask for an client. A keymask is an regular expression that is matched against the string represenation (see list_keybinds). If it matches the keybinding is active when this client is focused, otherwise it is disabled. The default keymask is an empty string (""), which does not disable any keybinding. A rule's behaviour can be configured by some special 'FLAGS': * +not+: negates the next 'CONDITION'. * +!+: same as +not+. * +once+: only apply this rule once (and delete it afterwards). * +printlabel+: prints the label of the newly created rule to stdout. * +prepend+: prepend the rule to the list of rules instead of appending it. So its consequences may be overwritten by already existing rules. Examples: * +rule --class=Netscape --tag=6 --focus=off+ + + Moves all Netscape instances to tag 6, but doesn't give focus to them. * +rule not class~.*[Tt]erm tag=2+ + + Moves all clients to tag 2, if their class does not end with +term+ or +Term+. * +rule class=Thunderbird index=/0+ + Insert all Thunderbird instances in the tree that has no focus and there in the first child. * +rule --windowtype=_NET_WM_WINDOW_TYPE_DIALOG --focus=on+ + Sets focus to new dialogs which set their +_NET_WM_WINDOW_TYPE+ correctly. [[WINDOW_IDS]] WINDOW IDS ---------- Several commands accept a window as reference, e.g. +close+. The syntax is as follows: - an empty string -- or missing argument -- references the currently focused window. - +urgent+ references some window that is urgent. - +0x+'HEXID' -- where 'HEXID' is some hexadecimal number -- references the window with hexadecimal X11 window id is 'HEXID'. - 'DECID' -- where 'DECID' is some decimal number -- references the window with the decimal X11 window id 'DECID'. [[OBJECTS]] OBJECTS ------- WARNING: The object tree is not stable yet, i.e. its interface may change until the next stable release. So check this documentation again after upgrading the next time. The object tree is a collection of objects with attributes similar to +/sys+ known from the Linux kernel. Many entities (like tags, monitors, clients, ...) have objects to access their attributes directly. The tree is printed by the +object_tree+ command and looks more or less as follows: ---- $ herbstclient object_tree ╾─┐ ├─┐ tags │ ├─┐ by-name │ │ ├─╼ 1 │ │ ... │ │ └─╼ 9 │ └─╼ focus ├─┐ clients │ ├─╼ 0x1400022 │ └─╼ focus └─┐ monitors ├─╼ by-name └─╼ focus ---- To print a subtree starting at a certain object, pass the 'PATH' of the object to +object_tree+. The object 'PATH' is the path using the separator +.+ (dot), e.g. +tags.by-name+: ---- $ herbstclient object_tree tags.by-name. ╾─┐ tags.by-name. ├─╼ 1 ├─╼ 2 ... └─╼ 9 ---- To query all attributes and children of a object, pass its 'PATH' to +attr+: ---- $ herbstclient attr tags. 2 children: by-name. focus. 1 attributes: .---- type | .-- writeable V V u - count = 9 $ herbstclient attr tags.focus. 0 children. 6 attributes: .---- type | .-- writeable V V s w name = "1" b w floating = false i - frame_count = 2 i - client_count = 1 i - curframe_windex = 0 i - curframe_wcount = 1 ---- This already gives an intuition of the output: +attr+ first lists the names of the child objects and then all attributes, telling for each attribute: - its type * +s+ for string * +i+ for integer * +b+ for boolean * +u+ for unsigned integer - if it is writeable by the user: +w+ if yes, +-+ else. - the name of the attribute - its current value (only quoted for strings) To get the unquoted value of a certain attribute, address the attribute using the same syntax as for object paths and pass it to +attr+ or +get_attr+: ---- $ herbstclient attr clients.focus.title herbstluftwm.txt = (~/dev/c/herbstluftwm/doc) - VIM $ herbstclient get_attr clients.focus.title herbstluftwm.txt = (~/dev/c/herbstluftwm/doc) - VIM ---- To change a writeable attribute value pass the new value to +attr+ or to +set_attr+: ---- $ herbstclient attr tags.focus.floating false $ herbstclient attr tags.focus.floating true $ herbstclient attr tags.focus.floating true $ herbstclient set_attr tags.focus.floating false $ herbstclient attr tags.focus.floating false ---- Just look around to get a feeling what is there. The detailed tree content is listed as follows: * +tags+: subtree for tags. + [format="csv",cols="m,"] |=========================== u - count , number of tags |=========================== ** 'index': the object of the tag with index 'index'. ** +by-name+ *** 'TAG': an object for each tag with the name 'TAG' + + [format="csv",cols="m,"] |=========================== s w name , name of the tag b w floating , if it is in floating mode i - index , index of this tag i - frame_count , number of frames i - client_count , number of clients on this tag i - curframe_windex , index of the focused client in the select frame i - curframe_wcount , number of clients in the selected frame |=========================== ** +focus+: the object of the focused tag * +clients+ ** 'WINID': a object for each client with its 'WINID' + + [format="csv",cols="m,"] |=========================== s - winid , its window id s - title , its window title s - tag , the tag it's currently on i - pid , the process id of it (-1 if unset) s - class , the class of it (second entry in WM_CLASS) s - instance , the instance of it (first entry in WM_CLASS) b w fullscreen , b w pseudotile , b w ewmhrequests , if ewmh requests are permitted for this client b w ewmhnotify , if the client is told about its state via ewmh b w urgent , its urgent state b w sizehints_tiling , if sizehints for this client should be respected in tiling mode b w sizehints_flaoting , if sizehints for this client should be respected in floating mode |=========================== ** +focus+: the object of the focused client, if any ** +dragged+: the object of a client which is dragged by the mouse, if any. See the documentation of the +mousebind+ command for examples. * +monitors+ + [format="csv",cols="m,"] |=========================== u - count , number of monitors |=========================== ** 'INDEX': a object for each monitor with its 'INDEX' + ** +by-name+ *** 'NAME': a object for each named monitor + + [format="csv",cols="m,"] |=========================== s - name , its name i - index , its index s - tag , the tag currently viewed on it b - lock_tag , |=========================== ** +focus+: the object of the focused monitor * +settings+ has an attribute for each setting. See <> for a list. * +theme+ has attributes to configure the window decorations. +theme+ and many of its child objects have the following attributes + [format="csv",cols="m,"] |=========================== i w border_width , the base width of the border i w padding_top , additional border width on the top i w padding_right , on the right i w padding_bottom , on the bottom i w padding_left , and on the left of the border c w color , the basic background color of the border i w inner_width , width of the border around the clients content c w inner_color , its color i w outer_width , width of an additional border close to the edge c w outer_color , its color c w background_color , color behind window contents visible on resize s w reset , Writing this resets all attributes to a default value |=========================== + ---- inner_color/inner_width ╻ outer_color/outer_width │ ╻ │ │ ┌────╴│╶─────────────────┷─────┐ ⎫ border_width │ │ color │ ⎬ + │ ┌──┷─────────────────────┐ │ ⎭ padding_top │ │====================....│ │ │ │== window content ==....│ │ │ │====================..╾──────── background_color │ │........................│ │ │ └────────────────────────┘ │ ⎱ border_width + └──────────────────────────────┘ ⎰ padding_bottom ---- + Setting an attribute of the +theme+ object just propagates the value to the respective attribute of the +tiling+ and the +floating+ object. ** +tiling+ configures the decoration of tiled clients, setting one of its attributes propagates the respective attribute of the +active+, +normal+ and +urgent+ child objects. *** +active+ configures the decoration of focused and tiled clients *** +normal+ configures the decoration of unfocused and tiled clients *** +urgent+ configures the decoration of urgent and tiled clients ** +floating+ behaves analogously to +tiling+ ** +minimal+ behaves analogously to +tiling+ and configures those minimal decorations triggered by +smart_window_surroundings+. ** +active+ propagates the attribute values to +tiling.active+ and +floating.active+ ** +normal+ propagates the attribute values to +tiling.normal+ and +floating.normal+ ** +urgent+ propagates the attribute values to +tiling.urgent+ and +floating.urgent+ [[AUTOSTART]] 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 *--autostart* argument, autostart file is located at '$XDG_CONFIG_HOME/herbstluftwm/autostart' or at '~/.config/herbstluftwm/autostart'. Normally it consists of a few *herbstclient* calls. If executing the autostart file in a user's home fails the global autostart file (mostly placed at /etc/xdg/herbstluftwm/autostart) is executed as a fallback. For a quick install, copy the default autostart file to '~/.config/herbstluftwm/'. HOOKS ----- On special events, herbstluftwm emits some hooks (with parameters). You can receive or wait for them with link:herbstclient.html[*herbstclient*(1)]. Also custom hooks can be emitted with the *emit_hook* command. The following hooks are emitted by herbstluftwm itself: fullscreen [on|off] 'WINID' 'STATE':: The fullscreen state of window 'WINID' was changed to [on|off]. tag_changed 'TAG' 'MONITOR':: The tag 'TAG' was selected on 'MONITOR'. focus_changed 'WINID' 'TITLE':: The window 'WINID' was focused. Its window title is 'TITLE'. window_title_changed 'WINID' 'TITLE':: The title of the *focused* window was changed. Its window id is 'WINID' and its new title is 'TITLE'. tag_flags:: The flags (i.e. urgent or filled state) have been changed. tag_added 'TAG':: A tag named 'TAG' was added. tag_removed 'TAG':: The tag named 'TAG' was removed. urgent [on|off] 'WINID':: The urgent state of client with given 'WINID' has been changed to [on|off]. rule 'NAME' 'WINID':: A window with the id 'WINID' appeared which triggerd a rule with the consequence hook='NAME'. There are also other useful hooks, which never will be emitted by herbstluftwm itself, but which can be emitted with the *emit_hook* command: quit_panel:: Tells a panel to quit. The default panel.sh quits on this hook. Many scripts are using this hook. reload:: Tells all daemons that the 'autostart' file is reloaded -- and tells them to quit. This hook *should* be emitted in the first line of every 'autostart' file. [[STACKING]] STACKING -------- Every tag has its own stack of clients that are on this tag. Similar to the EWMH specification each tag stack contains several layers, which are from top to bottom: * the focused client (if raise_on_focus_temporarily is enabled) * clients in fullscreen * normal clients * frame decorations 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 *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 'wmexec' fails. BUGS ---- See the *herbstluftwm* distribution BUGS file. COMMUNITY --------- Feel free to join the IRC channel '#herbstluftwm' on 'irc.freenode.net'. AUTHOR ------ *herbstluftwm* was written by Thorsten Wißmann. All contributors are listed in the *herbstluftwm* distribution AUTHORS file. RESOURCES --------- Homepage: Github page: Patch submission and bug reporting: hlwm@lists.herbstluftwm.org COPYING ------- Copyright 2011-2014 Thorsten Wißmann. All rights reserved. This software is licensed under the "Simplified BSD License". See LICENSE for details. // vim: tw=80 ft=asciidoc herbstluftwm-0.7.0/doc/herbstclient.txt0000644000175000001440000000557112607454114020041 0ustar thorstenusersherbstclient(1) =============== :doctype: manpage :man version: {herbstluftwmversion} NAME ---- herbstclient - sends commands to a running herbstluftwm instance via X SYNOPSIS -------- *herbstclient* ['OPTIONS'] 'COMMAND' ['ARGS ...'] *herbstclient* ['OPTIONS'] ['--wait'|'--idle'] ['FILTER ...'] DESCRIPTION ----------- Sends a 'COMMAND' with its (optional) arguments 'ARGS' to a running link:herbstluftwm.html[*herbstluftwm*(1)] instance via Xlib. If 'COMMAND' has an output, it is printed by *herbstclient*. If output does not end with a newline, then a newline is added to improve readability. See link:herbstluftwm.html[*herbstluftwm*(1)] for a list of available __COMMAND__s and their 'ARGS'. If '--wait' or '--idle' is passed, then it waits for hooks from *herbstluftwm*. The hook is printed, if it matches the optional 'FILTER'. __FILTER__s are regular expressions. For a list of available hooks see *herbstluftwm*(1). OPTIONS ------- *-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. *-w*, *--wait*:: Same as *--idle* but exit after first *--count* hooks. *-c*, *--count* 'COUNT':: Let *--wait* exit after 'COUNT' hooks were received and printed. The default of 'COUNT' is 1. *-q*, *--quiet*:: Do not print error messages if herbstclient cannot connect to the running herbstluftwm instance. *-v*, *--version*:: Print the herbstclient version. To get the herbstluftwm version, use *herbstclient version*. *-h*, *--help*:: Print the herbstclient usage with its command line options. ENVIRONMENT VARIABLES --------------------- DISPLAY:: Specifies the 'DISPLAY' to use, i.e. where *herbstluftwm*(1) is running. EXIT STATUS ----------- Returns the exit status of the 'COMMAND' execution in *herbstluftwm*(1) server. *0*:: Success. other:: Failure. See link:herbstluftwm.html[*herbstluftwm*(1)] for a list of error codes. BUGS ---- It waits endlessly for a response from *herbstluftwm* (there is no timeout yet). See the *herbstluftwm* distribution BUGS file. COMMUNITY --------- Feel free to join the IRC channel '#herbstluftwm' on 'irc.freenode.net'. AUTHOR ------ *herbstclient* was written by Thorsten Wißmann. All contributors are listed in the *herbstluftwm* distribution AUTHORS file. RESOURCES --------- Homepage: Github page: Patch submission and bug reporting: hlwm@lists.herbstluftwm.org COPYING ------- Copyright 2011-2014 Thorsten Wißmann. All rights reserved. This software is licensed under the "Simplified BSD License". See LICENSE for details. // vim: tw=80 ft=asciidoc herbstluftwm-0.7.0/doc/herbstclient.10000644000175000001440000001060212654701657017362 0ustar thorstenusers'\" t .\" Title: herbstclient .\" Author: [see the "AUTHOR" section] .\" Generator: DocBook XSL Stylesheets v1.79.1 .\" Date: 2016-02-04 .\" Manual: \ \& .\" Source: \ \& herbstluftwm 0.7.0\e \e(c179281\e) .\" Language: English .\" .TH "HERBSTCLIENT" "1" "2016\-02\-04" "\ \& herbstluftwm 0\&.7\&.0\e" "\ \&" .\" ----------------------------------------------------------------- .\" * Define some portability stuff .\" ----------------------------------------------------------------- .\" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ .\" http://bugs.debian.org/507673 .\" http://lists.gnu.org/archive/html/groff/2009-02/msg00013.html .\" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ .ie \n(.g .ds Aq \(aq .el .ds Aq ' .\" ----------------------------------------------------------------- .\" * set default formatting .\" ----------------------------------------------------------------- .\" disable hyphenation .nh .\" disable justification (adjust text to left margin only) .ad l .\" ----------------------------------------------------------------- .\" * MAIN CONTENT STARTS HERE * .\" ----------------------------------------------------------------- .SH "NAME" herbstclient \- sends commands to a running herbstluftwm instance via X .SH "SYNOPSIS" .sp \fBherbstclient\fR [\fIOPTIONS\fR] \fICOMMAND\fR [\fIARGS \&...\fR] .sp \fBherbstclient\fR [\fIOPTIONS\fR] [\fI\-\-wait\fR|\fI\-\-idle\fR] [\fIFILTER \&...\fR] .SH "DESCRIPTION" .sp Sends a \fICOMMAND\fR with its (optional) arguments \fIARGS\fR to a running \fBherbstluftwm\fR(1) instance via Xlib\&. If \fICOMMAND\fR has an output, it is printed by \fBherbstclient\fR\&. If output does not end with a newline, then a newline is added to improve readability\&. .sp See \fBherbstluftwm\fR(1) for a list of available \fICOMMAND\fRs and their \fIARGS\fR\&. .sp If \fI\-\-wait\fR or \fI\-\-idle\fR is passed, then it waits for hooks from \fBherbstluftwm\fR\&. The hook is printed, if it matches the optional \fIFILTER\fR\&. \fIFILTER\fRs are regular expressions\&. For a list of available hooks see \fBherbstluftwm\fR(1)\&. .SH "OPTIONS" .PP \fB\-n\fR, \fB\-\-no\-newline\fR .RS 4 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\&. .RE .PP \fB\-w\fR, \fB\-\-wait\fR .RS 4 Same as \fB\-\-idle\fR but exit after first \fB\-\-count\fR hooks\&. .RE .PP \fB\-c\fR, \fB\-\-count\fR \fICOUNT\fR .RS 4 Let \fB\-\-wait\fR exit after \fICOUNT\fR hooks were received and printed\&. The default of \fICOUNT\fR is 1\&. .RE .PP \fB\-q\fR, \fB\-\-quiet\fR .RS 4 Do not print error messages if herbstclient cannot connect to the running herbstluftwm instance\&. .RE .PP \fB\-v\fR, \fB\-\-version\fR .RS 4 Print the herbstclient version\&. To get the herbstluftwm version, use \fBherbstclient version\fR\&. .RE .PP \fB\-h\fR, \fB\-\-help\fR .RS 4 Print the herbstclient usage with its command line options\&. .RE .SH "ENVIRONMENT VARIABLES" .PP DISPLAY .RS 4 Specifies the \fIDISPLAY\fR to use, i\&.e\&. where \fBherbstluftwm\fR(1) is running\&. .RE .SH "EXIT STATUS" .sp Returns the exit status of the \fICOMMAND\fR execution in \fBherbstluftwm\fR(1) server\&. .PP \fB0\fR .RS 4 Success\&. .RE .PP other .RS 4 Failure\&. See \fBherbstluftwm\fR(1) for a list of error codes\&. .RE .SH "BUGS" .sp It waits endlessly for a response from \fBherbstluftwm\fR (there is no timeout yet)\&. .sp See the \fBherbstluftwm\fR distribution BUGS file\&. .SH "COMMUNITY" .sp Feel free to join the IRC channel \fI#herbstluftwm\fR on \fIirc\&.freenode\&.net\fR\&. .SH "AUTHOR" .sp \fBherbstclient\fR was written by Thorsten Wißmann\&. All contributors are listed in the \fBherbstluftwm\fR distribution AUTHORS file\&. .SH "RESOURCES" .sp Homepage: http://herbstluftwm\&.org .sp Github page: http://github\&.com/herbstluftwm/herbstluftwm .sp Patch submission and bug reporting: .sp .if n \{\ .RS 4 .\} .nf hlwm@lists\&.herbstluftwm\&.org .fi .if n \{\ .RE .\} .SH "COPYING" .sp Copyright 2011\-2014 Thorsten Wißmann\&. All rights reserved\&. .sp This software is licensed under the "Simplified BSD License"\&. See LICENSE for details\&. herbstluftwm-0.7.0/doc/herbstluftwm.html0000644000175000001440000033234412654701663020235 0ustar thorstenusers herbstluftwm(1)

SYNOPSIS

herbstluftwm [OPTION …]

DESCRIPTION

Starts the herbstluftwm window manager on DISPLAY. It also listens for calls from herbstclient(1) and executes them. The list of available COMMANDS is listed below.

OPTION can be:

-c, --autostart PATH

use PATH as autostart file instead of the one in $XDG_CONFIG_HOME

-v, --version

print version and exit

-l, --locked

Initially set the monitors_locked setting to 1

--verbose

print verbose information to stderr

This manual documents the scripting and configuration interface. For a more verbose introduction see herbstluftwm-tutorial(7).

TILING ALGORITHM

The basic tiling concept is that the layout is represented by a binary tree. On startup you see one big frame across the entire screen. A frame fulfills exactly one of the following conditions:

  1. Frame contains windows:
    It shows some clients and arranges them. The current layout algorithms are:

    • 0: vertical - clients are placed below each other

    • 1: horizontal - clients are placed next to each other

    • 2: max - all clients are maximized in this frame

    • 3: grid - clients are arranged in an almost quadratic grid

  2. Frame is split into subframes:
    It is split into exactly two subframes in a configurable fraction either in a vertical or horizontal way. So it produces two frames which fulfill the same conditions (new frames always are about to contain windows). If you split a frame that already contains windows, the windows are inherited by the first new child frame.

If a new window appears, it is put in the currently focused frame. Only the leaves of the frame tree can be focused.

A frame can be removed, it is then merged with its neighbour frame. Due to the layout structure of a binary tree, each frame (i.e. node in binary tree) has exactly one neighbour.

The analogy to a binary tree is explained the best way with a small example: On startup you have a simple binary tree, with one frame that can contain clients:

C

When splitting it (e.g. with the command split vertical 0.5) you will get this:

  V
 / \
C   C

You also can split the left frame horizontally and you will get:

    V
   / \
  H   C
 / \
C   C

If you change the focus to the client on the right and remove this frame, it will be merged with the left subtree and you will get:

  H
 / \
C   C

The layout command prints the current layout of all tags as a tree.

FRAME INDEX

The exact position of a frame in the layout tree may be described by its index which is just a string of characters. The lookup algorithm starts at the root frame and selects one of its two subtrees according to the each character in the index.

The characters are interpreted as follows:

  • 0: select the first subtree

  • 1: select the second subtree

  • .: select the subtree having the focus

  • /: select the subtree not having the focus

Thus an empty string refers to the root frame, and "00" refers to the first subtree of the first subtree of the root frame.

As a special case, the string "@" always refers to the currently focused frame.

TAGS

Tags are very similar to workspaces, virtual desktops or window groups. Each tag has one layout. There is a list of tags. You can add or remove tags dynamically.

MONITORS

Monitors in herbstluftwm are totally independent of the actual physical screens. This means you can for example split your screen in two virtual monitors to view two tags at once on a big screen.

Each monitor displays exactly one tag on a specified rectangle on the screen.

Each monitor may have a name, which can be set via add_monitor and rename_monitor. It can be unset with the rename_monitor command. A monitor name is an arbitrary non-empty string which must not start with +, - or any digit.

A monitor can be referenced in different ways:

  • by its absolute index as listed in the list_monitors command.

  • by its relative index: a + or - followed by a delta, e.g.: +3

  • by its relative position to the focused monitor. -l denotes the monitor left of the focused monitor, -r right of, -u above of, and -d below of, respectively.

  • by "" (an empty string) which represents the current monitor.

  • by its name.

COMMANDS

herbstluftwm is controlled by internal commands, which can be executed via herbstclient(1) or via keybindings.

quit

Quits herbstluftwm.

reload

Executes the autostart file.

version

Prints the version of the running herbstluftwm instance.

echo [ARGS …]

Prints all given ARGS separated by a single space and a newline afterwards.

true

Ignores all arguments and always returns success, i.e. 0.

false

Ignores all arguments and always returns failure, i.e. 1.

list_commands

Lists all available commands.

list_monitors

List currently configured monitors with their index, area (as rectangle), name (if named) and currently viewed tag.

list_rules

Lists all active rules. Each line consists of all the parameters the rule was called with, plus its label, separated by tabs.

list_keybinds

Lists all bound keys with their associated command. Each line consists of one key combination and the command with its parameters separated by tabs.

Warning
Tabs within command parameters are not escaped!
lock

Increases the monitors_locked setting. Use this if you want to do multiple window actions at once (i.e. without repainting between the single steps). See also: unlock

unlock

Decreases the monitors_locked setting. If monitors_locked is changed to 0, then all monitors are repainted again. See also: lock

keybind KEY COMMAND [ARGS …]

Adds a key binding. When KEY is pressed, the internal COMMAND (with its ARGS) is executed. A key binding is a (possibly empty) list of modifiers (Mod1, Mod2, Mod3, Mod4, Mod5, Alt, Super, Control/Ctrl, Shift) and one key (see keysymdef.h for a list of keys). Modifiers and the key are concatenated with - or + as separator. If there is already a binding for this KEY, it will be overwritten. Examples:

  • keybind Mod4+Ctrl+q quit

  • keybind Mod1-i toggle always_show_frame

  • keybind Mod1-Shift-space cycle_layout -1

keyunbind KEY|-F|--all

Removes the key binding for KEY. The syntax for KEY is defined in keybind. If -F or --all is given, then all key bindings will be removed.

mousebind BUTTON ACTION [COMMAND …]

Adds a mouse binding for the floating mode. When BUTTON is pressed, the specified ACTION will be performed. BUTTON has a similar syntax to the KEY argument of keybind: It consists of a list of modifiers (separated by - or +, valid modifiers are listed in the description of keybind) and exactly one button name:

  • B1 or Button1

  • B2 or Button2

  • B3 or Button3

  • B4 or Button4

  • B5 or Button5

ACTION must be one of the following actions:

  • move: Moves the window by dragging the cursor.

  • resize: Resizes the window by dragging a corner.

  • zoom: Resizes the window into all four directions while keeping the center of the window constant.

  • call: Only calls the specified COMMAND while client.dragged links to the client on which the BUTTON has been performed.

While an ACTION is performed, client.dragged is the client which is dragged. E.g.:

  • mousebind Mod1-Button3 zoom

  • mousebind Mod1-B4 call substitute WID clients.dragged.winid spawn transset-df --inc -i WID 0.05

  • mousebind Mod1-B5 call substitute WID clients.dragged.winid spawn transset-df --dec -i WID -m 0.2 0.05

mouseunbind

Removes all mouse bindings.

spawn EXECUTABLE [ARGS …]

Spawns an EXECUTABLE with its ARGS. For details see man 3 execvp. Example:

  • spawn xterm -e man 3 execvp

wmexec [WINDOWMANAGER [ARGS …]]

Executes the WINDOWMANAGER with its ARGS. This is useful to switch the window manager in the running session without restarting the session. If no or an invalid WINDOWMANAGER is given, then herbstluftwm is restarted. For details see man 3 execvp. Example:

  • wmexec openbox

chain SEPARATOR [COMMANDS …]

chain expects a SEPARATOR and a list of COMMANDS with arguments. The commands have to be separated by the specified SEPARATOR. The SEPARATOR can by any word and only is recognized as the separator between commands if it exactly matches SEPARATOR. "chain" outputs the appended outputs of all commands and returns the exit code of the last executed command. Examples are:

  • Create a tag called "foo" and directly use it:
    chain , add foo , use foo

  • Rotate the layout clockwise:
    chain .-. lock .-. rotate .-. rotate .-. rotate .-. unlock

Counterexamples are:

  • This will only create a tag called "foo,":
    chain , add foo, use foo

  • Separator "." defined, but "," is used:
    chain . add foo , use foo

and SEPARATOR [COMMANDS …]

"and" behaves like the chain command but only executes the specified COMMANDS while the commands return the exit code 0.

or SEPARATOR [COMMANDS …]

"or" behaves like the chain command but only executes the specified COMMANDS until one command returns the exit code 0.

! COMMAND

"!" executes the provided command, but inverts its return value. If the provided command returns a nonzero, "!" returns a 0, if the command returns a zero, "!" returns a 1.

try COMMAND

"try" executes the provided command, prints its output, but always returns success, i.e. 0.

silent COMMAND

"silent" executes the provided command, but discards its output and only returns its exit code.

focus_nth INDEX

Focuses the nth window in a frame. The first window has INDEX 0. If INDEX is negative or greater than the last window index, then the last window is focused.

cycle [DELTA]

Cycles the selection within the current frame by DELTA. If DELTA is omitted, DELTA = 1 will be used. DELTA can be negative; DELTA = -1 means: cycle in the opposite direction by 1.

cycle_all [--skip-invisible] [DIRECTION]

Cycles through all windows and frames on the current tag. DIRECTION = 1 means forward, DIRECTION = -1 means backward, DIRECTION = 0 has no effect. DIRECTION defaults to 1. If there are multiple windows within on frame, then it acts similar to the cycle command. (The cycle_all command focuses the next/previous leave in the layout tree.). If --skip-invisible is given, then this only cycles through all visible windows and skips invisible windows in the max layout. The focused window is raised.

cycle_frame [DIRECTION]

Cycles through all frames on the current tag. DIRECTION = 1 means forward, DIRECTION = -1 means backward, DIRECTION = 0 has no effect. DIRECTION defaults to 1.

cycle_layout [DELTA [LAYOUTS …]]

Cycles the layout algorithm in the current frame by DELTA. DELTA defaults to 1. You can find a list of layout algorithms above. If a list of LAYOUTS is given, cycle_layout will cycle through those instead of the default layout algorithm list. Each layout name should occur at most once. Example:

  • cycle_layout -1

  • cycle_layout 1 vertical grid

set_layout LAYOUT

Sets the layout algorithm in the current frame to LAYOUT. For the list of layouts, check the list of layout algorithms above.

close WINID

Closes the specified window gracefully or the focused window if none is given explicitly. See the section on WINDOW IDS how to reference a certain window.

close_or_remove

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

  • top

  • bottom (= vertical)

  • left,

  • right (= horizontal)

  • explode

  • auto (split along longest side)
    It specifies which of the two halves will be empty after the split. The other half will be occupied by the currently focused frame. After splitting, the originally focuse frame will stay focused. One special ALIGN mode is explode, which splits the frame in such a way that the window sizes and positions are kept as much as possible. If no FRACTION is given to explode mode an optimal fraction is picked automatically. Example:

  • split explode

  • split bottom 0.5

  • split horiz 0.3

  • split vertical 0.5

  • split h

focus [-i|-e] DIRECTION

Moves the focus from current frame to the next frame or client in DIRECTION which is in:

  • l[eft]

  • r[ight]

  • u[p]

  • d[own]

If -i (internal) is given or default_direction_external_only is unset, then the next client in DIRECTION can also be within the same frame. If there is no client within this frame or -e (external) is given, then the next frame in specified DIRECTION 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 DIRECTION. From there the focus goes back to the leaf.

Example: The focus is at frame A. After executing focus right focus will 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         └─────┘└─────┘

If the currently focused client is floated, then the next floating window in the specified direction is focused and raised.

If focus_crosses_monitor_boundaries is set and no client or frame is found in the specified DIRECTION, then the next monitor in that DIRECTION is focused.

focus_edge [-i|-e] DIRECTION

Focuses the window on the edge of the tag in the specified DIRECTION. The DIRECTIONS and -e behave as specified at the focus command.

If -i (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.

raise WINID

Raises the specified window. See the section on WINDOW IDS on how to reference a certain window. Its result is only visible in floating mode.

Tip
The WINID also can specify an unmanaged window, although the completion for the raise command does not list the IDs of unmanaged windows.
jumpto WINID

Puts the focus to the specified window. See the section on WINDOW IDS on how to reference a certain window.

bring WINID

Moves the specified window to the current frame and focuses it. See the section on WINDOW IDS on how to reference a certain window.

resize DIRECTION FRACTIONDELTA

Changes the next fraction in specified DIRECTION by FRACTIONDELTA. DIRECTION behaves as specified at the focus command. You should not omit the sign - or +, because in future versions, the behaviour may change if the sign is omitted. Example:

  • resize right +0.05

  • resize down -0.1

shift_edge [-i|-e] DIRECTION

Shifts the focused window to the the edge of a tag in the specified DIRECTION. The DIRECTIONS behave as specified at the focus command and -i and -e behave as specified at the focus_edge command.

shift [-i|-e] DIRECTION

Shifts the focused window to the next frame in the specified DIRECTION. The DIRECTIONS and -i|-e behave as specified at the focus command. If the focused client is floated instead of being tiled, then client is shifted to the next window or screen edge.

shift_to_monitor MONITOR

Moves the focused window to the tag on the specified MONITOR.

remove

Removes focused frame and merges its windows to its neighbour frame.

rotate

Rotates the layout on the focused tag counterclockwise by 90 degrees. This only manipulates the alignment of frames, not the content of them.

set NAME VALUE

Sets the specified setting NAME to VALUE. All SETTINGS are listed in the section below.

get NAME

Prints the value of setting NAME. All SETTINGS are listed in the section below.

toggle NAME

Toggles the setting NAME if it’s an integer setting: If its value is unequal to 0, it becomes 0; else its previous value (which was unequal to 0) is restored.

cycle_value NAME VALUES

Cycles value of the setting NAME through VALUES: I.e. it searches the first occurrence of the current value in VALUES and changes the value to the next in the list or to the first one if the end is reached or current value wasn’t found. Example:

  • cycle_value frame_gap 0 5 10 15

  • cycle_value frame_bg_normal_color red green blue

cycle_monitor [DELTA]

Cycles monitor focused by DELTA. DELTA defaults to 1.

focus_monitor MONITOR

Puts focus to the specified monitor.

add TAG

Creates a new empty tag named TAG.

use TAG

Switches the focused monitor to specified TAG.

use_index INDEX [--skip-visible]

Switches the focused monitor to the TAG with the specified INDEX. If INDEX starts with + or -, then INDEX is treated relative to the current TAG. If --skip-visible is passed and INDEX is relative, then tags that are already visible on a monitor are skipped. E.g. this cycles backwards through the tags:

  • use_index -1 --skip-visible

use_previous

Switches the focused monitor to the previously viewed tag.

merge_tag TAG [TARGET]

Removes tag named TAG and moves all its windows to tag TARGET. If TARGET is omitted, the focused tag will be used.

rename OLDTAG NEWTAG

Renames tag named OLDTAG to NEWTAG.

move TAG

Moves the focused window to the tag named TAG.

move_index INDEX [--skip-visible]

Moves the focused window to the tag specified by INDEX. Analogical to the argument for use_index: If INDEX starts with + or -, then it is treated relative. If --skip-visible is passed with a relative index, then already visible tags are skipped.

lock_tag [MONITOR]

Lock the tag switching on the specified monitor. If no argument is given, the currently focused monitor is used. When the tag switching is disabled for a monitor, the commands use and use_index have no effect when executed there. When swap_monitors_to_get_tag is enabled, switching to a tag which is located on a locked monitor, switches to that monitor instead of stealing it from there. The lock state of a monitor is indicated by "[LOCKED]" in the list_monitors output.

unlock_tag [MONITOR]

Re-enables the tag switching on the specified monitor. If no argument is given, the currently focused monitor is used. This is the reverse operation to lock_tag and has no further side effects but removing this lock.

disjoin_rects RECTS

Takes a list of rectangles and splits them into smaller pieces until all rectangles are disjoint, the result rectangles are printed line by line. This command does not modify the current list of monitors! So this can be useful in combination with the set_monitors command.

  • E.g. disjoin_rects 600x400+0+0 600x400+300+250 prints this:

    300x150+300+250
    600x250+0+0
    300x150+0+250
    300x150+600+250
    600x250+300+400
  • In the above example two monitors are split into 5 monitors, which graphically means:

    ┌──────┐                  ┌──────┐
    │      │                  └──────┘
    │  ┌───┼───┐              ┌─┐┌───┐┌──┐
    │  │   │   │   disjoin    │ ││   ││  │
    └──┼───┘   │  ─────────>  └─┘└───┘└──┘
       │       │                 ┌───────┐
       └───────┘                 └───────┘
set_monitors RECTS

Sets the list of monitors exactly to the list of given rectangles:

  • The i’th existing monitor is moved to the i’th given RECT

  • New monitors are created if there are more RECTS then monitors

  • Existing monitors are deleted if there are more monitors then RECTS

detect_monitors -l|--list|--no-disjoin

Sets the list of monitors to the available Xinerama monitors. If the Xinerama extension is missing, it will fall back to one monitor across the entire screen. If the detected monitors overlap, the will be split into more monitors that are disjoint but cover the same area using disjoin_rects.
If -l or --list is passed, the list of rectangles of detected pyhsical monitors is printed. So hc detect_monitors is equivalent to the bash command hc set_monitors $(hc disjoin_rects $(hc detect_monitors -l)).

add_monitor RECT [TAG [NAME]]

Adds a monitor on the specified rectangle RECT and displays TAG on it. TAG currently must not be displayed on any other monitor. RECT is a string of the form WxH±X±Y. If no or an empty TAG is given, then any free tag will be chosen. If a NAME is given, you can reference to this monitor by its name instead of using an index. Example:

  • add_monitor 1024x768-20+0 mynewtag main

remove_monitor MONITOR

Removes the specified monitor.

move_monitor MONITOR RECT [PADUP [PADRIGHT [PADDOWN [PADLEFT]]]]

Moves the specified monitor to rectangle RECT. RECT is defined as in add_monitor. If no or an empty pad is given, it is not changed.

raise_monitor [MONITOR]

Raises the specified monitor or the current one if MONITOR is omitted.

rename_monitor MONITOR NAME

(Re)names an already existing monitor. If NAME is empty, it removes the monitor’s name.

stack

Prints the stack of monitors with the visible tags and their layers as a tree. The order of the printed stack is top to bottom. The style is configured by the tree_style setting.

monitor_rect [[-p] MONITOR]

Prints the rectangle of the specified monitor in the format: X Y W H
If no MONITOR or cur is given, then the current monitor is used. If -p is supplied, then the remaining rect without the pad around this monitor is printed.

pad MONITOR [PADUP [PADRIGHT [PADDOWN [PADLEFT]]]]

Sets the pad of specified monitor to the specified padding. If no or an empty padding is given, it is not changed.

list_padding [MONITOR]

Lists the padding of the specified monitor, or the currently focused monitor if no monitor is given.

layout [TAG [INDEX]]

Prints the layout of frame with INDEX on TAG, in a nice tree style. Its style is defined by the tree_style setting. If no TAG is given, the current tag is used. If no INDEX is given, the root frame is used. To specify INDEX without specifying TAG (i.e. use current tag), pass an empty string as TAG.
An example output is:

╾─┐ horizontal 50% selection=1
  ├─╼ vertical: 0xe00009
  └─┐ vertical 50% selection=0
    ├─╼ vertical: 0xa00009 [FOCUS]
    └─╼ vertical: 0x1000009
dump [TAG [INDEX]]

Prints the same information as the layout command but in a machine readable format. Its output can be read back with the load command.
An example output (formatted afterwards) is:

(split horizontal:0.500000:1
    (clients vertical:0 0xe00009)
    (split vertical:0.500000:1
        (clients vertical:0 0xa00009)
        (clients vertical:0 0x1000009)))
load [TAG] LAYOUT

Loads a given LAYOUT description to specified TAG or current tag if no TAG is given.

Caution
LAYOUT is exactly one parameter. If you are calling it manually from your shell or from a script, quote it properly!
complete POSITION [COMMAND ARGS …]

Prints the result of tab completion for the partial COMMAND with optional ARGS. You usually do not need this, because there is already tab completion for bash. Example:

  • complete 0 m
    prints all commands beginning with m

  • complete 1 toggle fra
    prints all settings beginning with fra that can be toggled

complete_shell POSITION [COMMAND ARGS …]

Behaves like complete with the following extras, useful for completion on posix shells:

  • Escape sequences are removed in COMMAND and ARGS.

  • A space is appended to each full completion result.

  • Special characters will be escaped in the output.

emit_hook ARGS …

Emits a custom hook to all idling herbstclients.

tag_status [MONITOR]

Print a tab separated list of all tags for the specified MONITOR index. If no MONITOR index is given, the focused monitor is used. Each tag name is prefixed with one char, which indicates its state:

  • . the tag is empty

  • : the tag is not empty

  • + the tag is viewed on the specified MONITOR, but this monitor is not focused.

  • # the tag is viewed on the specified MONITOR and it is focused.

  • - the tag is viewed on a different MONITOR, but this monitor is not focused.

  • % the tag is viewed on a different MONITOR and it is focused.

  • ! the tag contains an urgent window

Warning
If you use a tab in one of the tag names, then tag_status is probably quite useless for you.
floating [[TAG] on|off|toggle|status]

Changes the current tag to floating/tiling mode on specified TAG or prints it current status. If no TAG is given, the current tag is used. If no argument is given, floating mode is toggled. If status is given, then on or off is printed, depending of the floating state of TAG.

rule [[--]FLAG|[--]LABEL|[--]CONDITION|[--]CONSEQUENCE …]

Defines a rule which will be applied to all new clients. Its behaviour is described in the RULES section.

unrule LABEL|--all|-F

Removes all rules named LABEL. If --all or -F is passed, then all rules are removed.

fullscreen [on|off|toggle]

Sets or toggles the fullscreen state of the focused client. If no argument is given, fullscreen mode is toggled.

pseudotile [on|off|toggle]

Sets or toggles the pseudotile state of the focused client. If a client is pseudotiled, then in tiling mode the client is only moved but not resized - the client size will stay the floating size. The only reason to resize the client is to ensure that it fits into its tile. If no argument is given, pseudotile mode is toggled.

object_tree [PATH]

Prints the tree of objects. If the object path PATH is given, only the subtree starting at PATH is printed. See the OBJECTS section for more details.

attr [PATH [NEWVALUE]

Prints the children and attributes of the given object addressed by PATH. If PATH is an attribute, then print the attribute value. If NEWVALUE is given, assign NEWVALUE to the attribute given by PATH. See the OBJECTS section for more details.

get_attr ATTRIBUTE

Print the value of the specified ATTRIBUTE as described in the OBJECTS section.

set_attr ATTRIBUTE NEWVALUE

Assign NEWVALUE to the specified ATTRIBUTE as described in the OBJECTS section.

new_attr [bool|color|int|string|uint] PATH

Creates a new attribute with the name and in the object specified by PATH. Its type is specified by the first argument. The attribute name has to begin with my_.

remove_attr PATH

Removes the user defined attribute PATH.

substitute IDENTIFIER ATTRIBUTE COMMAND [ARGS …]

Replaces all exact occurrences of IDENTIFIER in COMMAND and its ARGS by the value of the ATTRIBUTE. Note that the COMMAND also is replaced by the attribute value if it equals IDENTIFIER. The replaced command with its arguments then is executed. Example:

  • substitute MYTITLE clients.focus.title echo MYTITLE

    Prints the title of the currently focused window.

sprintf IDENTIFIER FORMAT [ATTRIBUTES …] COMMAND [ARGS …]

Replaces all exact occurrences of IDENTIFIER in COMMAND and its ARGS by the string specified by FORMAT. Each %s in FORMAT stands for the value of the next attribute in ATTRIBUTES, similar to the printf(1) command. The replaced command with its arguments then is executed. Examples:

  • sprintf STR title=%s clients.focus.title echo STR

    Prints the title of the currently focused window prepended by title=.

  • sprintf X tag=%s tags.focus.name rule once X

    Moves the next client that appears to the tag that is currently focused.

  • sprintf X %s/%s tags.focus.index tags.count echo X

    Tells which tag is focused and how many tags there are

  • sprintf l somelongstring echo l l l

    Prints somelongstring three times, separated by spaces.

mktemp [bool|int|string|uint] IDENTIFIER COMMAND [ARGS …]

Creates a temporary attribute with the given type and replaces all occurrences of IDENTIFIER in COMMAND and ARGS by by the path of the temporary attribute. The replaced command with its arguments is executed then. The exit status of COMMAND is returned.

compare ATTRIBUTE OPERATOR VALUE

Compares the value of ATTRIBUTE with VALUE using the comparation method OPERATOR. If the comparation succeeds, it returns 0, else 1. The operators are:

  • =: ATTRIBUTE's value equals VALUE

  • !=: ATTRIBUTE's value does not equal VALUE

  • le: ATTRIBUTE's value <= VALUE

  • lt: ATTRIBUTE's value < VALUE

  • ge: ATTRIBUTE's value >= VALUE

  • gt: ATTRIBUTE's value > VALUE

The OPERATORs le,lt,ge,gt can only be used if ATTRIBUTE 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:

substitute FC tags.focus.frame_count \
    compare tags.focus.client_count gt FC

It returns success if there are more clients on the focused tag than frames.

getenv NAME

Gets the value of the environment variable NAME.

setenv NAME VALUE

Set the value of the environment variable NAME to VALUE.

unsetenv NAME

Unsets the environment variable NAME.

SETTINGS

Settings configure the behaviour of herbstluftwm and can be controlled via the set, get and toggle commands. There are two types of settings: Strings and integer values. An integer value is set, if its value is 1 or another value unequal to 0. An integer value is unset, if its value is 0.

frame_gap (Integer)

The gap between frames in the tiling mode.

frame_padding (Integer)

The padding within a frame in the tiling mode, i.e. the space between the border of a frame and the windows within it.

window_gap (Integer)

The gap between windows within one frame in the tiling mode.

snap_distance (Integer)

If a client is dragged in floating mode, then it snaps to neighbour clients if the distance between them is smaller then snap_distance.

snap_gap (Integer)

Specifies the remaining gap if a dragged client snaps to an edge in floating mode. If snap_gap is set to 0, no gap will remain.

mouse_recenter_gap (Integer)

Specifies the gap around a monitor. If the monitor is selected and the mouse position would be restored into this gap, it is set to the center of the monitor. This is useful, when the monitor was left via mouse movement, but is reselected by keyboard. If the gap is 0 (default), the mouse is never recentered.

frame_border_active_color (String/Color)

The border color of a focused frame.

frame_border_normal_color (String/Color)

The border color of an unfocused frame.

frame_border_inner_color (String/Color)

The color of the inner border of a frame.

frame_bg_active_color (String/Color)

The fill color of a focused frame.

frame_bg_normal_color (String/Color)

The fill color of an unfocused frame (It is only visible if always_show_frame is set).

frame_bg_transparent (Integer)

If set, the background of frames are transparent. That means a rectangle is cut out frome the inner such that only the frame border and a stripe of width frame_transparent_width can be seen. Use frame_active_opacity and frame_normal_opacity for real transparency.

frame_transparent_width (Integer)

Specifies the width of the remaining frame colored with frame_bg_active_color if frame_bg_transparent is set.

frame_border_width (Integer)

Border width of a frame.

frame_border_inner_width (Integer)

The width of the inner border of a frame. Must be less than frame_border_width, since it does not add to the frame border width but is a part of it.

focus_crosses_monitor_boundaries (Integer)

If set, the focus command crosses monitor boundaries. If there is no client in the direction given to focus, then the monitor in the specified direction is focused.

raise_on_focus (Integer)

If set, a window is raised if it is focused. The value of this setting is only used in floating mode.

raise_on_focus_temporarily (Integer)

If set, a window is raised temporarily if it is focused on its tag. Temporarily in this case means that the window will return to its previous stacking position if another window is focused.

raise_on_click (Integer)

If set, a window is raised if it is clicked. The value of this setting is only noticed in floating mode.

window_border_width (Integer)

Border width of a window.

window_border_inner_width (Integer)

The width of the inner border of a window. Must be less than window_border_width, since it does not add to the window border width but is a part of it.

window_border_active_color (String/Color)

Border color of a focused window.

window_border_normal_color (String/Color)

Border color of an unfocused window.

window_border_urgent_color (String/Color)

Border color of an unfocused but urgent window.

window_border_inner_color (String/Color)

Color of the inner border of a window.

always_show_frame (Integer)

If set, all frames are displayed. If unset, only frames with focus or with windows in it are displayed.

frame_active_opacity (Integer)

Focused frame opacity in percent. Requires a running compositing manager to take actual effect.

frame_normal_opacity (Integer)

Unfocused frame opacity in percent. Requires a running compositing manager to take actual effect.

default_frame_layout (Integer)

Index of the frame layout, which is used if a new frame is created (by split or on a new tag). For a list of valid indices and their meanings, check the list of layout algorithms above.

default_direction_external_only (Integer)

This setting controls the behaviour of focus and shift if no -e or -i argument is given. if set, then focus and shift changes the focused frame even if there are other clients in this frame in the specified DIRECTION. Else, a client within current frame is selected if it is in the specified DIRECTION.

gapless_grid (Integer)

This setting affects the size of the last client in a frame that is arranged by grid layout. If set, then the last client always fills the gap within this frame. If unset, then the last client has the same size as all other clients in this frame.

smart_frame_surroundings (Integer)

If set, frame borders and gaps will be removed when there’s no ambiguity regarding the focused frame.

smart_window_surroundings (Integer)

If set, window borders and gaps will be removed and minimal when there’s no ambiguity regarding the focused window. This minimal window decoration can be configured by the theme.minimal object.

focus_follows_mouse (Integer)

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.

focus_stealing_prevention (Integer)

If set, only pagers and taskbars are allowed to change the focus. If unset, all applications can request a focus change.

monitors_locked (Integer)

If greater than 0, then the clients on all monitors aren’t moved or resized anymore. If it is set to 0, then the arranging of monitors is enabled again, and all monitors are rearranged if their content has changed in the meantime. You should not change this setting manually due to concurrency issues; use the commands lock and unlock instead.

swap_monitors_to_get_tag (Integer)

If set: If you want to view a tag, that already is viewed on another monitor, then the monitor contents will be swapped and you see the wanted tag on the focused monitor. If not set, the other monitor is focused if it shows the desired tag.

auto_detect_monitors (Integer)

If set, detect_monitors is automatically executed every time a monitor is connected, disconnected or resized.

tree_style (String)

It contains the chars that are used to print a nice ascii tree. It must contain at least 8 characters. e.g. X|:#+*-. produces a tree like:

X-.root
  #-. child 0
  | #-* child 01
  | +-* child 02
  +-. child 1
  : #-* child 10
  : +-* child 01

Useful values for tree_style are: ╾│ ├└╼─┐ or -| |'--. or ╾│ ├╰╼─╮.

wmname (String)

It controls the value of the _NET_WM_NAME property on the root window, which specifies the name of the running window manager. The value of this setting is not updated if the actual _NET_WM_NAME property on the root window is changed externally. Example usage:

  • cycle_value wmname herbstluftwm LG3D

pseudotile_center_threshold (Int)

If greater than 0, it specifies the least distance between a centered pseudotile window and the border of the frame or tile it is assigned to. If this distance is lower than pseudotile_center_threshold, it is aligned to the top left of the client’s tile.

update_dragged_clients (Int)

If set, a client’s window content is resized immediately during resizing it with the mouse. If unset, the client’s content is resized after the mouse button are released.

RULES

Rules are used to change default properties for certain clients when they appear. Each rule matches against a certain subset of all clients and defines a set of properties for them (called CONSEQUENCEs). A rule can be defined with this command:

rule [[--]FLAG|[--]LABEL|[--]CONDITION|[--]CONSEQUENCE …]

Each rule consists of a list of FLAGs, CONDITIONs, CONSEQUENCEs and, optionally, a LABEL. (each of them can be optionally prefixed with two dashes (--) to provide a more iptables(8)-like feeling).

Each rule can be given a custom label by specifying the LABEL property:

  • [--]label=VALUE

If multiple labels are specified, the last one in the list will be applied. If no label is given, then the rule will be given an integer name that represents the index of the rule since the last unrule -F command (which is triggered in the default autostart).

Tip
Rule labels default to an incremental index. These default labels are unique, unless you assign a different rule a custom integer LABEL. Default labels can be captured with the printlabel flag.

If a new client appears, herbstluftwm tries to apply each rule to this new client as follows: If each CONDITION of this rule matches against this client, then every CONSEQUENCE is executed. (If there are no conditions given, then this rule is executed for each client)

Each CONDITION consists of a property name, an operator and a value. Valid operators are:

  • ~ matches if client’s property is matched by the regex value.

  • = matches if client’s properly string is equal to value.

Valid properties are:

instance

the first entry in client’s WM_CLASS.

class

the second entry in client’s WM_CLASS.

title

client’s window title.

pid

the client’s process id (Warning: the pid is not available for every client. This only matches if the client sets _NET_WM_PID to the pid itself).

maxage

matches if the age of the rule measured in seconds does not exceed value. This condition only can be used with the = operator. If maxage already is exceeded (and never will match again), then this rule is removed. (With this you can build rules that only live for a certain time.)

windowtype

matches the _NET_WM_WINDOW_TYPE property of a window.

windowrole

matches the WM_WINDOW_ROLE property of a window if it is set by the window.

Each CONSEQUENCE consists of a NAME=VALUE pair. Valid NAMES are:

tag

moves the client to tag VALUE.

monitor

moves the client to the tag on monitor VALUE. If the tag consequence was also specified, and switchtag is set for the client, move the client to that tag, then display that tag on monitor VALUE. If the tag consequence was specified, but switchtag was not, ignore this consequence.

focus

decides whether the client gets the input focus on his tag. The default is off. VALUE can be on, off or toggle.

switchtag

if focus is activated and the client is put to a not focused tag, then switchtag tells whether the client’s tag will be shown or not. If the tag is shown on any monitor but is not focused, the client’s tag only is brought to the current monitor if swap_monitors_to_get_tag is activated. VALUE can be on, off or toggle.

manage

decides whether the client will be managed or not. The default is on. VALUE can be on, off or toggle.

index

moves the window to a specified index in the tree. VALUE is a frame index.

pseudotile

sets the pseudotile state of the client. VALUE can be on, off or toggle.

ewmhrequests

sets whether the window state (the fullscreen state and the demands attention flag) can be changed by the application via ewmh itself. This does not affect the initial fullscreen state requested by the window. VALUE can be on, off or toggle, it defaults to on.

ewmhnotify

sets whether hlwm should let the client know about EMWH changes (currently only the fullscreen state). If this is set, applications do not change to their fullscreen-mode while still being fullscreen. VALUE can be on, off or toggle, it defaults to on.

fullscreen

sets the fullscreen flag of the client. VALUE can be on, off or toggle.

hook

emits the custom hook rule VALUE WINID when this rule is triggered by a new window with the id WINID. This consequence can be used multiple times, which will cause a hook to be emitted for each occurrence of a hook consequence.

keymask

Sets the keymask for an client. A keymask is an regular expression that is matched against the string represenation (see list_keybinds). If it matches the keybinding is active when this client is focused, otherwise it is disabled. The default keymask is an empty string (""), which does not disable any keybinding.

A rule’s behaviour can be configured by some special FLAGS:

  • not: negates the next CONDITION.

  • !: same as not.

  • once: only apply this rule once (and delete it afterwards).

  • printlabel: prints the label of the newly created rule to stdout.

  • prepend: prepend the rule to the list of rules instead of appending it. So its consequences may be overwritten by already existing rules.

Examples:

  • rule --class=Netscape --tag=6 --focus=off

    Moves all Netscape instances to tag 6, but doesn’t give focus to them.

  • rule not class~.*[Tt]erm tag=2

    Moves all clients to tag 2, if their class does not end with term or Term.

  • rule class=Thunderbird index=/0
    Insert all Thunderbird instances in the tree that has no focus and there in the first child.

  • rule --windowtype=_NET_WM_WINDOW_TYPE_DIALOG --focus=on
    Sets focus to new dialogs which set their _NET_WM_WINDOW_TYPE correctly.

WINDOW IDS

Several commands accept a window as reference, e.g. close. The syntax is as follows:

  • an empty string — or missing argument — references the currently focused window.

  • urgent references some window that is urgent.

  • 0xHEXID — where HEXID is some hexadecimal number — references the window with hexadecimal X11 window id is HEXID.

  • DECID — where DECID is some decimal number — references the window with the decimal X11 window id DECID.

OBJECTS

Warning
The object tree is not stable yet, i.e. its interface may change until the next stable release. So check this documentation again after upgrading the next time.

The object tree is a collection of objects with attributes similar to /sys known from the Linux kernel. Many entities (like tags, monitors, clients, …) have objects to access their attributes directly. The tree is printed by the object_tree command and looks more or less as follows:

$ herbstclient object_tree
╾─┐
  ├─┐ tags
  │ ├─┐ by-name
  │ │ ├─╼ 1
  │ │ ...
  │ │ └─╼ 9
  │ └─╼ focus
  ├─┐ clients
  │ ├─╼ 0x1400022
  │ └─╼ focus
  └─┐ monitors
    ├─╼ by-name
    └─╼ focus

To print a subtree starting at a certain object, pass the PATH of the object to object_tree. The object PATH is the path using the separator . (dot), e.g. tags.by-name:

$ herbstclient object_tree tags.by-name.
╾─┐ tags.by-name.
  ├─╼ 1
  ├─╼ 2
  ...
  └─╼ 9

To query all attributes and children of a object, pass its PATH to attr:

$ herbstclient attr tags.
2 children:
  by-name.
  focus.

1 attributes:
 .---- type
 | .-- writeable
 V V
 u - count                = 9

$ herbstclient attr tags.focus.
0 children.
6 attributes:
 .---- type
 | .-- writeable
 V V
 s w name                 = "1"
 b w floating             = false
 i - frame_count          = 2
 i - client_count         = 1
 i - curframe_windex      = 0
 i - curframe_wcount      = 1

This already gives an intuition of the output: attr first lists the names of the child objects and then all attributes, telling for each attribute:

  • its type

    • s for string

    • i for integer

    • b for boolean

    • u for unsigned integer

  • if it is writeable by the user: w if yes, - else.

  • the name of the attribute

  • its current value (only quoted for strings)

To get the unquoted value of a certain attribute, address the attribute using the same syntax as for object paths and pass it to attr or get_attr:

$ herbstclient attr clients.focus.title
herbstluftwm.txt = (~/dev/c/herbstluftwm/doc) - VIM
$ herbstclient get_attr  clients.focus.title
herbstluftwm.txt = (~/dev/c/herbstluftwm/doc) - VIM

To change a writeable attribute value pass the new value to attr or to set_attr:

$ herbstclient attr tags.focus.floating
false
$ herbstclient attr tags.focus.floating true
$ herbstclient attr tags.focus.floating
true
$ herbstclient set_attr tags.focus.floating false
$ herbstclient attr tags.focus.floating
false

Just look around to get a feeling what is there. The detailed tree content is listed as follows:

  • tags: subtree for tags.

    u - count

    number of tags

    • index: the object of the tag with index index.

    • by-name

      • TAG: an object for each tag with the name TAG

        s w name

        name of the tag

        b w floating

        if it is in floating mode

        i - index

        index of this tag

        i - frame_count

        number of frames

        i - client_count

        number of clients on this tag

        i - curframe_windex

        index of the focused client in the select frame

        i - curframe_wcount

        number of clients in the selected frame

    • focus: the object of the focused tag

  • clients

    • WINID: a object for each client with its WINID

      s - winid

      its window id

      s - title

      its window title

      s - tag

      the tag it’s currently on

      i - pid

      the process id of it (-1 if unset)

      s - class

      the class of it (second entry in WM_CLASS)

      s - instance

      the instance of it (first entry in WM_CLASS)

      b w fullscreen

      b w pseudotile

      b w ewmhrequests

      if ewmh requests are permitted for this client

      b w ewmhnotify

      if the client is told about its state via ewmh

      b w urgent

      its urgent state

      b w sizehints_tiling

      if sizehints for this client should be respected in tiling mode

      b w sizehints_flaoting

      if sizehints for this client should be respected in floating mode

    • focus: the object of the focused client, if any

    • dragged: the object of a client which is dragged by the mouse, if any. See the documentation of the mousebind command for examples.

  • monitors

    u - count

    number of monitors

    • INDEX: a object for each monitor with its INDEX

    • by-name

      • NAME: a object for each named monitor

        s - name

        its name

        i - index

        its index

        s - tag

        the tag currently viewed on it

        b - lock_tag

    • focus: the object of the focused monitor

  • settings has an attribute for each setting. See SETTINGS for a list.

  • theme has attributes to configure the window decorations. theme and many of its child objects have the following attributes

    i w border_width

    the base width of the border

    i w padding_top

    additional border width on the top

    i w padding_right

    on the right

    i w padding_bottom

    on the bottom

    i w padding_left

    and on the left of the border

    c w color

    the basic background color of the border

    i w inner_width

    width of the border around the clients content

    c w inner_color

    its color

    i w outer_width

    width of an additional border close to the edge

    c w outer_color

    its color

    c w background_color

    color behind window contents visible on resize

    s w reset

    Writing this resets all attributes to a default value

    inner_color/inner_width
          ╻        outer_color/outer_width
          │                  ╻
          │                  │
    ┌────╴│╶─────────────────┷─────┐ ⎫ border_width
    │     │      color             │ ⎬     +
    │  ┌──┷─────────────────────┐  │ ⎭ padding_top
    │  │====================....│  │
    │  │== window content ==....│  │
    │  │====================..╾──────── background_color
    │  │........................│  │
    │  └────────────────────────┘  │ ⎱ border_width +
    └──────────────────────────────┘ ⎰ padding_bottom

    Setting an attribute of the theme object just propagates the value to the respective attribute of the tiling and the floating object.

    • tiling configures the decoration of tiled clients, setting one of its attributes propagates the respective attribute of the active, normal and urgent child objects.

      • active configures the decoration of focused and tiled clients

      • normal configures the decoration of unfocused and tiled clients

      • urgent configures the decoration of urgent and tiled clients

    • floating behaves analogously to tiling

    • minimal behaves analogously to tiling and configures those minimal decorations triggered by smart_window_surroundings.

    • active propagates the attribute values to tiling.active and floating.active

    • normal propagates the attribute values to tiling.normal and floating.normal

    • urgent propagates the attribute values to tiling.urgent and floating.urgent

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 --autostart argument, autostart file is located at $XDG_CONFIG_HOME/herbstluftwm/autostart or at ~/.config/herbstluftwm/autostart. Normally it consists of a few herbstclient calls. If executing the autostart file in a user’s home fails the global autostart file (mostly placed at /etc/xdg/herbstluftwm/autostart) is executed as a fallback.

For a quick install, copy the default autostart file to ~/.config/herbstluftwm/.

HOOKS

On special events, herbstluftwm emits some hooks (with parameters). You can receive or wait for them with herbstclient(1). Also custom hooks can be emitted with the emit_hook command. The following hooks are emitted by herbstluftwm itself:

fullscreen [on|off] WINID STATE

The fullscreen state of window WINID was changed to [on|off].

tag_changed TAG MONITOR

The tag TAG was selected on MONITOR.

focus_changed WINID TITLE

The window WINID was focused. Its window title is TITLE.

window_title_changed WINID TITLE

The title of the focused window was changed. Its window id is WINID and its new title is TITLE.

tag_flags

The flags (i.e. urgent or filled state) have been changed.

tag_added TAG

A tag named TAG was added.

tag_removed TAG

The tag named TAG was removed.

urgent [on|off] WINID

The urgent state of client with given WINID has been changed to [on|off].

rule NAME WINID

A window with the id WINID appeared which triggerd a rule with the consequence hook=NAME.

There are also other useful hooks, which never will be emitted by herbstluftwm itself, but which can be emitted with the emit_hook command:

quit_panel

Tells a panel to quit. The default panel.sh quits on this hook. Many scripts are using this hook.

reload

Tells all daemons that the autostart file is reloaded — and tells them to quit. This hook should be emitted in the first line of every autostart file.

STACKING

Every tag has its own stack of clients that are on this tag. Similar to the EWMH specification each tag stack contains several layers, which are from top to bottom:

  • the focused client (if raise_on_focus_temporarily is enabled)

  • clients in fullscreen

  • normal clients

  • frame decorations

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 raise_monitor command. The current stack is illustrated by the stack command.

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:

EXIT STATUS

Returns 0 on success. Returns EXIT_FAILURE if it cannot startup or if wmexec fails.

BUGS

See the herbstluftwm distribution BUGS file.

COMMUNITY

Feel free to join the IRC channel #herbstluftwm on irc.freenode.net.

AUTHOR

herbstluftwm was written by Thorsten Wißmann. All contributors are listed in the herbstluftwm distribution AUTHORS file.

RESOURCES

Patch submission and bug reporting:

hlwm@lists.herbstluftwm.org

COPYING

Copyright 2011-2014 Thorsten Wißmann. All rights reserved.

This software is licensed under the "Simplified BSD License". See LICENSE for details.


herbstluftwm-0.7.0/doc/herbstclient.html0000644000175000001440000005365612654701657020206 0ustar thorstenusers herbstclient(1)

SYNOPSIS

herbstclient [OPTIONS] COMMAND [ARGS …]

herbstclient [OPTIONS] [--wait|--idle] [FILTER …]

DESCRIPTION

Sends a COMMAND with its (optional) arguments ARGS to a running herbstluftwm(1) instance via Xlib. If COMMAND has an output, it is printed by herbstclient. If output does not end with a newline, then a newline is added to improve readability.

See herbstluftwm(1) for a list of available COMMANDs and their ARGS.

If --wait or --idle is passed, then it waits for hooks from herbstluftwm. The hook is printed, if it matches the optional FILTER. FILTERs are regular expressions. For a list of available hooks see herbstluftwm(1).

OPTIONS

-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.

-w, --wait

Same as --idle but exit after first --count hooks.

-c, --count COUNT

Let --wait exit after COUNT hooks were received and printed. The default of COUNT is 1.

-q, --quiet

Do not print error messages if herbstclient cannot connect to the running herbstluftwm instance.

-v, --version

Print the herbstclient version. To get the herbstluftwm version, use herbstclient version.

-h, --help

Print the herbstclient usage with its command line options.

ENVIRONMENT VARIABLES

DISPLAY

Specifies the DISPLAY to use, i.e. where herbstluftwm(1) is running.

EXIT STATUS

Returns the exit status of the COMMAND execution in herbstluftwm(1) server.

0

Success.

other

Failure. See herbstluftwm(1) for a list of error codes.

BUGS

It waits endlessly for a response from herbstluftwm (there is no timeout yet).

See the herbstluftwm distribution BUGS file.

COMMUNITY

Feel free to join the IRC channel #herbstluftwm on irc.freenode.net.

AUTHOR

herbstclient was written by Thorsten Wißmann. All contributors are listed in the herbstluftwm distribution AUTHORS file.

RESOURCES

Patch submission and bug reporting:

hlwm@lists.herbstluftwm.org

COPYING

Copyright 2011-2014 Thorsten Wißmann. All rights reserved.

This software is licensed under the "Simplified BSD License". See LICENSE for details.


herbstluftwm-0.7.0/doc/herbstluftwm-tutorial.txt0000644000175000001440000002456712607454114021750 0ustar thorstenusersherbstluftwm-tutorial(7) ======================== :doctype: manpage :man version: {herbstluftwmversion} Name ---- herbstluftwm-tutorial - A tutorial introduction to herbstluftwm Description ----------- This tutorial explains how to create a basic herbstluftwm setup and introduces the major herbstluftwm features. This tutorial neither covers all features nor specifies the mentioned features entirely; see link:herbstluftwm.html[*herbstluftwm*(1)] for a compact and more complete description. This tutorial covers these topics: - <> - <> - <> - <> - <> [[installation]] Basic installation ------------------ This describes two alternate installation methods. In any case, you also have to install the dependencies. Beside the standard libraries (GLib, XLib) which are found on nearly any system, you should install dzen2 (as current as possible) which is needed by the default +panel.sh+. Via the package manager ~~~~~~~~~~~~~~~~~~~~~~~ You always should prefer installing herbstluftwm via your package manager on your system. It should be called +herbstluftwm+. After installing it, the default configuration file has to be copied to your home directory: ---- mkdir -p ~/.config/herbstluftwm cp /etc/xdg/herbstluftwm/autostart ~/.config/herbstluftwm/ ---- You also should activate the tab completion for +herbstclient+. In case of bash, you can either activate the tab completion in general or source the herbstclient-completion from the bash_completion.d directory in your bashrc. In case of zsh the tab-completion normally is activated already (if not, consider activating it). Directly from git ~~~~~~~~~~~~~~~~~ If there is no package for your platform or if you want to use the current git version, then you can pull directly from the main repository: ---- git clone git://github.com/herbstluftwm/herbstluftwm cd herbstluftwm make # build the binaries # install files mkdir -p ~/bin # you also have to put $HOME/bin to your path, e.g. by: echo 'export PATH=$PATH:$HOME/bin' >> ~/.bashrc # or to your zshrc, etc... ln -s `pwd`/herbstluftwm ~/bin/ ln -s `pwd`/herbstclient ~/bin/ # copy the configuration mkdir -p ~/.config/herbstluftwm/ cp share/autostart ~/.config/herbstluftwm/ cp share/panel.sh ~/.config/herbstluftwm/ ---- - If you are using bash, then source the bash completion file in your +~/.bashrc+ + ---- source path-to/herbstluftwm/share/herbstclient-completion ---- - If you are using zsh, then copy the +share/_herbstclient+ file to the appropriate zsh-completion directory. Each time there is an update, you have to do the following steps in your herbstluftwm directory: ---- git pull make ---- Configure herbstluftwm as your window manager --------------------------------------------- As usual you can define herbstluftwm as your window manager by either selecting herbstluftwm in your login manager or by starting it in your +~/.xinitrc+, mostly by writing to your xinitrc (or +.xsession+ on some systems): ---- # start herbstluftwm in locked mode (it will be unlocked at the end of your # autostart) exec herbstluftwm --locked ---- After logging in the next time, you will get a default herbstluftwm session. First start ----------- After starting herbstluftwm, the screen is surrounded by a green frame initially, which indicates that there is only one large frame. A frame is a container where actual windows can be placed or which can be split into two frames. Start an +xterm+ by pressing Alt-Return, which will fill your entire screen. [[client]] Using the client ---------------- The only way to communicate to herbstluftwm is by using the client application called link:herbstclient.html[+herbstclient+]. Its usual syntax is: +herbstclient COMMAND [ARGUMENTS]+. This calls a certain +COMMAND+ within your running herbstluftwm instance. This causes some effect (which depends on the given +COMMAND+ and +ARGUMENTS+), produces some output which is printed by +herbstclient+ and lets +herbstclient+ exit with a exit-code (e.g. 0 for success) like many other UNIX tools: ---- shell COMMANDS, ╲ COMMAND, ARGUMENTS ╲ ARGUMENTS ╭────────────╮ ╲ │ │ V │ V herbstclient herbstluftwm ╱ ^ │ ╱ output, │ │ ╱ exit-code ╰────────────╯ V output, shell/terminal exit-code ---- The most simple command only prints the herbstluftwm version: ---- $ # lines prefixed with $ describes what to type, other lines describe the $ # typical output $ # Type: herc ve $ herbstclient version herbstluftwm 0.4.1 (built on Aug 30 2012) $ herbstclient set window_border_active_color red $ # now the window border turned red ---- The configuration of herbstluftwm only is done by calling commands via herbstclient. So the only configuration file is the +autostart+ which is placed at +~/.config/herbstluftwm/+ and which is a sequence of those herbstclient calls. Open it in your favourite text editor and replace the Mod-line by this to use the Super-key (or also called Windows-key) as the main modifier: ---- # Mod=Mod1 # use alt as the main modifier Mod=Mod4 # use Super as the main modifier ---- After saving the autostart file, you have to reload the configuration: ---- # the following line is identical to directly calling: # ~/.config/herbstluftwm/autostart herbstclient reload ---- Now you may notice that the red border color of your terminal turned green again, because the color is set in the default autostart. That's the typical configuration workflow: . Try some new settings in the command line . Add them to the autostart file . Press Mod-Shift-r which calls the +reload+ command or directly execute the autostart file from your shell to get the error messages if something went wrong. To learn more about herbstluftwm, just go through the man page line by line and check using the link:herbstluftwm.html[herbstluftwm(1) man page] what it does. For a quick introduction to the central paradigms, continue reading this. [[tiling]] Tiling ------ Initially there is one frame. Each frame has one of the two following possible types: . It serves as a container for windows, i.e. it can hold zero up to arbitrarily many windows. Launch several more terminals to see what happens: If there are multiple windows in one frame, they are aligned below each other. To change this layout algorithm, press Mod-space to cycle all the available layouting algorithms for the focused frame. . A frame also can be split into two subframes, which can be aligned next to or below each other. Press Mod-o to split to an horizontal alignment. To navigate to the fresh frame right of the old one press Mod-l. Press Mod-u to split vertically. The intuitive navigation is: + + ---- ⎧ h (or ←) ⎫ ⎧ left ⎪ j (or ↓) ⎪ means ⎪ down Mod + ⎨ k (or ↑) ⎬ ═══════> focus ⎨ up ⎩ l (or →) ⎭ ⎩ right ---- + To undo splitting, you can remove a frame via Mod-r. To shift some window from one frame to one of its neighbours, use the same keyboard shortcut while holding the Shift key pressed. It is not possible to resize single windows, only to resize frames. The according keyboard shortcut is the same while holding Control pressed. All in all it is: + ---- ⎧ h (or ←) ⎫ ⎧ left ⎧ ⎫ ⎪ j (or ↓) ⎪ means ⎧ focus frame ⎫ ⎪ down Mod + ⎨ Shift ⎬ + ⎨ k (or ↑) ⎬ ═════> ⎨ move window ⎬ ⎨ up ⎩ Control ⎭ ⎩ l (or →) ⎭ ⎩ resize frame ⎭ ⎩ right ---- With this, you can define a custom layout. It can be printed via the +layout+ command: ---- $ herbstclient layout ╾─┐ horizontal 50% selection=1 ├─┐ vertical 70% selection=0 │ ├─╼ vertical: 0x1400009 │ └─╼ vertical: └─╼ max: 0x1a00009 [FOCUS] ---- Just play with it a bit to how it works. You also can permanently save the layout using the +dump+ command: ---- $ herbstclient dump (split horizontal:0.500000:1 (split vertical:0.700000:0 (clients vertical:0 0x1400009) (clients vertical:0)) (clients max:0 0x1a00009)) $ layout=$(herbstclient dump) ---- And after some changes you can rewind to the original layout with the +load+ command: ---- $ herbstclient load "$layout" # mind the quotes! ---- [[tags]] Tags (or workspaces or virtual desktops or ....) ------------------------------------------------ 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. [[monitors]] Monitors -------- The notion of a monitor in herbstluftwm is treated much more abstract and general than in other window managers: A monitor just is a rectangular part of your screen which shows exactly one tag on it. Initially there is only one large monitor ranging over your entire screen: ---- $ herbstclient list_monitors 0: 1440x900+0+0 with tag "1" [FOCUS] ---- The output shows that there is only one monitor with index 0 at position +0+0 of size 1440x900 showing tag 1. In most cases, the herbstluftwm monitors will match the list of physical monitors. So to add another physical monitor, you have to perform several steps: . Enable it, such that it shows a part of your screen. You can use +xrandr+, +xinerama+ or any other tool you like. . Register it in herbstluftwm: Lets assume your new monitor has the resolution 1024x768 and is right of your main screen, then you can activate it via: + ---- $ herbstclient set_monitors 1440x900+0+0 1024x768+1440+0 ---- + Alternatively, if +xinerama+ works for your setup, simply run: + ---- $ herbstclient detect_monitors ---- For even more automation, you can enable the setting +auto_detect_monitors+. For more advanced examples, look at the +q3terminal.sh+ example script, which implements a drop-down-terminal like monitor where you can put any application you like. // TODO: add sections for: // rules // vim: tw=80 ft=asciidoc herbstluftwm-0.7.0/doc/herbstluftwm.10000644000175000001440000022603712654701661017430 0ustar thorstenusers'\" t .\" Title: herbstluftwm .\" Author: [see the "AUTHOR" section] .\" Generator: DocBook XSL Stylesheets v1.79.1 .\" Date: 2016-02-04 .\" Manual: \ \& .\" Source: \ \& herbstluftwm 0.7.0\e \e(c179281\e) .\" Language: English .\" .TH "HERBSTLUFTWM" "1" "2016\-02\-04" "\ \& herbstluftwm 0\&.7\&.0\e" "\ \&" .\" ----------------------------------------------------------------- .\" * Define some portability stuff .\" ----------------------------------------------------------------- .\" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ .\" http://bugs.debian.org/507673 .\" http://lists.gnu.org/archive/html/groff/2009-02/msg00013.html .\" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ .ie \n(.g .ds Aq \(aq .el .ds Aq ' .\" ----------------------------------------------------------------- .\" * set default formatting .\" ----------------------------------------------------------------- .\" disable hyphenation .nh .\" disable justification (adjust text to left margin only) .ad l .\" ----------------------------------------------------------------- .\" * MAIN CONTENT STARTS HERE * .\" ----------------------------------------------------------------- .SH "NAME" herbstluftwm \- a manual tiling window manager for X .SH "SYNOPSIS" .sp \fBherbstluftwm\fR [\fIOPTION\fR \&...] .SH "DESCRIPTION" .sp Starts the \fBherbstluftwm\fR window manager on \fIDISPLAY\fR\&. It also listens for calls from \fBherbstclient\fR(1) and executes them\&. The list of available \fBCOMMANDS\fR is listed below\&. .sp \fIOPTION\fR can be: .PP \fB\-c\fR, \fB\-\-autostart\fR \fIPATH\fR .RS 4 use \fIPATH\fR as autostart file instead of the one in \fI$XDG_CONFIG_HOME\fR .RE .PP \fB\-v\fR, \fB\-\-version\fR .RS 4 print version and exit .RE .PP \fB\-l\fR, \fB\-\-locked\fR .RS 4 Initially set the monitors_locked setting to 1 .RE .PP \fB\-\-verbose\fR .RS 4 print verbose information to stderr .RE .sp This manual documents the scripting and configuration interface\&. For a more verbose introduction see \fBherbstluftwm\-tutorial\fR(7)\&. .SH "TILING ALGORITHM" .sp The basic tiling concept is that the layout is represented by a binary tree\&. On startup you see one big frame across the entire screen\&. A frame fulfills exactly one of the following conditions: .sp .RS 4 .ie n \{\ \h'-04' 1.\h'+01'\c .\} .el \{\ .sp -1 .IP " 1." 4.2 .\} Frame contains windows: It shows some clients and arranges them\&. The current layout algorithms are: .sp .RS 4 .ie n \{\ \h'-04'\(bu\h'+03'\c .\} .el \{\ .sp -1 .IP \(bu 2.3 .\} 0: \fIvertical\fR \- clients are placed below each other .RE .sp .RS 4 .ie n \{\ \h'-04'\(bu\h'+03'\c .\} .el \{\ .sp -1 .IP \(bu 2.3 .\} 1: \fIhorizontal\fR \- clients are placed next to each other .RE .sp .RS 4 .ie n \{\ \h'-04'\(bu\h'+03'\c .\} .el \{\ .sp -1 .IP \(bu 2.3 .\} 2: \fImax\fR \- all clients are maximized in this frame .RE .sp .RS 4 .ie n \{\ \h'-04'\(bu\h'+03'\c .\} .el \{\ .sp -1 .IP \(bu 2.3 .\} 3: \fIgrid\fR \- clients are arranged in an almost quadratic grid .RE .RE .sp .RS 4 .ie n \{\ \h'-04' 2.\h'+01'\c .\} .el \{\ .sp -1 .IP " 2." 4.2 .\} Frame is split into subframes: It is split into exactly two \fBsubframes\fR in a configurable \fIfraction\fR either in a vertical or horizontal way\&. So it produces two \fBframes\fR which fulfill the same conditions (new frames always are about to contain \fBwindows\fR)\&. If you split a frame that already contains windows, the windows are inherited by the first new child frame\&. .RE .sp If a new window appears, it is put in the currently focused frame\&. Only the leaves of the frame tree can be focused\&. .sp A frame can be removed, it is then merged with its neighbour frame\&. Due to the layout structure of a binary tree, each frame (i\&.e\&. node in binary tree) has exactly one neighbour\&. .sp The analogy to a binary tree is explained the best way with a small example: On startup you have a simple binary tree, with one frame that can contain clients: .sp .if n \{\ .RS 4 .\} .nf C .fi .if n \{\ .RE .\} .sp When splitting it (e\&.g\&. with the command \fIsplit vertical 0\&.5\fR) you will get this: .sp .if n \{\ .RS 4 .\} .nf V / \e C C .fi .if n \{\ .RE .\} .sp You also can split the left frame horizontally and you will get: .sp .if n \{\ .RS 4 .\} .nf V / \e H C / \e C C .fi .if n \{\ .RE .\} .sp If you change the focus to the client on the right and remove this frame, it will be merged with the left subtree and you will get: .sp .if n \{\ .RS 4 .\} .nf H / \e C C .fi .if n \{\ .RE .\} .sp The \fIlayout\fR command prints the current layout of all tags as a tree\&. .SH "FRAME INDEX" .sp The exact position of a frame in the layout tree may be described by its \fBindex\fR which is just a string of characters\&. The lookup algorithm starts at the root frame and selects one of its two subtrees according to the each character in the index\&. .sp The characters are interpreted as follows: .sp .RS 4 .ie n \{\ \h'-04'\(bu\h'+03'\c .\} .el \{\ .sp -1 .IP \(bu 2.3 .\} 0: select the first subtree .RE .sp .RS 4 .ie n \{\ \h'-04'\(bu\h'+03'\c .\} .el \{\ .sp -1 .IP \(bu 2.3 .\} 1: select the second subtree .RE .sp .RS 4 .ie n \{\ \h'-04'\(bu\h'+03'\c .\} .el \{\ .sp -1 .IP \(bu 2.3 .\} \&.: select the subtree having the focus .RE .sp .RS 4 .ie n \{\ \h'-04'\(bu\h'+03'\c .\} .el \{\ .sp -1 .IP \(bu 2.3 .\} /: select the subtree not having the focus .RE .sp Thus an empty string refers to the root frame, and "00" refers to the first subtree of the first subtree of the root frame\&. .sp As a special case, the string "@" always refers to the currently focused frame\&. .SH "TAGS" .sp Tags are very similar to workspaces, virtual desktops or window groups\&. Each tag has one layout\&. There is a list of tags\&. You can add or remove tags dynamically\&. .SH "MONITORS" .sp Monitors in \fBherbstluftwm\fR are totally independent of the actual physical screens\&. This means you can for example split your screen in two virtual monitors to view two tags at once on a big screen\&. .sp Each monitor displays exactly one tag on a specified rectangle on the screen\&. .sp Each monitor may have a name, which can be set via \fBadd_monitor\fR and \fBrename_monitor\fR\&. It can be unset with the \fBrename_monitor\fR command\&. A monitor name is an arbitrary non\-empty string which must not start with +, \- or any digit\&. .sp A monitor can be referenced in different ways: .sp .RS 4 .ie n \{\ \h'-04'\(bu\h'+03'\c .\} .el \{\ .sp -1 .IP \(bu 2.3 .\} by its absolute index as listed in the \fBlist_monitors\fR command\&. .RE .sp .RS 4 .ie n \{\ \h'-04'\(bu\h'+03'\c .\} .el \{\ .sp -1 .IP \(bu 2.3 .\} by its relative index: a + or \- followed by a delta, e\&.g\&.: +3 .RE .sp .RS 4 .ie n \{\ \h'-04'\(bu\h'+03'\c .\} .el \{\ .sp -1 .IP \(bu 2.3 .\} by its relative position to the focused monitor\&. \-l denotes the monitor left of the focused monitor, \-r right of, \-u above of, and \-d below of, respectively\&. .RE .sp .RS 4 .ie n \{\ \h'-04'\(bu\h'+03'\c .\} .el \{\ .sp -1 .IP \(bu 2.3 .\} by "" (an empty string) which represents the current monitor\&. .RE .sp .RS 4 .ie n \{\ \h'-04'\(bu\h'+03'\c .\} .el \{\ .sp -1 .IP \(bu 2.3 .\} by its name\&. .RE .SH "COMMANDS" .sp \fBherbstluftwm\fR is controlled by internal commands, which can be executed via \fBherbstclient\fR(1) or via keybindings\&. .PP quit .RS 4 Quits herbstluftwm\&. .RE .PP reload .RS 4 Executes the autostart file\&. .RE .PP version .RS 4 Prints the version of the running herbstluftwm instance\&. .RE .PP echo [\fIARGS\fR \&...] .RS 4 Prints all given \fIARGS\fR separated by a single space and a newline afterwards\&. .RE .PP true .RS 4 Ignores all arguments and always returns success, i\&.e\&. 0\&. .RE .PP false .RS 4 Ignores all arguments and always returns failure, i\&.e\&. 1\&. .RE .PP list_commands .RS 4 Lists all available commands\&. .RE .PP list_monitors .RS 4 List currently configured monitors with their index, area (as rectangle), name (if named) and currently viewed tag\&. .RE .PP list_rules .RS 4 Lists all active rules\&. Each line consists of all the parameters the rule was called with, plus its label, separated by tabs\&. .RE .PP list_keybinds .RS 4 Lists all bound keys with their associated command\&. Each line consists of one key combination and the command with its parameters separated by tabs\&. .RE .if n \{\ .sp .\} .RS 4 .it 1 an-trap .nr an-no-space-flag 1 .nr an-break-flag 1 .br .ps +1 \fBWarning\fR .ps -1 .br .sp Tabs within command parameters are not escaped! .sp .5v .RE .PP lock .RS 4 Increases the \fImonitors_locked\fR setting\&. Use this if you want to do multiple window actions at once (i\&.e\&. without repainting between the single steps)\&. See also: \fBunlock\fR .RE .PP unlock .RS 4 Decreases the \fImonitors_locked\fR setting\&. If \fImonitors_locked\fR is changed to 0, then all monitors are repainted again\&. See also: \fBlock\fR .RE .PP keybind \fIKEY\fR \fICOMMAND\fR [\fIARGS \&...\fR] .RS 4 Adds a key binding\&. When \fIKEY\fR is pressed, the internal \fICOMMAND\fR (with its \fIARGS\fR) is executed\&. A key binding is a (possibly empty) list of modifiers (Mod1, Mod2, Mod3, Mod4, Mod5, Alt, Super, Control/Ctrl, Shift) and one key (see keysymdef\&.h for a list of keys)\&. Modifiers and the key are concatenated with \fI\-\fR or \fI+\fR as separator\&. If there is already a binding for this \fIKEY\fR, it will be overwritten\&. Examples: .sp .RS 4 .ie n \{\ \h'-04'\(bu\h'+03'\c .\} .el \{\ .sp -1 .IP \(bu 2.3 .\} keybind Mod4+Ctrl+q quit .RE .sp .RS 4 .ie n \{\ \h'-04'\(bu\h'+03'\c .\} .el \{\ .sp -1 .IP \(bu 2.3 .\} keybind Mod1\-i toggle always_show_frame .RE .sp .RS 4 .ie n \{\ \h'-04'\(bu\h'+03'\c .\} .el \{\ .sp -1 .IP \(bu 2.3 .\} keybind Mod1\-Shift\-space cycle_layout \-1 .RE .RE .PP keyunbind \fIKEY\fR|\fB\-F\fR|\fB\-\-all\fR .RS 4 Removes the key binding for \fIKEY\fR\&. The syntax for \fIKEY\fR is defined in \fBkeybind\fR\&. If \fB\-F\fR or \fB\-\-all\fR is given, then all key bindings will be removed\&. .RE .PP mousebind \fIBUTTON\fR \fIACTION\fR [\fICOMMAND\fR \&...] .RS 4 Adds a mouse binding for the floating mode\&. When \fIBUTTON\fR is pressed, the specified \fIACTION\fR will be performed\&. \fIBUTTON\fR has a similar syntax to the \fIKEY\fR argument of keybind: It consists of a list of modifiers (separated by \fI\-\fR or \fI+\fR, valid modifiers are listed in the description of \fIkeybind\fR) and exactly one button name: .sp .RS 4 .ie n \{\ \h'-04'\(bu\h'+03'\c .\} .el \{\ .sp -1 .IP \(bu 2.3 .\} B1 or Button1 .RE .sp .RS 4 .ie n \{\ \h'-04'\(bu\h'+03'\c .\} .el \{\ .sp -1 .IP \(bu 2.3 .\} B2 or Button2 .RE .sp .RS 4 .ie n \{\ \h'-04'\(bu\h'+03'\c .\} .el \{\ .sp -1 .IP \(bu 2.3 .\} B3 or Button3 .RE .sp .RS 4 .ie n \{\ \h'-04'\(bu\h'+03'\c .\} .el \{\ .sp -1 .IP \(bu 2.3 .\} B4 or Button4 .RE .sp .RS 4 .ie n \{\ \h'-04'\(bu\h'+03'\c .\} .el \{\ .sp -1 .IP \(bu 2.3 .\} B5 or Button5 .RE .RE .PP .RS 4 \fIACTION\fR must be one of the following actions: .sp .RS 4 .ie n \{\ \h'-04'\(bu\h'+03'\c .\} .el \{\ .sp -1 .IP \(bu 2.3 .\} move: Moves the window by dragging the cursor\&. .RE .sp .RS 4 .ie n \{\ \h'-04'\(bu\h'+03'\c .\} .el \{\ .sp -1 .IP \(bu 2.3 .\} resize: Resizes the window by dragging a corner\&. .RE .sp .RS 4 .ie n \{\ \h'-04'\(bu\h'+03'\c .\} .el \{\ .sp -1 .IP \(bu 2.3 .\} zoom: Resizes the window into all four directions while keeping the center of the window constant\&. .RE .sp .RS 4 .ie n \{\ \h'-04'\(bu\h'+03'\c .\} .el \{\ .sp -1 .IP \(bu 2.3 .\} call: Only calls the specified \fICOMMAND\fR while client\&.dragged links to the client on which the \fIBUTTON\fR has been performed\&. .RE .RE .PP .RS 4 While an \fIACTION\fR is performed, client\&.dragged is the client which is dragged\&. E\&.g\&.: .sp .RS 4 .ie n \{\ \h'-04'\(bu\h'+03'\c .\} .el \{\ .sp -1 .IP \(bu 2.3 .\} mousebind Mod1\-Button3 zoom .RE .sp .RS 4 .ie n \{\ \h'-04'\(bu\h'+03'\c .\} .el \{\ .sp -1 .IP \(bu 2.3 .\} mousebind Mod1\-B4 call substitute WID clients\&.dragged\&.winid spawn transset\-df \-\-inc \-i WID 0\&.05 .RE .sp .RS 4 .ie n \{\ \h'-04'\(bu\h'+03'\c .\} .el \{\ .sp -1 .IP \(bu 2.3 .\} mousebind Mod1\-B5 call substitute WID clients\&.dragged\&.winid spawn transset\-df \-\-dec \-i WID \-m 0\&.2 0\&.05 .RE .RE .PP mouseunbind .RS 4 Removes all mouse bindings\&. .RE .PP spawn \fIEXECUTABLE\fR [\fIARGS \&...\fR] .RS 4 Spawns an \fIEXECUTABLE\fR with its \fIARGS\fR\&. For details see \fIman 3 execvp\fR\&. Example: .sp .RS 4 .ie n \{\ \h'-04'\(bu\h'+03'\c .\} .el \{\ .sp -1 .IP \(bu 2.3 .\} spawn xterm \-e man 3 execvp .RE .RE .PP wmexec [\fIWINDOWMANAGER\fR [\fIARGS \&...\fR]] .RS 4 Executes the \fIWINDOWMANAGER\fR with its \fIARGS\fR\&. This is useful to switch the window manager in the running session without restarting the session\&. If no or an invalid \fIWINDOWMANAGER\fR is given, then herbstluftwm is restarted\&. For details see \fIman 3 execvp\fR\&. Example: .sp .RS 4 .ie n \{\ \h'-04'\(bu\h'+03'\c .\} .el \{\ .sp -1 .IP \(bu 2.3 .\} wmexec openbox .RE .RE .PP chain \fISEPARATOR\fR [\fICOMMANDS\fR \&...] .RS 4 chain expects a \fISEPARATOR\fR and a list of \fICOMMANDS\fR with arguments\&. The commands have to be separated by the specified \fISEPARATOR\fR\&. The \fISEPARATOR\fR can by any word and only is recognized as the separator between commands if it exactly matches \fISEPARATOR\fR\&. "chain" outputs the appended outputs of all commands and returns the exit code of the last executed command\&. Examples are: .sp .RS 4 .ie n \{\ \h'-04'\(bu\h'+03'\c .\} .el \{\ .sp -1 .IP \(bu 2.3 .\} Create a tag called "foo" and directly use it: chain , add foo , use foo .RE .sp .RS 4 .ie n \{\ \h'-04'\(bu\h'+03'\c .\} .el \{\ .sp -1 .IP \(bu 2.3 .\} Rotate the layout clockwise: chain \&.\-\&. lock \&.\-\&. rotate \&.\-\&. rotate \&.\-\&. rotate \&.\-\&. unlock .RE .RE .PP .RS 4 Counterexamples are: .sp .RS 4 .ie n \{\ \h'-04'\(bu\h'+03'\c .\} .el \{\ .sp -1 .IP \(bu 2.3 .\} This will only create a tag called "foo,": chain , add foo, use foo .RE .sp .RS 4 .ie n \{\ \h'-04'\(bu\h'+03'\c .\} .el \{\ .sp -1 .IP \(bu 2.3 .\} Separator "\&." defined, but "," is used: chain \&. add foo , use foo .RE .RE .PP and \fISEPARATOR\fR [\fICOMMANDS\fR \&...] .RS 4 "and" behaves like the chain command but only executes the specified \fICOMMANDS\fR while the commands return the exit code 0\&. .RE .PP or \fISEPARATOR\fR [\fICOMMANDS\fR \&...] .RS 4 "or" behaves like the chain command but only executes the specified \fICOMMANDS\fR until one command returns the exit code 0\&. .RE .PP ! \fICOMMAND\fR .RS 4 "!" executes the provided command, but inverts its return value\&. If the provided command returns a nonzero, "!" returns a 0, if the command returns a zero, "!" returns a 1\&. .RE .PP try \fICOMMAND\fR .RS 4 "try" executes the provided command, prints its output, but always returns success, i\&.e\&. 0\&. .RE .PP silent \fICOMMAND\fR .RS 4 "silent" executes the provided command, but discards its output and only returns its exit code\&. .RE .PP focus_nth \fIINDEX\fR .RS 4 Focuses the nth window in a frame\&. The first window has \fIINDEX\fR 0\&. If \fIINDEX\fR is negative or greater than the last window index, then the last window is focused\&. .RE .PP cycle [\fIDELTA\fR] .RS 4 Cycles the selection within the current frame by \fIDELTA\fR\&. If \fIDELTA\fR is omitted, \fIDELTA\fR = 1 will be used\&. \fIDELTA\fR can be negative; \fIDELTA\fR = \-1 means: cycle in the opposite direction by 1\&. .RE .PP cycle_all [\fB\-\-skip\-invisible\fR] [\fIDIRECTION\fR] .RS 4 Cycles through all windows and frames on the current tag\&. \fIDIRECTION\fR = 1 means forward, \fIDIRECTION\fR = \-1 means backward, \fIDIRECTION\fR = 0 has no effect\&. \fIDIRECTION\fR defaults to 1\&. If there are multiple windows within on frame, then it acts similar to the \fIcycle\fR command\&. (The \fIcycle_all\fR command focuses the next/previous leave in the \fIlayout\fR tree\&.)\&. If \fB\-\-skip\-invisible\fR is given, then this only cycles through all visible windows and skips invisible windows in the max layout\&. The focused window is raised\&. .RE .PP cycle_frame [\fIDIRECTION\fR] .RS 4 Cycles through all frames on the current tag\&. \fIDIRECTION\fR = 1 means forward, \fIDIRECTION\fR = \-1 means backward, \fIDIRECTION\fR = 0 has no effect\&. \fIDIRECTION\fR defaults to 1\&. .RE .PP cycle_layout [\fIDELTA\fR [\fILAYOUTS\fR \&...]] .RS 4 Cycles the layout algorithm in the current frame by \fIDELTA\fR\&. \fIDELTA\fR defaults to 1\&. You can find a list of layout algorithms above\&. If a list of \fILAYOUTS\fR is given, cycle_layout will cycle through those instead of the default layout algorithm list\&. Each layout name should occur at most once\&. Example: .sp .RS 4 .ie n \{\ \h'-04'\(bu\h'+03'\c .\} .el \{\ .sp -1 .IP \(bu 2.3 .\} cycle_layout \-1 .RE .sp .RS 4 .ie n \{\ \h'-04'\(bu\h'+03'\c .\} .el \{\ .sp -1 .IP \(bu 2.3 .\} cycle_layout 1 vertical grid .RE .RE .PP set_layout \fILAYOUT\fR .RS 4 Sets the layout algorithm in the current frame to \fILAYOUT\fR\&. For the list of layouts, check the list of layout algorithms above\&. .RE .PP close \fIWINID\fR .RS 4 Closes the specified window gracefully or the focused window if none is given explicitly\&. See the section on WINDOW IDS how to reference a certain window\&. .RE .PP close_or_remove .RS 4 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 \fIFRACTION\fR between 0 and 1 which defaults to 0\&.5\&. \fIALIGN\fR is one of .sp .RS 4 .ie n \{\ \h'-04'\(bu\h'+03'\c .\} .el \{\ .sp -1 .IP \(bu 2.3 .\} \fItop\fR .RE .sp .RS 4 .ie n \{\ \h'-04'\(bu\h'+03'\c .\} .el \{\ .sp -1 .IP \(bu 2.3 .\} \fIbottom\fR (= \fIvertical\fR) .RE .sp .RS 4 .ie n \{\ \h'-04'\(bu\h'+03'\c .\} .el \{\ .sp -1 .IP \(bu 2.3 .\} \fIleft\fR, .RE .sp .RS 4 .ie n \{\ \h'-04'\(bu\h'+03'\c .\} .el \{\ .sp -1 .IP \(bu 2.3 .\} \fIright\fR (= \fIhorizontal\fR) .RE .sp .RS 4 .ie n \{\ \h'-04'\(bu\h'+03'\c .\} .el \{\ .sp -1 .IP \(bu 2.3 .\} \fIexplode\fR .RE .sp .RS 4 .ie n \{\ \h'-04'\(bu\h'+03'\c .\} .el \{\ .sp -1 .IP \(bu 2.3 .\} \fIauto\fR (split along longest side) It specifies which of the two halves will be empty after the split\&. The other half will be occupied by the currently focused frame\&. After splitting, the originally focuse frame will stay focused\&. One special \fIALIGN\fR mode is \fIexplode\fR, which splits the frame in such a way that the window sizes and positions are kept as much as possible\&. If no \fIFRACTION\fR is given to \fIexplode\fR mode an optimal fraction is picked automatically\&. Example: .RE .sp .RS 4 .ie n \{\ \h'-04'\(bu\h'+03'\c .\} .el \{\ .sp -1 .IP \(bu 2.3 .\} split explode .RE .sp .RS 4 .ie n \{\ \h'-04'\(bu\h'+03'\c .\} .el \{\ .sp -1 .IP \(bu 2.3 .\} split bottom 0\&.5 .RE .sp .RS 4 .ie n \{\ \h'-04'\(bu\h'+03'\c .\} .el \{\ .sp -1 .IP \(bu 2.3 .\} split horiz 0\&.3 .RE .sp .RS 4 .ie n \{\ \h'-04'\(bu\h'+03'\c .\} .el \{\ .sp -1 .IP \(bu 2.3 .\} split vertical 0\&.5 .RE .sp .RS 4 .ie n \{\ \h'-04'\(bu\h'+03'\c .\} .el \{\ .sp -1 .IP \(bu 2.3 .\} split h .RE .RE .PP focus [\fI\-i\fR|\fI\-e\fR] \fIDIRECTION\fR .RS 4 Moves the focus from current frame to the next frame or client in \fIDIRECTION\fR which is in: .sp .RS 4 .ie n \{\ \h'-04'\(bu\h'+03'\c .\} .el \{\ .sp -1 .IP \(bu 2.3 .\} l[eft] .RE .sp .RS 4 .ie n \{\ \h'-04'\(bu\h'+03'\c .\} .el \{\ .sp -1 .IP \(bu 2.3 .\} r[ight] .RE .sp .RS 4 .ie n \{\ \h'-04'\(bu\h'+03'\c .\} .el \{\ .sp -1 .IP \(bu 2.3 .\} u[p] .RE .sp .RS 4 .ie n \{\ \h'-04'\(bu\h'+03'\c .\} .el \{\ .sp -1 .IP \(bu 2.3 .\} d[own] .RE .RE .PP .RS 4 If \fI\-i\fR (internal) is given or default_direction_external_only is unset, then the next client in \fIDIRECTION\fR can also be within the same frame\&. If there is no client within this frame or \fI\-e\fR (external) is given, then the next frame in specified \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\&. .sp .if n \{\ .RS 4 .\} .nf 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 .\} .RE .PP .RS 4 If the currently focused client is floated, then the next floating window in the specified direction is focused and raised\&. .RE .PP .RS 4 If \fIfocus_crosses_monitor_boundaries\fR is set and no client or frame is found in the specified \fIDIRECTION\fR, then the next monitor in that \fIDIRECTION\fR is focused\&. .RE .PP focus_edge [\fI\-i\fR|\fI\-e\fR] \fIDIRECTION\fR .RS 4 Focuses the window on the edge of the tag in the specified \fIDIRECTION\fR\&. The \fIDIRECTIONS\fR and \fI\-e\fR behave as specified at the \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\&. .RE .PP raise \fIWINID\fR .RS 4 Raises the specified window\&. See the section on WINDOW IDS on how to reference a certain window\&. Its result is only visible in floating mode\&. .RE .if n \{\ .sp .\} .RS 4 .it 1 an-trap .nr an-no-space-flag 1 .nr an-break-flag 1 .br .ps +1 \fBTip\fR .ps -1 .br .sp The \fIWINID\fR also can specify an unmanaged window, although the completion for the raise command does not list the IDs of unmanaged windows\&. .sp .5v .RE .PP jumpto \fIWINID\fR .RS 4 Puts the focus to the specified window\&. See the section on WINDOW IDS on how to reference a certain window\&. .RE .PP bring \fIWINID\fR .RS 4 Moves the specified window to the current frame and focuses it\&. See the section on WINDOW IDS on how to reference a certain window\&. .RE .PP resize \fIDIRECTION\fR \fIFRACTIONDELTA\fR .RS 4 Changes the next fraction in specified \fIDIRECTION\fR by \fIFRACTIONDELTA\fR\&. \fIDIRECTION\fR behaves as specified at the \fIfocus\fR command\&. You should not omit the sign \fI\-\fR or \fI+\fR, because in future versions, the behaviour may change if the sign is omitted\&. Example: .sp .RS 4 .ie n \{\ \h'-04'\(bu\h'+03'\c .\} .el \{\ .sp -1 .IP \(bu 2.3 .\} resize right +0\&.05 .RE .sp .RS 4 .ie n \{\ \h'-04'\(bu\h'+03'\c .\} .el \{\ .sp -1 .IP \(bu 2.3 .\} resize down \-0\&.1 .RE .RE .PP shift_edge [\fI\-i\fR|\fI\-e\fR] \fIDIRECTION\fR .RS 4 Shifts the focused window to the the edge of a tag in the specified \fIDIRECTION\fR\&. The \fIDIRECTIONS\fR behave as specified at the \fIfocus\fR command and \fI\-i\fR and \fI\-e\fR behave as specified at the \fIfocus_edge\fR command\&. .RE .PP shift [\fI\-i\fR|\fI\-e\fR] \fIDIRECTION\fR .RS 4 Shifts the focused window to the next frame in the specified \fIDIRECTION\fR\&. The \fIDIRECTIONS\fR and \fI\-i\fR|\fI\-e\fR behave as specified at the \fIfocus\fR command\&. If the focused client is floated instead of being tiled, then client is shifted to the next window or screen edge\&. .RE .PP shift_to_monitor \fIMONITOR\fR .RS 4 Moves the focused window to the tag on the specified \fIMONITOR\fR\&. .RE .PP remove .RS 4 Removes focused frame and merges its windows to its neighbour frame\&. .RE .PP rotate .RS 4 Rotates the layout on the focused tag counterclockwise by 90 degrees\&. This only manipulates the alignment of frames, not the content of them\&. .RE .PP set \fINAME\fR \fIVALUE\fR .RS 4 Sets the specified setting \fINAME\fR to \fIVALUE\fR\&. All \fBSETTINGS\fR are listed in the section below\&. .RE .PP get \fINAME\fR .RS 4 Prints the value of setting \fINAME\fR\&. All \fBSETTINGS\fR are listed in the section below\&. .RE .PP toggle \fINAME\fR .RS 4 Toggles the setting \fINAME\fR if it\(cqs an integer setting: If its value is unequal to 0, it becomes 0; else its previous value (which was unequal to 0) is restored\&. .RE .PP cycle_value \fINAME\fR \fIVALUES\fR \&... .RS 4 Cycles value of the setting \fINAME\fR through \fIVALUES\fR: I\&.e\&. it searches the first occurrence of the current value in \fIVALUES\fR and changes the value to the next in the list or to the first one if the end is reached or current value wasn\(cqt found\&. Example: .sp .RS 4 .ie n \{\ \h'-04'\(bu\h'+03'\c .\} .el \{\ .sp -1 .IP \(bu 2.3 .\} cycle_value frame_gap 0 5 10 15 .RE .sp .RS 4 .ie n \{\ \h'-04'\(bu\h'+03'\c .\} .el \{\ .sp -1 .IP \(bu 2.3 .\} cycle_value frame_bg_normal_color red green blue .RE .RE .PP cycle_monitor [\fIDELTA\fR] .RS 4 Cycles monitor focused by \fIDELTA\fR\&. \fIDELTA\fR defaults to 1\&. .RE .PP focus_monitor \fIMONITOR\fR .RS 4 Puts focus to the specified monitor\&. .RE .PP add \fITAG\fR .RS 4 Creates a new empty tag named \fITAG\fR\&. .RE .PP use \fITAG\fR .RS 4 Switches the focused monitor to specified \fITAG\fR\&. .RE .PP use_index \fIINDEX\fR [\fB\-\-skip\-visible\fR] .RS 4 Switches the focused monitor to the \fITAG\fR with the specified \fIINDEX\fR\&. If \fIINDEX\fR starts with + or \-, then \fIINDEX\fR is treated relative to the current \fITAG\fR\&. If \fB\-\-skip\-visible\fR is passed and \fIINDEX\fR is relative, then tags that are already visible on a monitor are skipped\&. E\&.g\&. this cycles backwards through the tags: .sp .RS 4 .ie n \{\ \h'-04'\(bu\h'+03'\c .\} .el \{\ .sp -1 .IP \(bu 2.3 .\} use_index \-1 \-\-skip\-visible .RE .RE .PP use_previous .RS 4 Switches the focused monitor to the previously viewed tag\&. .RE .PP merge_tag \fITAG\fR [\fITARGET\fR] .RS 4 Removes tag named \fITAG\fR and moves all its windows to tag \fITARGET\fR\&. If \fITARGET\fR is omitted, the focused tag will be used\&. .RE .PP rename \fIOLDTAG\fR \fINEWTAG\fR .RS 4 Renames tag named \fIOLDTAG\fR to \fINEWTAG\fR\&. .RE .PP move \fITAG\fR .RS 4 Moves the focused window to the tag named \fITAG\fR\&. .RE .PP move_index \fIINDEX\fR [\fB\-\-skip\-visible\fR] .RS 4 Moves the focused window to the tag specified by \fIINDEX\fR\&. Analogical to the argument for \fBuse_index\fR: If \fIINDEX\fR starts with + or \-, then it is treated relative\&. If \fB\-\-skip\-visible\fR is passed with a relative index, then already visible tags are skipped\&. .RE .PP lock_tag [\fIMONITOR\fR] .RS 4 Lock the tag switching on the specified monitor\&. If no argument is given, the currently focused monitor is used\&. When the tag switching is disabled for a monitor, the commands \fBuse\fR and \fBuse_index\fR have no effect when executed there\&. When \fIswap_monitors_to_get_tag\fR is enabled, switching to a tag which is located on a locked monitor, switches to that monitor instead of stealing it from there\&. The lock state of a monitor is indicated by "[LOCKED]" in the \fBlist_monitors\fR output\&. .RE .PP unlock_tag [\fIMONITOR\fR] .RS 4 Re\-enables the tag switching on the specified monitor\&. If no argument is given, the currently focused monitor is used\&. This is the reverse operation to \fBlock_tag\fR and has no further side effects but removing this lock\&. .RE .PP disjoin_rects \fIRECTS\fR \&... .RS 4 Takes a list of rectangles and splits them into smaller pieces until all rectangles are disjoint, the result rectangles are printed line by line\&. This command does not modify the current list of monitors! So this can be useful in combination with the set_monitors command\&. .sp .RS 4 .ie n \{\ \h'-04'\(bu\h'+03'\c .\} .el \{\ .sp -1 .IP \(bu 2.3 .\} E\&.g\&. disjoin_rects 600x400+0+0 600x400+300+250 prints this: .sp .if n \{\ .RS 4 .\} .nf 300x150+300+250 600x250+0+0 300x150+0+250 300x150+600+250 600x250+300+400 .fi .if n \{\ .RE .\} .RE .sp .RS 4 .ie n \{\ \h'-04'\(bu\h'+03'\c .\} .el \{\ .sp -1 .IP \(bu 2.3 .\} In the above example two monitors are split into 5 monitors, which graphically means: .sp .if n \{\ .RS 4 .\} .nf ┌──────┐ ┌──────┐ │ │ └──────┘ │ ┌───┼───┐ ┌─┐┌───┐┌──┐ │ │ │ │ disjoin │ ││ ││ │ └──┼───┘ │ ─────────> └─┘└───┘└──┘ │ │ ┌───────┐ └───────┘ └───────┘ .fi .if n \{\ .RE .\} .RE .RE .PP set_monitors \fIRECTS\fR \&... .RS 4 Sets the list of monitors \fBexactly\fR to the list of given rectangles: .sp .RS 4 .ie n \{\ \h'-04'\(bu\h'+03'\c .\} .el \{\ .sp -1 .IP \(bu 2.3 .\} The i\(cqth existing monitor is moved to the i\(cqth given \fIRECT\fR .RE .sp .RS 4 .ie n \{\ \h'-04'\(bu\h'+03'\c .\} .el \{\ .sp -1 .IP \(bu 2.3 .\} New monitors are created if there are more \fIRECTS\fR then monitors .RE .sp .RS 4 .ie n \{\ \h'-04'\(bu\h'+03'\c .\} .el \{\ .sp -1 .IP \(bu 2.3 .\} Existing monitors are deleted if there are more monitors then \fIRECTS\fR .RE .RE .PP detect_monitors \fI\-l\fR|\fI\-\-list\fR|\fI\-\-no\-disjoin\fR .RS 4 Sets the list of monitors to the available Xinerama monitors\&. If the Xinerama extension is missing, it will fall back to one monitor across the entire screen\&. If the detected monitors overlap, the will be split into more monitors that are disjoint but cover the same area using disjoin_rects\&. If \fI\-l\fR or \fI\-\-list\fR is passed, the list of rectangles of detected pyhsical monitors is printed\&. So hc detect_monitors is equivalent to the bash command hc set_monitors $(hc disjoin_rects $(hc detect_monitors \-l))\&. .RE .PP add_monitor \fIRECT\fR [\fITAG\fR [\fINAME\fR]] .RS 4 Adds a monitor on the specified rectangle \fIRECT\fR and displays \fITAG\fR on it\&. \fITAG\fR currently must not be displayed on any other monitor\&. \fIRECT\fR is a string of the form \fIWxH\(+-X\(+-Y\fR\&. If no or an empty \fITAG\fR is given, then any free tag will be chosen\&. If a \fINAME\fR is given, you can reference to this monitor by its name instead of using an index\&. Example: .sp .RS 4 .ie n \{\ \h'-04'\(bu\h'+03'\c .\} .el \{\ .sp -1 .IP \(bu 2.3 .\} add_monitor 1024x768\-20+0 mynewtag main .RE .RE .PP remove_monitor \fIMONITOR\fR .RS 4 Removes the specified monitor\&. .RE .PP move_monitor \fIMONITOR\fR \fIRECT\fR [\fIPADUP\fR [\fIPADRIGHT\fR [\fIPADDOWN\fR [\fIPADLEFT\fR]]]] .RS 4 Moves the specified monitor to rectangle \fIRECT\fR\&. \fIRECT\fR is defined as in \fIadd_monitor\fR\&. If no or an empty pad is given, it is not changed\&. .RE .PP raise_monitor [\fIMONITOR\fR] .RS 4 Raises the specified monitor or the current one if \fIMONITOR\fR is omitted\&. .RE .PP rename_monitor \fIMONITOR\fR \fINAME\fR .RS 4 (Re)names an already existing monitor\&. If \fINAME\fR is empty, it removes the monitor\(cqs name\&. .RE .PP stack .RS 4 Prints the stack of monitors with the visible tags and their layers as a tree\&. The order of the printed stack is top to bottom\&. The style is configured by the \fItree_style\fR setting\&. .RE .PP monitor_rect [[\-p] \fIMONITOR\fR] .RS 4 Prints the rectangle of the specified monitor in the format: \fBX Y W H\fR If no \fIMONITOR\fR or \fIcur\fR is given, then the current monitor is used\&. If \fI\-p\fR is supplied, then the remaining rect without the pad around this monitor is printed\&. .RE .PP pad \fIMONITOR\fR [\fIPADUP\fR [\fIPADRIGHT\fR [\fIPADDOWN\fR [\fIPADLEFT\fR]]]] .RS 4 Sets the pad of specified monitor to the specified padding\&. If no or an empty padding is given, it is not changed\&. .RE .PP list_padding [\fIMONITOR\fR] .RS 4 Lists the padding of the specified monitor, or the currently focused monitor if no monitor is given\&. .RE .PP layout [\fITAG\fR [\fIINDEX\fR]] .RS 4 Prints the layout of frame with \fIINDEX\fR on \fITAG\fR, in a nice tree style\&. Its style is defined by the \fItree_style\fR setting\&. If no \fITAG\fR is given, the current tag is used\&. If no \fIINDEX\fR is given, the root frame is used\&. To specify \fIINDEX\fR without specifying \fITAG\fR (i\&.e\&. use current tag), pass an empty string as \fITAG\fR\&. An example output is: .sp .if n \{\ .RS 4 .\} .nf ╾─┐ horizontal 50% selection=1 ├─╼ vertical: 0xe00009 └─┐ vertical 50% selection=0 ├─╼ vertical: 0xa00009 [FOCUS] └─╼ vertical: 0x1000009 .fi .if n \{\ .RE .\} .RE .PP dump [\fITAG\fR [\fIINDEX\fR]] .RS 4 Prints the same information as the \fIlayout\fR command but in a machine readable format\&. Its output can be read back with the \fIload\fR command\&. An example output (formatted afterwards) is: .sp .if n \{\ .RS 4 .\} .nf (split horizontal:0\&.500000:1 (clients vertical:0 0xe00009) (split vertical:0\&.500000:1 (clients vertical:0 0xa00009) (clients vertical:0 0x1000009))) .fi .if n \{\ .RE .\} .RE .PP load [\fITAG\fR] \fILAYOUT\fR .RS 4 Loads a given \fILAYOUT\fR description to specified \fITAG\fR or current tag if no \fITAG\fR is given\&. .RE .if n \{\ .sp .\} .RS 4 .it 1 an-trap .nr an-no-space-flag 1 .nr an-break-flag 1 .br .ps +1 \fBCaution\fR .ps -1 .br .sp \fILAYOUT\fR is exactly one parameter\&. If you are calling it manually from your shell or from a script, quote it properly! .sp .5v .RE .PP complete \fIPOSITION\fR [\fICOMMAND\fR \fIARGS \&...\fR] .RS 4 Prints the result of tab completion for the partial \fICOMMAND\fR with optional \fIARGS\fR\&. You usually do not need this, because there is already tab completion for bash\&. Example: .sp .RS 4 .ie n \{\ \h'-04'\(bu\h'+03'\c .\} .el \{\ .sp -1 .IP \(bu 2.3 .\} complete 0 m prints all commands beginning with m .RE .sp .RS 4 .ie n \{\ \h'-04'\(bu\h'+03'\c .\} .el \{\ .sp -1 .IP \(bu 2.3 .\} complete 1 toggle fra prints all settings beginning with fra that can be toggled .RE .RE .PP complete_shell \fIPOSITION\fR [\fICOMMAND\fR \fIARGS \&...\fR] .RS 4 Behaves like \fBcomplete\fR with the following extras, useful for completion on posix shells: .sp .RS 4 .ie n \{\ \h'-04'\(bu\h'+03'\c .\} .el \{\ .sp -1 .IP \(bu 2.3 .\} Escape sequences are removed in \fICOMMAND\fR and \fIARGS\fR\&. .RE .sp .RS 4 .ie n \{\ \h'-04'\(bu\h'+03'\c .\} .el \{\ .sp -1 .IP \(bu 2.3 .\} A space is appended to each full completion result\&. .RE .sp .RS 4 .ie n \{\ \h'-04'\(bu\h'+03'\c .\} .el \{\ .sp -1 .IP \(bu 2.3 .\} Special characters will be escaped in the output\&. .RE .RE .PP emit_hook \fIARGS \&...\fR .RS 4 Emits a custom hook to all idling herbstclients\&. .RE .PP tag_status [\fIMONITOR\fR] .RS 4 Print a tab separated list of all tags for the specified \fIMONITOR\fR index\&. If no \fIMONITOR\fR index is given, the focused monitor is used\&. Each tag name is prefixed with one char, which indicates its state: .sp .RS 4 .ie n \{\ \h'-04'\(bu\h'+03'\c .\} .el \{\ .sp -1 .IP \(bu 2.3 .\} \fB\&.\fR the tag is empty .RE .sp .RS 4 .ie n \{\ \h'-04'\(bu\h'+03'\c .\} .el \{\ .sp -1 .IP \(bu 2.3 .\} \fB:\fR the tag is not empty .RE .sp .RS 4 .ie n \{\ \h'-04'\(bu\h'+03'\c .\} .el \{\ .sp -1 .IP \(bu 2.3 .\} \fB+\fR the tag is viewed on the specified \fIMONITOR\fR, but this monitor is not focused\&. .RE .sp .RS 4 .ie n \{\ \h'-04'\(bu\h'+03'\c .\} .el \{\ .sp -1 .IP \(bu 2.3 .\} \fB#\fR the tag is viewed on the specified \fIMONITOR\fR and it is focused\&. .RE .sp .RS 4 .ie n \{\ \h'-04'\(bu\h'+03'\c .\} .el \{\ .sp -1 .IP \(bu 2.3 .\} \fB\-\fR the tag is viewed on a different \fIMONITOR\fR, but this monitor is not focused\&. .RE .sp .RS 4 .ie n \{\ \h'-04'\(bu\h'+03'\c .\} .el \{\ .sp -1 .IP \(bu 2.3 .\} \fB%\fR the tag is viewed on a different \fIMONITOR\fR and it is focused\&. .RE .sp .RS 4 .ie n \{\ \h'-04'\(bu\h'+03'\c .\} .el \{\ .sp -1 .IP \(bu 2.3 .\} \fB!\fR the tag contains an urgent window .RE .RE .if n \{\ .sp .\} .RS 4 .it 1 an-trap .nr an-no-space-flag 1 .nr an-break-flag 1 .br .ps +1 \fBWarning\fR .ps -1 .br .sp If you use a tab in one of the tag names, then tag_status is probably quite useless for you\&. .sp .5v .RE .PP floating [[\fITAG\fR] \fBon\fR|\fBoff\fR|\fBtoggle\fR|\fBstatus\fR] .RS 4 Changes the current tag to floating/tiling mode on specified \fITAG\fR or prints it current status\&. If no \fITAG\fR is given, the current tag is used\&. If no argument is given, floating mode is toggled\&. If \fBstatus\fR is given, then \fBon\fR or \fBoff\fR is printed, depending of the floating state of \fITAG\fR\&. .RE .PP rule [[\-\-]\fIFLAG\fR|[\-\-]\fILABEL\fR|[\-\-]\fICONDITION\fR|[\-\-]\fICONSEQUENCE\fR \&...] .RS 4 Defines a rule which will be applied to all new clients\&. Its behaviour is described in the \fBRULES section\fR\&. .RE .PP unrule \fILABEL\fR|\fB\-\-all\fR|\fB\-F\fR .RS 4 Removes all rules named \fILABEL\fR\&. If \-\-all or \-F is passed, then all rules are removed\&. .RE .PP fullscreen [\fBon\fR|\fBoff\fR|\fBtoggle\fR] .RS 4 Sets or toggles the fullscreen state of the focused client\&. If no argument is given, fullscreen mode is toggled\&. .RE .PP pseudotile [\fBon\fR|\fBoff\fR|\fBtoggle\fR] .RS 4 Sets or toggles the pseudotile state of the focused client\&. If a client is pseudotiled, then in tiling mode the client is only moved but not resized \- the client size will stay the floating size\&. The only reason to resize the client is to ensure that it fits into its tile\&. If no argument is given, pseudotile mode is toggled\&. .RE .PP object_tree [\fIPATH\fR] .RS 4 Prints the tree of objects\&. If the object path \fIPATH\fR is given, only the subtree starting at \fIPATH\fR is printed\&. See the \fBOBJECTS section\fR for more details\&. .RE .PP attr [\fIPATH\fR [\fINEWVALUE\fR] .RS 4 Prints the children and attributes of the given object addressed by \fIPATH\fR\&. If \fIPATH\fR is an attribute, then print the attribute value\&. If \fINEWVALUE\fR is given, assign \fINEWVALUE\fR to the attribute given by \fIPATH\fR\&. See the \fBOBJECTS section\fR for more details\&. .RE .PP get_attr \fIATTRIBUTE\fR .RS 4 Print the value of the specified \fIATTRIBUTE\fR as described in the \fBOBJECTS section\fR\&. .RE .PP set_attr \fIATTRIBUTE\fR \fINEWVALUE\fR .RS 4 Assign \fINEWVALUE\fR to the specified \fIATTRIBUTE\fR as described in the \fBOBJECTS section\fR\&. .RE .PP new_attr [\fBbool\fR|\fBcolor\fR|\fBint\fR|\fBstring\fR|\fBuint\fR] \fIPATH\fR .RS 4 Creates a new attribute with the name and in the object specified by \fIPATH\fR\&. Its type is specified by the first argument\&. The attribute name has to begin with my_\&. .RE .PP remove_attr \fIPATH\fR .RS 4 Removes the user defined attribute \fIPATH\fR\&. .RE .PP substitute \fIIDENTIFIER\fR \fIATTRIBUTE\fR \fICOMMAND\fR [\fIARGS\fR \&...] .RS 4 Replaces all exact occurrences of \fIIDENTIFIER\fR in \fICOMMAND\fR and its \fIARGS\fR by the value of the \fIATTRIBUTE\fR\&. Note that the \fICOMMAND\fR also is replaced by the attribute value if it equals \fIIDENTIFIER\fR\&. The replaced command with its arguments then is executed\&. Example: .sp .RS 4 .ie n \{\ \h'-04'\(bu\h'+03'\c .\} .el \{\ .sp -1 .IP \(bu 2.3 .\} substitute MYTITLE clients\&.focus\&.title echo MYTITLE Prints the title of the currently focused window\&. .RE .RE .PP sprintf \fIIDENTIFIER\fR \fIFORMAT\fR [\fIATTRIBUTES\fR \&...] \fICOMMAND\fR [\fIARGS\fR \&...] .RS 4 Replaces all exact occurrences of \fIIDENTIFIER\fR in \fICOMMAND\fR and its \fIARGS\fR by the string specified by \fIFORMAT\fR\&. Each %s in \fIFORMAT\fR stands for the value of the next attribute in \fIATTRIBUTES\fR, similar to the \fBprintf\fR(1) command\&. The replaced command with its arguments then is executed\&. Examples: .sp .RS 4 .ie n \{\ \h'-04'\(bu\h'+03'\c .\} .el \{\ .sp -1 .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 .sp .RS 4 .ie n \{\ \h'-04'\(bu\h'+03'\c .\} .el \{\ .sp -1 .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 .RS 4 .ie n \{\ \h'-04'\(bu\h'+03'\c .\} .el \{\ .sp -1 .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 .RS 4 .ie n \{\ \h'-04'\(bu\h'+03'\c .\} .el \{\ .sp -1 .IP \(bu 2.3 .\} sprintf l somelongstring echo l l l Prints somelongstring three times, separated by spaces\&. .RE .RE .PP mktemp [\fBbool\fR|\fBint\fR|\fBstring\fR|\fBuint\fR] \fIIDENTIFIER\fR \fICOMMAND\fR [\fIARGS\fR \&...] .RS 4 Creates a temporary attribute with the given type and replaces all occurrences of \fIIDENTIFIER\fR in \fICOMMAND\fR and \fIARGS\fR by by the path of the temporary attribute\&. The replaced command with its arguments is executed then\&. The exit status of \fICOMMAND\fR is returned\&. .RE .PP compare \fIATTRIBUTE\fR \fIOPERATOR\fR \fIVALUE\fR .RS 4 Compares the value of \fIATTRIBUTE\fR with \fIVALUE\fR using the comparation method \fIOPERATOR\fR\&. If the comparation succeeds, it returns 0, else 1\&. The operators are: .sp .RS 4 .ie n \{\ \h'-04'\(bu\h'+03'\c .\} .el \{\ .sp -1 .IP \(bu 2.3 .\} \fB=\fR: \fIATTRIBUTE\fR\*(Aqs value equals \fIVALUE\fR .RE .sp .RS 4 .ie n \{\ \h'-04'\(bu\h'+03'\c .\} .el \{\ .sp -1 .IP \(bu 2.3 .\} \fB!=\fR: \fIATTRIBUTE\fR\*(Aqs value does not equal \fIVALUE\fR .RE .sp .RS 4 .ie n \{\ \h'-04'\(bu\h'+03'\c .\} .el \{\ .sp -1 .IP \(bu 2.3 .\} \fBle\fR: \fIATTRIBUTE\fR\*(Aqs value <= \fIVALUE\fR .RE .sp .RS 4 .ie n \{\ \h'-04'\(bu\h'+03'\c .\} .el \{\ .sp -1 .IP \(bu 2.3 .\} \fBlt\fR: \fIATTRIBUTE\fR\*(Aqs value < \fIVALUE\fR .RE .sp .RS 4 .ie n \{\ \h'-04'\(bu\h'+03'\c .\} .el \{\ .sp -1 .IP \(bu 2.3 .\} \fBge\fR: \fIATTRIBUTE\fR\*(Aqs value >= \fIVALUE\fR .RE .sp .RS 4 .ie n \{\ \h'-04'\(bu\h'+03'\c .\} .el \{\ .sp -1 .IP \(bu 2.3 .\} \fBgt\fR: \fIATTRIBUTE\fR\*(Aqs value > \fIVALUE\fR .RE .RE .PP .RS 4 The \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: .sp .if n \{\ .RS 4 .\} .nf substitute FC tags\&.focus\&.frame_count \e compare tags\&.focus\&.client_count gt FC .fi .if n \{\ .RE .\} .sp It returns success if there are more clients on the focused tag than frames\&. .RE .PP getenv \fINAME\fR .RS 4 Gets the value of the environment variable \fINAME\fR\&. .RE .PP setenv \fINAME\fR \fIVALUE\fR .RS 4 Set the value of the environment variable \fINAME\fR to \fIVALUE\fR\&. .RE .PP unsetenv \fINAME\fR .RS 4 Unsets the environment variable \fINAME\fR\&. .RE .SH "SETTINGS" .sp Settings configure the behaviour of herbstluftwm and can be controlled via the \fIset\fR, \fIget\fR and \fItoggle\fR commands\&. There are two types of settings: Strings and integer values\&. An integer value is set, if its value is 1 or another value unequal to 0\&. An integer value is unset, if its value is 0\&. .PP frame_gap (Integer) .RS 4 The gap between frames in the tiling mode\&. .RE .PP frame_padding (Integer) .RS 4 The padding within a frame in the tiling mode, i\&.e\&. the space between the border of a frame and the windows within it\&. .RE .PP window_gap (Integer) .RS 4 The gap between windows within one frame in the tiling mode\&. .RE .PP snap_distance (Integer) .RS 4 If a client is dragged in floating mode, then it snaps to neighbour clients if the distance between them is smaller then snap_distance\&. .RE .PP snap_gap (Integer) .RS 4 Specifies the remaining gap if a dragged client snaps to an edge in floating mode\&. If snap_gap is set to 0, no gap will remain\&. .RE .PP mouse_recenter_gap (Integer) .RS 4 Specifies the gap around a monitor\&. If the monitor is selected and the mouse position would be restored into this gap, it is set to the center of the monitor\&. This is useful, when the monitor was left via mouse movement, but is reselected by keyboard\&. If the gap is 0 (default), the mouse is never recentered\&. .RE .PP frame_border_active_color (String/Color) .RS 4 The border color of a focused frame\&. .RE .PP frame_border_normal_color (String/Color) .RS 4 The border color of an unfocused frame\&. .RE .PP frame_border_inner_color (String/Color) .RS 4 The color of the inner border of a frame\&. .RE .PP frame_bg_active_color (String/Color) .RS 4 The fill color of a focused frame\&. .RE .PP frame_bg_normal_color (String/Color) .RS 4 The fill color of an unfocused frame (It is only visible if always_show_frame is set)\&. .RE .PP frame_bg_transparent (Integer) .RS 4 If set, the background of frames are transparent\&. That means a rectangle is cut out frome the inner such that only the frame border and a stripe of width \fIframe_transparent_width\fR can be seen\&. Use \fIframe_active_opacity\fR and \fIframe_normal_opacity\fR for real transparency\&. .RE .PP frame_transparent_width (Integer) .RS 4 Specifies the width of the remaining frame colored with \fIframe_bg_active_color\fR if \fIframe_bg_transparent\fR is set\&. .RE .PP frame_border_width (Integer) .RS 4 Border width of a frame\&. .RE .PP frame_border_inner_width (Integer) .RS 4 The width of the inner border of a frame\&. Must be less than frame_border_width, since it does not add to the frame border width but is a part of it\&. .RE .PP focus_crosses_monitor_boundaries (Integer) .RS 4 If set, the focus command crosses monitor boundaries\&. If there is no client in the direction given to focus, then the monitor in the specified direction is focused\&. .RE .PP raise_on_focus (Integer) .RS 4 If set, a window is raised if it is focused\&. The value of this setting is only used in floating mode\&. .RE .PP raise_on_focus_temporarily (Integer) .RS 4 If set, a window is raised temporarily if it is focused on its tag\&. Temporarily in this case means that the window will return to its previous stacking position if another window is focused\&. .RE .PP raise_on_click (Integer) .RS 4 If set, a window is raised if it is clicked\&. The value of this setting is only noticed in floating mode\&. .RE .PP window_border_width (Integer) .RS 4 Border width of a window\&. .RE .PP window_border_inner_width (Integer) .RS 4 The width of the inner border of a window\&. Must be less than window_border_width, since it does not add to the window border width but is a part of it\&. .RE .PP window_border_active_color (String/Color) .RS 4 Border color of a focused window\&. .RE .PP window_border_normal_color (String/Color) .RS 4 Border color of an unfocused window\&. .RE .PP window_border_urgent_color (String/Color) .RS 4 Border color of an unfocused but urgent window\&. .RE .PP window_border_inner_color (String/Color) .RS 4 Color of the inner border of a window\&. .RE .PP always_show_frame (Integer) .RS 4 If set, all frames are displayed\&. If unset, only frames with focus or with windows in it are displayed\&. .RE .PP frame_active_opacity (Integer) .RS 4 Focused frame opacity in percent\&. Requires a running compositing manager to take actual effect\&. .RE .PP frame_normal_opacity (Integer) .RS 4 Unfocused frame opacity in percent\&. Requires a running compositing manager to take actual effect\&. .RE .PP default_frame_layout (Integer) .RS 4 Index of the frame layout, which is used if a new frame is created (by split or on a new tag)\&. For a list of valid indices and their meanings, check the list of layout algorithms above\&. .RE .PP default_direction_external_only (Integer) .RS 4 This setting controls the behaviour of focus and shift if no \fI\-e\fR or \fI\-i\fR argument is given\&. if set, then focus and shift changes the focused frame even if there are other clients in this frame in the specified \fIDIRECTION\fR\&. Else, a client within current frame is selected if it is in the specified \fIDIRECTION\fR\&. .RE .PP gapless_grid (Integer) .RS 4 This setting affects the size of the last client in a frame that is arranged by grid layout\&. If set, then the last client always fills the gap within this frame\&. If unset, then the last client has the same size as all other clients in this frame\&. .RE .PP smart_frame_surroundings (Integer) .RS 4 If set, frame borders and gaps will be removed when there\(cqs no ambiguity regarding the focused frame\&. .RE .PP smart_window_surroundings (Integer) .RS 4 If set, window borders and gaps will be removed and minimal when there\(cqs no ambiguity regarding the focused window\&. This minimal window decoration can be configured by the theme\&.minimal object\&. .RE .PP focus_follows_mouse (Integer) .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 focus_stealing_prevention (Integer) .RS 4 If set, only pagers and taskbars are allowed to change the focus\&. If unset, all applications can request a focus change\&. .RE .PP monitors_locked (Integer) .RS 4 If greater than 0, then the clients on all monitors aren\(cqt moved or resized anymore\&. If it is set to 0, then the arranging of monitors is enabled again, and all monitors are rearranged if their content has changed in the meantime\&. You should not change this setting manually due to concurrency issues; use the commands \fBlock\fR and \fBunlock\fR instead\&. .RE .PP swap_monitors_to_get_tag (Integer) .RS 4 If set: If you want to view a tag, that already is viewed on another monitor, then the monitor contents will be swapped and you see the wanted tag on the focused monitor\&. If not set, the other monitor is focused if it shows the desired tag\&. .RE .PP auto_detect_monitors (Integer) .RS 4 If set, detect_monitors is automatically executed every time a monitor is connected, disconnected or resized\&. .RE .PP tree_style (String) .RS 4 It contains the chars that are used to print a nice ascii tree\&. It must contain at least 8 characters\&. e\&.g\&. X|:#+*\-\&. produces a tree like: .sp .if n \{\ .RS 4 .\} .nf X\-\&.root #\-\&. child 0 | #\-* child 01 | +\-* child 02 +\-\&. child 1 : #\-* child 10 : +\-* child 01 .fi .if n \{\ .RE .\} .sp Useful values for \fItree_style\fR are: ╾│ ├└╼─┐ or \-| |\*(Aq\-\-\&. or ╾│ ├╰╼─╮\&. .RE .PP wmname (String) .RS 4 It controls the value of the _NET_WM_NAME property on the root window, which specifies the name of the running window manager\&. The value of this setting is not updated if the actual _NET_WM_NAME property on the root window is changed externally\&. Example usage: .sp .RS 4 .ie n \{\ \h'-04'\(bu\h'+03'\c .\} .el \{\ .sp -1 .IP \(bu 2.3 .\} cycle_value wmname herbstluftwm LG3D .RE .RE .PP pseudotile_center_threshold (Int) .RS 4 If greater than 0, it specifies the least distance between a centered pseudotile window and the border of the frame or tile it is assigned to\&. If this distance is lower than \fIpseudotile_center_threshold\fR, it is aligned to the top left of the client\(cqs tile\&. .RE .PP update_dragged_clients (Int) .RS 4 If set, a client\(cqs window content is resized immediately during resizing it with the mouse\&. If unset, the client\(cqs content is resized after the mouse button are released\&. .RE .SH "RULES" .sp Rules are used to change default properties for certain clients when they appear\&. Each rule matches against a certain subset of all clients and defines a set of properties for them (called \fICONSEQUENCE\fRs)\&. A rule can be defined with this command: .sp rule [[\-\-]\fIFLAG\fR|[\-\-]\fILABEL\fR|[\-\-]\fICONDITION\fR|[\-\-]\fICONSEQUENCE\fR \&...] .sp Each rule consists of a list of \fIFLAG\fRs, \fICONDITION\fRs, \fICONSEQUENCE\fRs and, optionally, a \fILABEL\fR\&. (each of them can be optionally prefixed with two dashes (\-\-) to provide a more \fBiptables\fR(8)\-like feeling)\&. .sp Each rule can be given a custom label by specifying the \fILABEL\fR property: .sp .RS 4 .ie n \{\ \h'-04'\(bu\h'+03'\c .\} .el \{\ .sp -1 .IP \(bu 2.3 .\} [\-\-]label=\fIVALUE\fR .RE .sp If multiple labels are specified, the last one in the list will be applied\&. If no label is given, then the rule will be given an integer name that represents the index of the rule since the last \fIunrule \-F\fR command (which is triggered in the default autostart)\&. .if n \{\ .sp .\} .RS 4 .it 1 an-trap .nr an-no-space-flag 1 .nr an-break-flag 1 .br .ps +1 \fBTip\fR .ps -1 .br .sp Rule labels default to an incremental index\&. These default labels are unique, unless you assign a different rule a custom integer \fILABEL\fR\&. Default labels can be captured with the \fIprintlabel\fR flag\&. .sp .5v .RE .sp If a new client appears, herbstluftwm tries to apply each rule to this new client as follows: If each \fICONDITION\fR of this rule matches against this client, then every \fICONSEQUENCE\fR is executed\&. (If there are no conditions given, then this rule is executed for each client) .sp Each \fICONDITION\fR consists of a \fIproperty\fR name, an operator and a \fIvalue\fR\&. Valid operators are: .sp .RS 4 .ie n \{\ \h'-04'\(bu\h'+03'\c .\} .el \{\ .sp -1 .IP \(bu 2.3 .\} ~ matches if client\(cqs \fIproperty\fR is matched by the regex \fIvalue\fR\&. .RE .sp .RS 4 .ie n \{\ \h'-04'\(bu\h'+03'\c .\} .el \{\ .sp -1 .IP \(bu 2.3 .\} = matches if client\(cqs \fIproperly\fR string is equal to \fIvalue\fR\&. .RE .sp Valid \fIproperties\fR are: .PP instance .RS 4 the first entry in client\(cqs WM_CLASS\&. .RE .PP class .RS 4 the second entry in client\(cqs WM_CLASS\&. .RE .PP title .RS 4 client\(cqs window title\&. .RE .PP pid .RS 4 the client\(cqs process id (Warning: the pid is not available for every client\&. This only matches if the client sets _NET_WM_PID to the pid itself)\&. .RE .PP maxage .RS 4 matches if the age of the rule measured in seconds does not exceed \fIvalue\fR\&. This condition only can be used with the = operator\&. If maxage already is exceeded (and never will match again), then this rule is removed\&. (With this you can build rules that only live for a certain time\&.) .RE .PP windowtype .RS 4 matches the _NET_WM_WINDOW_TYPE property of a window\&. .RE .PP windowrole .RS 4 matches the WM_WINDOW_ROLE property of a window if it is set by the window\&. .RE .sp Each \fICONSEQUENCE\fR consists of a \fINAME\fR=\fIVALUE\fR pair\&. Valid \fINAMES\fR are: .PP tag .RS 4 moves the client to tag \fIVALUE\fR\&. .RE .PP monitor .RS 4 moves the client to the tag on monitor \fIVALUE\fR\&. If the tag consequence was also specified, and switchtag is set for the client, move the client to that tag, then display that tag on monitor \fIVALUE\fR\&. If the tag consequence was specified, but switchtag was not, ignore this consequence\&. .RE .PP focus .RS 4 decides whether the client gets the input focus on his tag\&. The default is \fBoff\fR\&. \fIVALUE\fR can be \fBon\fR, \fBoff\fR or \fBtoggle\fR\&. .RE .PP switchtag .RS 4 if focus is activated and the client is put to a not focused tag, then switchtag tells whether the client\(cqs tag will be shown or not\&. If the tag is shown on any monitor but is not focused, the client\(cqs tag only is brought to the current monitor if \fBswap_monitors_to_get_tag\fR is activated\&. \fIVALUE\fR can be \fBon\fR, \fBoff\fR or \fBtoggle\fR\&. .RE .PP manage .RS 4 decides whether the client will be managed or not\&. The default is \fBon\fR\&. \fIVALUE\fR can be \fBon\fR, \fBoff\fR or \fBtoggle\fR\&. .RE .PP index .RS 4 moves the window to a specified index in the tree\&. \fIVALUE\fR is a \fBframe index\fR\&. .RE .PP pseudotile .RS 4 sets the pseudotile state of the client\&. \fIVALUE\fR can be \fBon\fR, \fBoff\fR or \fBtoggle\fR\&. .RE .PP ewmhrequests .RS 4 sets whether the window state (the fullscreen state and the demands attention flag) can be changed by the application via ewmh itself\&. This does not affect the initial fullscreen state requested by the window\&. \fIVALUE\fR can be \fBon\fR, \fBoff\fR or \fBtoggle\fR, it defaults to \fBon\fR\&. .RE .PP ewmhnotify .RS 4 sets whether hlwm should let the client know about EMWH changes (currently only the fullscreen state)\&. If this is set, applications do not change to their fullscreen\-mode while still being fullscreen\&. \fIVALUE\fR can be \fBon\fR, \fBoff\fR or \fBtoggle\fR, it defaults to \fBon\fR\&. .RE .PP fullscreen .RS 4 sets the fullscreen flag of the client\&. \fIVALUE\fR can be \fBon\fR, \fBoff\fR or \fBtoggle\fR\&. .RE .PP hook .RS 4 emits the custom hook 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 .PP keymask .RS 4 Sets the keymask for an client\&. A keymask is an regular expression that is matched against the string represenation (see list_keybinds)\&. If it matches the keybinding is active when this client is focused, otherwise it is disabled\&. The default keymask is an empty string (""), which does not disable any keybinding\&. .RE .sp A rule\(cqs behaviour can be configured by some special \fIFLAGS\fR: .sp .RS 4 .ie n \{\ \h'-04'\(bu\h'+03'\c .\} .el \{\ .sp -1 .IP \(bu 2.3 .\} not: negates the next \fICONDITION\fR\&. .RE .sp .RS 4 .ie n \{\ \h'-04'\(bu\h'+03'\c .\} .el \{\ .sp -1 .IP \(bu 2.3 .\} !: same as not\&. .RE .sp .RS 4 .ie n \{\ \h'-04'\(bu\h'+03'\c .\} .el \{\ .sp -1 .IP \(bu 2.3 .\} once: only apply this rule once (and delete it afterwards)\&. .RE .sp .RS 4 .ie n \{\ \h'-04'\(bu\h'+03'\c .\} .el \{\ .sp -1 .IP \(bu 2.3 .\} printlabel: prints the label of the newly created rule to stdout\&. .RE .sp .RS 4 .ie n \{\ \h'-04'\(bu\h'+03'\c .\} .el \{\ .sp -1 .IP \(bu 2.3 .\} prepend: prepend the rule to the list of rules instead of appending it\&. So its consequences may be overwritten by already existing rules\&. .RE .sp Examples: .sp .RS 4 .ie n \{\ \h'-04'\(bu\h'+03'\c .\} .el \{\ .sp -1 .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 .RS 4 .ie n \{\ \h'-04'\(bu\h'+03'\c .\} .el \{\ .sp -1 .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 Term\&. .RE .sp .RS 4 .ie n \{\ \h'-04'\(bu\h'+03'\c .\} .el \{\ .sp -1 .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 .RS 4 .ie n \{\ \h'-04'\(bu\h'+03'\c .\} .el \{\ .sp -1 .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\&. .RE .SH "WINDOW IDS" .sp Several commands accept a window as reference, e\&.g\&. close\&. The syntax is as follows: .sp .RS 4 .ie n \{\ \h'-04'\(bu\h'+03'\c .\} .el \{\ .sp -1 .IP \(bu 2.3 .\} an empty string \(em or missing argument \(em references the currently focused window\&. .RE .sp .RS 4 .ie n \{\ \h'-04'\(bu\h'+03'\c .\} .el \{\ .sp -1 .IP \(bu 2.3 .\} urgent references some window that is urgent\&. .RE .sp .RS 4 .ie n \{\ \h'-04'\(bu\h'+03'\c .\} .el \{\ .sp -1 .IP \(bu 2.3 .\} 0x\fIHEXID\fR \(em where \fIHEXID\fR is some hexadecimal number \(em references the window with hexadecimal X11 window id is \fIHEXID\fR\&. .RE .sp .RS 4 .ie n \{\ \h'-04'\(bu\h'+03'\c .\} .el \{\ .sp -1 .IP \(bu 2.3 .\} \fIDECID\fR \(em where \fIDECID\fR is some decimal number \(em references the window with the decimal X11 window id \fIDECID\fR\&. .RE .SH "OBJECTS" .if n \{\ .sp .\} .RS 4 .it 1 an-trap .nr an-no-space-flag 1 .nr an-break-flag 1 .br .ps +1 \fBWarning\fR .ps -1 .br .sp The object tree is not stable yet, i\&.e\&. its interface may change until the next stable release\&. So check this documentation again after upgrading the next time\&. .sp .5v .RE .sp The object tree is a collection of objects with attributes similar to /sys known from the Linux kernel\&. Many entities (like tags, monitors, clients, \&...) have objects to access their attributes directly\&. The tree is printed by the object_tree command and looks more or less as follows: .sp .if n \{\ .RS 4 .\} .nf $ herbstclient object_tree ╾─┐ ├─┐ tags │ ├─┐ by\-name │ │ ├─╼ 1 │ │ \&.\&.\&. │ │ └─╼ 9 │ └─╼ focus ├─┐ clients │ ├─╼ 0x1400022 │ └─╼ focus └─┐ monitors ├─╼ by\-name └─╼ focus .fi .if n \{\ .RE .\} .sp To print a subtree starting at a certain object, pass the \fIPATH\fR of the object to object_tree\&. The object \fIPATH\fR is the path using the separator \&. (dot), e\&.g\&. tags\&.by\-name: .sp .if n \{\ .RS 4 .\} .nf $ herbstclient object_tree tags\&.by\-name\&. ╾─┐ tags\&.by\-name\&. ├─╼ 1 ├─╼ 2 \&.\&.\&. └─╼ 9 .fi .if n \{\ .RE .\} .sp To query all attributes and children of a object, pass its \fIPATH\fR to attr: .sp .if n \{\ .RS 4 .\} .nf $ herbstclient attr tags\&. 2 children: by\-name\&. focus\&. 1 attributes: \&.\-\-\-\- type | \&.\-\- writeable V V u \- count = 9 $ herbstclient attr tags\&.focus\&. 0 children\&. 6 attributes: \&.\-\-\-\- type | \&.\-\- writeable V V s w name = "1" b w floating = false i \- frame_count = 2 i \- client_count = 1 i \- curframe_windex = 0 i \- curframe_wcount = 1 .fi .if n \{\ .RE .\} .sp This already gives an intuition of the output: attr first lists the names of the child objects and then all attributes, telling for each attribute: .sp .RS 4 .ie n \{\ \h'-04'\(bu\h'+03'\c .\} .el \{\ .sp -1 .IP \(bu 2.3 .\} its type .sp .RS 4 .ie n \{\ \h'-04'\(bu\h'+03'\c .\} .el \{\ .sp -1 .IP \(bu 2.3 .\} s for string .RE .sp .RS 4 .ie n \{\ \h'-04'\(bu\h'+03'\c .\} .el \{\ .sp -1 .IP \(bu 2.3 .\} i for integer .RE .sp .RS 4 .ie n \{\ \h'-04'\(bu\h'+03'\c .\} .el \{\ .sp -1 .IP \(bu 2.3 .\} b for boolean .RE .sp .RS 4 .ie n \{\ \h'-04'\(bu\h'+03'\c .\} .el \{\ .sp -1 .IP \(bu 2.3 .\} u for unsigned integer .RE .RE .sp .RS 4 .ie n \{\ \h'-04'\(bu\h'+03'\c .\} .el \{\ .sp -1 .IP \(bu 2.3 .\} if it is writeable by the user: w if yes, \- else\&. .RE .sp .RS 4 .ie n \{\ \h'-04'\(bu\h'+03'\c .\} .el \{\ .sp -1 .IP \(bu 2.3 .\} the name of the attribute .RE .sp .RS 4 .ie n \{\ \h'-04'\(bu\h'+03'\c .\} .el \{\ .sp -1 .IP \(bu 2.3 .\} its current value (only quoted for strings) .RE .sp To get the unquoted value of a certain attribute, address the attribute using the same syntax as for object paths and pass it to attr or get_attr: .sp .if n \{\ .RS 4 .\} .nf $ herbstclient attr clients\&.focus\&.title herbstluftwm\&.txt = (~/dev/c/herbstluftwm/doc) \- VIM $ herbstclient get_attr clients\&.focus\&.title herbstluftwm\&.txt = (~/dev/c/herbstluftwm/doc) \- VIM .fi .if n \{\ .RE .\} .sp To change a writeable attribute value pass the new value to attr or to set_attr: .sp .if n \{\ .RS 4 .\} .nf $ herbstclient attr tags\&.focus\&.floating false $ herbstclient attr tags\&.focus\&.floating true $ herbstclient attr tags\&.focus\&.floating true $ herbstclient set_attr tags\&.focus\&.floating false $ herbstclient attr tags\&.focus\&.floating false .fi .if n \{\ .RE .\} .sp Just look around to get a feeling what is there\&. The detailed tree content is listed as follows: .sp .RS 4 .ie n \{\ \h'-04'\(bu\h'+03'\c .\} .el \{\ .sp -1 .IP \(bu 2.3 .\} tags: subtree for tags\&. .TS allbox tab(:); lt lt. T{ u \- count T}:T{ number of tags T} .TE .sp 1 .sp .RS 4 .ie n \{\ \h'-04'\(bu\h'+03'\c .\} .el \{\ .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 .ie n \{\ \h'-04'\(bu\h'+03'\c .\} .el \{\ .sp -1 .IP \(bu 2.3 .\} \fITAG\fR: an object for each tag with the name \fITAG\fR .TS allbox tab(:); lt lt lt lt lt lt lt lt lt lt lt lt lt lt. T{ s w name T}:T{ name of the tag T} T{ b w floating T}:T{ if it is in floating mode T} T{ i \- index T}:T{ index of this tag T} T{ i \- frame_count T}:T{ number of frames T} T{ i \- client_count T}:T{ number of clients on this tag T} T{ i \- curframe_windex T}:T{ index of the focused client in the select frame T} T{ i \- curframe_wcount T}:T{ number of clients in the selected frame T} .TE .sp 1 .RE .RE .sp .RS 4 .ie n \{\ \h'-04'\(bu\h'+03'\c .\} .el \{\ .sp -1 .IP \(bu 2.3 .\} focus: the object of the focused tag .RE .RE .sp .RS 4 .ie n \{\ \h'-04'\(bu\h'+03'\c .\} .el \{\ .sp -1 .IP \(bu 2.3 .\} clients .sp .RS 4 .ie n \{\ \h'-04'\(bu\h'+03'\c .\} .el \{\ .sp -1 .IP \(bu 2.3 .\} \fIWINID\fR: a object for each client with its \fIWINID\fR .TS allbox tab(:); lt lt lt lt lt lt lt lt lt lt lt lt lt lt lt lt lt lt lt lt lt lt lt lt lt lt. T{ s \- winid T}:T{ its window id T} T{ s \- title T}:T{ its window title T} T{ s \- tag T}:T{ the tag it\(cqs currently on T} T{ i \- pid T}:T{ the process id of it (\-1 if unset) T} T{ s \- class T}:T{ the class of it (second entry in WM_CLASS) T} T{ s \- instance T}:T{ the instance of it (first entry in WM_CLASS) T} T{ b w fullscreen T}:T{ T} T{ b w pseudotile T}:T{ T} T{ b w ewmhrequests T}:T{ if ewmh requests are permitted for this client T} T{ b w ewmhnotify T}:T{ if the client is told about its state via ewmh T} T{ b w urgent T}:T{ its urgent state T} T{ b w sizehints_tiling T}:T{ if sizehints for this client should be respected in tiling mode T} T{ b w sizehints_flaoting T}:T{ if sizehints for this client should be respected in floating mode T} .TE .sp 1 .RE .sp .RS 4 .ie n \{\ \h'-04'\(bu\h'+03'\c .\} .el \{\ .sp -1 .IP \(bu 2.3 .\} focus: the object of the focused client, if any .RE .sp .RS 4 .ie n \{\ \h'-04'\(bu\h'+03'\c .\} .el \{\ .sp -1 .IP \(bu 2.3 .\} dragged: the object of a client which is dragged by the mouse, if any\&. See the documentation of the mousebind command for examples\&. .RE .RE .sp .RS 4 .ie n \{\ \h'-04'\(bu\h'+03'\c .\} .el \{\ .sp -1 .IP \(bu 2.3 .\} monitors .TS allbox tab(:); lt lt. T{ u \- count T}:T{ number of monitors T} .TE .sp 1 .sp .RS 4 .ie n \{\ \h'-04'\(bu\h'+03'\c .\} .el \{\ .sp -1 .IP \(bu 2.3 .\} \fIINDEX\fR: a object for each monitor with its \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 .ie n \{\ \h'-04'\(bu\h'+03'\c .\} .el \{\ .sp -1 .IP \(bu 2.3 .\} \fINAME\fR: a object for each named monitor .TS allbox tab(:); lt lt lt lt lt lt lt lt. T{ s \- name T}:T{ its name T} T{ i \- index T}:T{ its index T} T{ s \- tag T}:T{ the tag currently viewed on it T} T{ b \- lock_tag T}:T{ T} .TE .sp 1 .RE .RE .sp .RS 4 .ie n \{\ \h'-04'\(bu\h'+03'\c .\} .el \{\ .sp -1 .IP \(bu 2.3 .\} focus: the object of the focused monitor .RE .RE .sp .RS 4 .ie n \{\ \h'-04'\(bu\h'+03'\c .\} .el \{\ .sp -1 .IP \(bu 2.3 .\} settings has an attribute for each setting\&. See \fBSETTINGS\fR for a list\&. .RE .sp .RS 4 .ie n \{\ \h'-04'\(bu\h'+03'\c .\} .el \{\ .sp -1 .IP \(bu 2.3 .\} theme has attributes to configure the window decorations\&. theme and many of its child objects have the following attributes .TS allbox tab(:); lt lt lt lt lt lt lt lt lt lt lt lt lt lt lt lt lt lt lt lt lt lt lt lt. T{ i w border_width T}:T{ the base width of the border T} T{ i w padding_top T}:T{ additional border width on the top T} T{ i w padding_right T}:T{ on the right T} T{ i w padding_bottom T}:T{ on the bottom T} T{ i w padding_left T}:T{ and on the left of the border T} T{ c w color T}:T{ the basic background color of the border T} T{ i w inner_width T}:T{ width of the border around the clients content T} T{ c w inner_color T}:T{ its color T} T{ i w outer_width T}:T{ width of an additional border close to the edge T} T{ c w outer_color T}:T{ its color T} T{ c w background_color T}:T{ color behind window contents visible on resize T} T{ s w reset T}:T{ Writing this resets all attributes to a default value T} .TE .sp 1 .sp .if n \{\ .RS 4 .\} .nf inner_color/inner_width ╻ outer_color/outer_width │ ╻ │ │ ┌────╴│╶─────────────────┷─────┐ ⎫ border_width │ │ color │ ⎬ + │ ┌──┷─────────────────────┐ │ ⎭ padding_top │ │====================\&.\&.\&.\&.│ │ │ │== window content ==\&.\&.\&.\&.│ │ │ │====================\&.\&.╾──────── background_color │ │\&.\&.\&.\&.\&.\&.\&.\&.\&.\&.\&.\&.\&.\&.\&.\&.\&.\&.\&.\&.\&.\&.\&.\&.│ │ │ └────────────────────────┘ │ ⎱ border_width + └──────────────────────────────┘ ⎰ padding_bottom .fi .if n \{\ .RE .\} .sp Setting an attribute of the theme object just propagates the value to the respective attribute of the tiling and the floating object\&. .sp .RS 4 .ie n \{\ \h'-04'\(bu\h'+03'\c .\} .el \{\ .sp -1 .IP \(bu 2.3 .\} tiling configures the decoration of tiled clients, setting one of its attributes propagates the respective attribute of the active, normal and urgent child objects\&. .sp .RS 4 .ie n \{\ \h'-04'\(bu\h'+03'\c .\} .el \{\ .sp -1 .IP \(bu 2.3 .\} active configures the decoration of focused and tiled clients .RE .sp .RS 4 .ie n \{\ \h'-04'\(bu\h'+03'\c .\} .el \{\ .sp -1 .IP \(bu 2.3 .\} normal configures the decoration of unfocused and tiled clients .RE .sp .RS 4 .ie n \{\ \h'-04'\(bu\h'+03'\c .\} .el \{\ .sp -1 .IP \(bu 2.3 .\} urgent configures the decoration of urgent and tiled clients .RE .RE .sp .RS 4 .ie n \{\ \h'-04'\(bu\h'+03'\c .\} .el \{\ .sp -1 .IP \(bu 2.3 .\} floating behaves analogously to tiling .RE .sp .RS 4 .ie n \{\ \h'-04'\(bu\h'+03'\c .\} .el \{\ .sp -1 .IP \(bu 2.3 .\} minimal behaves analogously to tiling and configures those minimal decorations triggered by smart_window_surroundings\&. .RE .sp .RS 4 .ie n \{\ \h'-04'\(bu\h'+03'\c .\} .el \{\ .sp -1 .IP \(bu 2.3 .\} active propagates the attribute values to tiling\&.active and floating\&.active .RE .sp .RS 4 .ie n \{\ \h'-04'\(bu\h'+03'\c .\} .el \{\ .sp -1 .IP \(bu 2.3 .\} normal propagates the attribute values to tiling\&.normal and floating\&.normal .RE .sp .RS 4 .ie n \{\ \h'-04'\(bu\h'+03'\c .\} .el \{\ .sp -1 .IP \(bu 2.3 .\} urgent propagates the attribute values to tiling\&.urgent and floating\&.urgent .RE .RE .SH "AUTOSTART FILE" .sp There is no configuration file but an autostart file, which is executed on startup\&. It is also executed on command \fIreload\fR\&. If not specified by the \fB\-\-autostart\fR argument, autostart file is located at \fI$XDG_CONFIG_HOME/herbstluftwm/autostart\fR or at \fI~/\&.config/herbstluftwm/autostart\fR\&. Normally it consists of a few \fBherbstclient\fR calls\&. If executing the autostart file in a user\(cqs home fails the global autostart file (mostly placed at /etc/xdg/herbstluftwm/autostart) is executed as a fallback\&. .sp For a quick install, copy the default autostart file to \fI~/\&.config/herbstluftwm/\fR\&. .SH "HOOKS" .sp On special events, herbstluftwm emits some hooks (with parameters)\&. You can receive or wait for them with \fBherbstclient\fR(1)\&. Also custom hooks can be emitted with the \fBemit_hook\fR command\&. The following hooks are emitted by herbstluftwm itself: .PP fullscreen [on|off] \fIWINID\fR \fISTATE\fR .RS 4 The fullscreen state of window \fIWINID\fR was changed to [on|off]\&. .RE .PP tag_changed \fITAG\fR \fIMONITOR\fR .RS 4 The tag \fITAG\fR was selected on \fIMONITOR\fR\&. .RE .PP focus_changed \fIWINID\fR \fITITLE\fR .RS 4 The window \fIWINID\fR was focused\&. Its window title is \fITITLE\fR\&. .RE .PP window_title_changed \fIWINID\fR \fITITLE\fR .RS 4 The title of the \fBfocused\fR window was changed\&. Its window id is \fIWINID\fR and its new title is \fITITLE\fR\&. .RE .PP tag_flags .RS 4 The flags (i\&.e\&. urgent or filled state) have been changed\&. .RE .PP tag_added \fITAG\fR .RS 4 A tag named \fITAG\fR was added\&. .RE .PP tag_removed \fITAG\fR .RS 4 The tag named \fITAG\fR was removed\&. .RE .PP urgent [on|off] \fIWINID\fR .RS 4 The urgent state of client with given \fIWINID\fR has been changed to [on|off]\&. .RE .PP rule \fINAME\fR \fIWINID\fR .RS 4 A window with the id \fIWINID\fR appeared which triggerd a rule with the consequence hook=\fINAME\fR\&. .RE .sp There are also other useful hooks, which never will be emitted by herbstluftwm itself, but which can be emitted with the \fBemit_hook\fR command: .PP quit_panel .RS 4 Tells a panel to quit\&. The default panel\&.sh quits on this hook\&. Many scripts are using this hook\&. .RE .PP reload .RS 4 Tells all daemons that the \fIautostart\fR file is reloaded \(em and tells them to quit\&. This hook \fBshould\fR be emitted in the first line of every \fIautostart\fR file\&. .RE .SH "STACKING" .sp Every tag has its own stack of clients that are on this tag\&. Similar to the EWMH specification each tag stack contains several layers, which are from top to bottom: .sp .RS 4 .ie n \{\ \h'-04'\(bu\h'+03'\c .\} .el \{\ .sp -1 .IP \(bu 2.3 .\} the focused client (if raise_on_focus_temporarily is enabled) .RE .sp .RS 4 .ie n \{\ \h'-04'\(bu\h'+03'\c .\} .el \{\ .sp -1 .IP \(bu 2.3 .\} clients in fullscreen .RE .sp .RS 4 .ie n \{\ \h'-04'\(bu\h'+03'\c .\} .el \{\ .sp -1 .IP \(bu 2.3 .\} normal clients .RE .sp .RS 4 .ie n \{\ \h'-04'\(bu\h'+03'\c .\} .el \{\ .sp -1 .IP \(bu 2.3 .\} frame decorations .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 .RS 4 Specifies the \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\&. .SH "BUGS" .sp See the \fBherbstluftwm\fR distribution BUGS file\&. .SH "COMMUNITY" .sp Feel free to join the IRC channel \fI#herbstluftwm\fR on \fIirc\&.freenode\&.net\fR\&. .SH "AUTHOR" .sp \fBherbstluftwm\fR was written by Thorsten Wißmann\&. All contributors are listed in the \fBherbstluftwm\fR distribution AUTHORS file\&. .SH "RESOURCES" .sp Homepage: http://herbstluftwm\&.org .sp Github page: http://github\&.com/herbstluftwm/herbstluftwm .sp Patch submission and bug reporting: .sp .if n \{\ .RS 4 .\} .nf hlwm@lists\&.herbstluftwm\&.org .fi .if n \{\ .RE .\} .SH "COPYING" .sp Copyright 2011\-2014 Thorsten Wißmann\&. All rights reserved\&. .sp This software is licensed under the "Simplified BSD License"\&. See LICENSE for details\&. herbstluftwm-0.7.0/doc/herbstluftwm-tutorial.70000644000175000001440000003203012654701663021265 0ustar thorstenusers'\" t .\" Title: herbstluftwm-tutorial .\" Author: [FIXME: author] [see http://docbook.sf.net/el/author] .\" Generator: DocBook XSL Stylesheets v1.79.1 .\" Date: 2016-02-04 .\" Manual: \ \& .\" Source: \ \& herbstluftwm 0.7.0\e \e(c179281\e) .\" Language: English .\" .TH "HERBSTLUFTWM\-TUTORI" "7" "2016\-02\-04" "\ \& herbstluftwm 0\&.7\&.0\e" "\ \&" .\" ----------------------------------------------------------------- .\" * Define some portability stuff .\" ----------------------------------------------------------------- .\" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ .\" http://bugs.debian.org/507673 .\" http://lists.gnu.org/archive/html/groff/2009-02/msg00013.html .\" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ .ie \n(.g .ds Aq \(aq .el .ds Aq ' .\" ----------------------------------------------------------------- .\" * set default formatting .\" ----------------------------------------------------------------- .\" disable hyphenation .nh .\" disable justification (adjust text to left margin only) .ad l .\" ----------------------------------------------------------------- .\" * MAIN CONTENT STARTS HERE * .\" ----------------------------------------------------------------- .SH "NAME" herbstluftwm-tutorial \- A tutorial introduction to herbstluftwm .SH "DESCRIPTION" .sp This tutorial explains how to create a basic herbstluftwm setup and introduces the major herbstluftwm features\&. This tutorial neither covers all features nor specifies the mentioned features entirely; see \fBherbstluftwm\fR(1) for a compact and more complete description\&. .sp This tutorial covers these topics: .sp .RS 4 .ie n \{\ \h'-04'\(bu\h'+03'\c .\} .el \{\ .sp -1 .IP \(bu 2.3 .\} Basic installation .RE .sp .RS 4 .ie n \{\ \h'-04'\(bu\h'+03'\c .\} .el \{\ .sp -1 .IP \(bu 2.3 .\} Usage of the client .RE .sp .RS 4 .ie n \{\ \h'-04'\(bu\h'+03'\c .\} .el \{\ .sp -1 .IP \(bu 2.3 .\} The tiling method .RE .sp .RS 4 .ie n \{\ \h'-04'\(bu\h'+03'\c .\} .el \{\ .sp -1 .IP \(bu 2.3 .\} Tags (or workspaces\&...) .RE .sp .RS 4 .ie n \{\ \h'-04'\(bu\h'+03'\c .\} .el \{\ .sp -1 .IP \(bu 2.3 .\} Multi\-Monitor handling .RE .SH "BASIC INSTALLATION" .sp This describes two alternate installation methods\&. In any case, you also have to install the dependencies\&. Beside the standard libraries (GLib, XLib) which are found on nearly any system, you should install dzen2 (as current as possible) which is needed by the default panel\&.sh\&. .SS "Via the package manager" .sp You always should prefer installing herbstluftwm via your package manager on your system\&. It should be called herbstluftwm\&. .sp After installing it, the default configuration file has to be copied to your home directory: .sp .if n \{\ .RS 4 .\} .nf mkdir \-p ~/\&.config/herbstluftwm cp /etc/xdg/herbstluftwm/autostart ~/\&.config/herbstluftwm/ .fi .if n \{\ .RE .\} .sp You also should activate the tab completion for herbstclient\&. In case of bash, you can either activate the tab completion in general or source the herbstclient\-completion from the bash_completion\&.d directory in your bashrc\&. In case of zsh the tab\-completion normally is activated already (if not, consider activating it)\&. .SS "Directly from git" .sp If there is no package for your platform or if you want to use the current git version, then you can pull directly from the main repository: .sp .if n \{\ .RS 4 .\} .nf git clone git://github\&.com/herbstluftwm/herbstluftwm cd herbstluftwm make # build the binaries # install files mkdir \-p ~/bin # you also have to put $HOME/bin to your path, e\&.g\&. by: echo \*(Aqexport PATH=$PATH:$HOME/bin\*(Aq >> ~/\&.bashrc # or to your zshrc, etc\&.\&.\&. ln \-s `pwd`/herbstluftwm ~/bin/ ln \-s `pwd`/herbstclient ~/bin/ # copy the configuration mkdir \-p ~/\&.config/herbstluftwm/ cp share/autostart ~/\&.config/herbstluftwm/ cp share/panel\&.sh ~/\&.config/herbstluftwm/ .fi .if n \{\ .RE .\} .sp .RS 4 .ie n \{\ \h'-04'\(bu\h'+03'\c .\} .el \{\ .sp -1 .IP \(bu 2.3 .\} If you are using bash, then source the bash completion file in your ~/\&.bashrc .sp .if n \{\ .RS 4 .\} .nf source path\-to/herbstluftwm/share/herbstclient\-completion .fi .if n \{\ .RE .\} .RE .sp .RS 4 .ie n \{\ \h'-04'\(bu\h'+03'\c .\} .el \{\ .sp -1 .IP \(bu 2.3 .\} If you are using zsh, then copy the share/_herbstclient file to the appropriate zsh\-completion directory\&. .RE .sp Each time there is an update, you have to do the following steps in your herbstluftwm directory: .sp .if n \{\ .RS 4 .\} .nf git pull make .fi .if n \{\ .RE .\} .SH "CONFIGURE HERBSTLUFTWM AS YOUR WINDOW MANAGER" .sp As usual you can define herbstluftwm as your window manager by either selecting herbstluftwm in your login manager or by starting it in your ~/\&.xinitrc, mostly by writing to your xinitrc (or \&.xsession on some systems): .sp .if n \{\ .RS 4 .\} .nf # start herbstluftwm in locked mode (it will be unlocked at the end of your # autostart) exec herbstluftwm \-\-locked .fi .if n \{\ .RE .\} .sp After logging in the next time, you will get a default herbstluftwm session\&. .SH "FIRST START" .sp After starting herbstluftwm, the screen is surrounded by a green frame initially, which indicates that there is only one large frame\&. A frame is a container where actual windows can be placed or which can be split into two frames\&. .sp Start an xterm by pressing Alt\-Return, which will fill your entire screen\&. .SH "USING THE CLIENT" .sp The only way to communicate to herbstluftwm is by using the client application called herbstclient\&. Its usual syntax is: herbstclient COMMAND [ARGUMENTS]\&. This calls a certain COMMAND within your running herbstluftwm instance\&. This causes some effect (which depends on the given COMMAND and ARGUMENTS), produces some output which is printed by herbstclient and lets herbstclient exit with a exit\-code (e\&.g\&. 0 for success) like many other UNIX tools: .sp .if n \{\ .RS 4 .\} .nf shell COMMANDS, ╲ COMMAND, ARGUMENTS ╲ ARGUMENTS ╭────────────╮ ╲ │ │ V │ V herbstclient herbstluftwm ╱ ^ │ ╱ output, │ │ ╱ exit\-code ╰────────────╯ V output, shell/terminal exit\-code .fi .if n \{\ .RE .\} .sp The most simple command only prints the herbstluftwm version: .sp .if n \{\ .RS 4 .\} .nf $ # lines prefixed with $ describes what to type, other lines describe the $ # typical output $ # Type: herc ve $ herbstclient version herbstluftwm 0\&.4\&.1 (built on Aug 30 2012) $ herbstclient set window_border_active_color red $ # now the window border turned red .fi .if n \{\ .RE .\} .sp The configuration of herbstluftwm only is done by calling commands via herbstclient\&. So the only configuration file is the autostart which is placed at ~/\&.config/herbstluftwm/ and which is a sequence of those herbstclient calls\&. .sp Open it in your favourite text editor and replace the Mod\-line by this to use the Super\-key (or also called Windows\-key) as the main modifier: .sp .if n \{\ .RS 4 .\} .nf # Mod=Mod1 # use alt as the main modifier Mod=Mod4 # use Super as the main modifier .fi .if n \{\ .RE .\} .sp After saving the autostart file, you have to reload the configuration: .sp .if n \{\ .RS 4 .\} .nf # the following line is identical to directly calling: # ~/\&.config/herbstluftwm/autostart herbstclient reload .fi .if n \{\ .RE .\} .sp Now you may notice that the red border color of your terminal turned green again, because the color is set in the default autostart\&. That\(cqs the typical configuration workflow: .sp .RS 4 .ie n \{\ \h'-04' 1.\h'+01'\c .\} .el \{\ .sp -1 .IP " 1." 4.2 .\} Try some new settings in the command line .RE .sp .RS 4 .ie n \{\ \h'-04' 2.\h'+01'\c .\} .el \{\ .sp -1 .IP " 2." 4.2 .\} Add them to the autostart file .RE .sp .RS 4 .ie n \{\ \h'-04' 3.\h'+01'\c .\} .el \{\ .sp -1 .IP " 3." 4.2 .\} Press Mod\-Shift\-r which calls the reload command or directly execute the autostart file from your shell to get the error messages if something went wrong\&. .RE .sp To learn more about herbstluftwm, just go through the man page line by line and check using the herbstluftwm(1) man page what it does\&. For a quick introduction to the central paradigms, continue reading this\&. .SH "TILING" .sp Initially there is one frame\&. Each frame has one of the two following possible types: .sp .RS 4 .ie n \{\ \h'-04' 1.\h'+01'\c .\} .el \{\ .sp -1 .IP " 1." 4.2 .\} It serves as a container for windows, i\&.e\&. it can hold zero up to arbitrarily many windows\&. Launch several more terminals to see what happens: If there are multiple windows in one frame, they are aligned below each other\&. To change this layout algorithm, press Mod\-space to cycle all the available layouting algorithms for the focused frame\&. .RE .sp .RS 4 .ie n \{\ \h'-04' 2.\h'+01'\c .\} .el \{\ .sp -1 .IP " 2." 4.2 .\} A frame also can be split into two subframes, which can be aligned next to or below each other\&. Press Mod\-o to split to an horizontal alignment\&. To navigate to the fresh frame right of the old one press Mod\-l\&. Press Mod\-u to split vertically\&. The intuitive navigation is: .sp .if n \{\ .RS 4 .\} .nf ⎧ h (or ←) ⎫ ⎧ left ⎪ j (or ↓) ⎪ means ⎪ down Mod + ⎨ k (or ↑) ⎬ ═══════> focus ⎨ up ⎩ l (or →) ⎭ ⎩ right .fi .if n \{\ .RE .\} .sp To undo splitting, you can remove a frame via Mod\-r\&. To shift some window from one frame to one of its neighbours, use the same keyboard shortcut while holding the Shift key pressed\&. It is not possible to resize single windows, only to resize frames\&. The according keyboard shortcut is the same while holding Control pressed\&. All in all it is: .sp .if n \{\ .RS 4 .\} .nf ⎧ h (or ←) ⎫ ⎧ left ⎧ ⎫ ⎪ j (or ↓) ⎪ means ⎧ focus frame ⎫ ⎪ down Mod + ⎨ Shift ⎬ + ⎨ k (or ↑) ⎬ ═════> ⎨ move window ⎬ ⎨ up ⎩ Control ⎭ ⎩ l (or →) ⎭ ⎩ resize frame ⎭ ⎩ right .fi .if n \{\ .RE .\} .RE .sp With this, you can define a custom layout\&. It can be printed via the layout command: .sp .if n \{\ .RS 4 .\} .nf $ herbstclient layout ╾─┐ horizontal 50% selection=1 ├─┐ vertical 70% selection=0 │ ├─╼ vertical: 0x1400009 │ └─╼ vertical: └─╼ max: 0x1a00009 [FOCUS] .fi .if n \{\ .RE .\} .sp Just play with it a bit to how it works\&. You also can permanently save the layout using the dump command: .sp .if n \{\ .RS 4 .\} .nf $ herbstclient dump (split horizontal:0\&.500000:1 (split vertical:0\&.700000:0 (clients vertical:0 0x1400009) (clients vertical:0)) (clients max:0 0x1a00009)) $ layout=$(herbstclient dump) .fi .if n \{\ .RE .\} .sp And after some changes you can rewind to the original layout with the load command: .sp .if n \{\ .RS 4 .\} .nf $ herbstclient load "$layout" # mind the quotes! .fi .if n \{\ .RE .\} .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" .sp The notion of a monitor in herbstluftwm is treated much more abstract and general than in other window managers: A monitor just is a rectangular part of your screen which shows exactly one tag on it\&. .sp Initially there is only one large monitor ranging over your entire screen: .sp .if n \{\ .RS 4 .\} .nf $ herbstclient list_monitors 0: 1440x900+0+0 with tag "1" [FOCUS] .fi .if n \{\ .RE .\} .sp The output shows that there is only one monitor with index 0 at position +0+0 of size 1440x900 showing tag 1\&. In most cases, the herbstluftwm monitors will match the list of physical monitors\&. So to add another physical monitor, you have to perform several steps: .sp .RS 4 .ie n \{\ \h'-04' 1.\h'+01'\c .\} .el \{\ .sp -1 .IP " 1." 4.2 .\} Enable it, such that it shows a part of your screen\&. You can use xrandr, xinerama or any other tool you like\&. .RE .sp .RS 4 .ie n \{\ \h'-04' 2.\h'+01'\c .\} .el \{\ .sp -1 .IP " 2." 4.2 .\} Register it in herbstluftwm: Lets assume your new monitor has the resolution 1024x768 and is right of your main screen, then you can activate it via: .sp .if n \{\ .RS 4 .\} .nf $ herbstclient set_monitors 1440x900+0+0 1024x768+1440+0 .fi .if n \{\ .RE .\} .sp Alternatively, if xinerama works for your setup, simply run: .sp .if n \{\ .RS 4 .\} .nf $ herbstclient detect_monitors .fi .if n \{\ .RE .\} .RE .sp For even more automation, you can enable the setting auto_detect_monitors\&. For more advanced examples, look at the q3terminal\&.sh example script, which implements a drop\-down\-terminal like monitor where you can put any application you like\&. herbstluftwm-0.7.0/CMakeLists.txt0000644000175000001440000002320412607454114016576 0ustar thorstenusers# 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() herbstluftwm-0.7.0/HACKING0000644000175000001440000001546612607454114015040 0ustar thorstenusersHACKING ======= If you want to contribute to herbstluftwm, this file is for you! Contributing ------------ Beside writing code you can help herbstluftwm by testing, reporting bugs, writing/fixing documentation (e.g. by fixing spelling mistakes) or packaging it for distributions. Coding style ------------ The coding style is similar to the Linux-kernel style with some changes: - Use 4 spaces instead of tabs. - Do not add any trailing spaces at the end of a line. - Data type names are CamelCase - Globals must be prefixed with g_ - If a function returns success or failure, then encode it in a bool (from stdbool.h). Only use main()-like exit codes (0 = success, non zero = failure) for commands. - Always typedef struct, e.g.: typedef struct { int first_member; long long other_member; } MyStruct; Build system ------------ The build system mainly is one Makefile. To check which source files depend on which header files it uses dependency files src/*.d and ipc-client/*.d (one for each C source file) which are generated by default. Note: If you switch a branch, the dependency files are not marked as dirty! This may cause unresolvable dependencies e.g. if you switch from a branch with a new header file to a branch without it. So after switching the branch, you always should remove dependency files by running: 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 git format-patch. Always specify your full name and a valid e-mail address in the author field. The commit description must consist two parts, separated by an empty line: - A mandatory short description in imperative form, e.g.: "Do things in this or that way". The description must not exceed 50 characters. - An optional longer description consisting of full sentences. This is only needed if the commit introduces non-trivial changes. When introducing new features, always - add documentation for it in doc/herbstluftwm.txt (or doc/herbstclient.txt). - document the change (e.g. "new command ...") in the NEWS file. You can send those patches to the mailing list[1] or via the irc[2]. [1] hlwm@lists.herbstluftwm.org + [2] #herbstluftwm on irc.freenode.net Mailing list ------------ The main mailing list for general development, discussion, release announcements is: hlwm@lists.herbstluftwm.org You can subscribe by sending a mail with subscribe in the subject to hlwm-request@lists.herbstluftwm.org or by using the web interface at: https://lists.schokokeks.org/mailman/listinfo.cgi/hlwm Debugging with valgrind ----------------------- If you use tools like valgrind, then it is needed to turn of the memory optimization settings for glib. So export this before running herbstluftwm within valgrind: export G_SLICE=always-malloc export G_DEBUG=gc-friendly valgrind --leak-check=full ./herbstluftwm -c share/autostart Internal structure ------------------ The main modules (i.e. source file + header file) communicate like this if a key is pressed or if a command is called via herbstclient: X11 | V +--------+ key +-----+ call +---------+ | main |------>| key |------>| command | +--------+ event +-----+ +---------+ \ output / ^ IPC-Call\ +------------+<----' / Execute -Window `---->| ipc-server |-------' IPC-Call -Event +------------+ | V X11 herbstclient is rather simple: Command-Line- +------+ Arguments --->| main |----. | | X11 stdout <----| |<---' +------+ Objects ------- There is one tree of objects in herbstluftwm to provide an easy access for the user to many options. The usage is similar to kobjects known from the Linux kernel. The most important functions are: - HSObject* hsobject_create_and_link(HSObject* parent, char* name): Create a new child object under parent. If name already exists it will be replaced. - void hsobject_unlink_and_destroy(HSObject* parent, HSObject* child): Remove all child entries at the parent object and destroys the object afterwards. - HSObject* hsobject_root(): Return the root node of the object tree. - void hsobject_set_attributes(HSObject* obj, HSAttribute* attributes): Set the attributes of an object, attributes is an array terminated by ATTRIBUTE_LAST. For a full list see src/object.h. Each object has a certain set of attributes. Each attribute has a type, which is defined by the according constructor: - ATTRIBUTE_BOOL(N, V, CHANGE): accords to bool - ATTRIBUTE_INT(N, V, CHANGE): accords to int - ATTRIBUTE_UINT(N, V, CHANGE): accords to unsigned int - ATTRIBUTE_STRING(N, V, CHANGE): accords to GString* - ATTRIBUTE_CUSTOM(N, V, CHANGE): accords to HSAttributeCustom - ATTRIBUTE_CUSTOM_INT(N, V, CHANGE): accords to HSAttributeCustomInt N is the name of the attribute as displayed to the user, V is the value in the struct; the macro automatically prepends a '&' to it to get the pointer. To check if attributes are changed properly (or to do certain actions after a change), the callback CHANGE is definied: GString* (*on_change) (struct HSAttribute* attr); It is called after the attribute is changed by the user. If this function returns something != NULL, this string is put as an error message to the user and the original value is restored. To mark a attribute as read-only for the user, give ATTR_READ_ONLY as CHANGE. The "custom" attributes do not give a pointer to a variable but give a function that returns the variable. HSAttributeCustom returns a GString, HSAttributeCustomInt an int. typedef void (*HSAttributeCustom)(void* data, GString* output); typedef int (*HSAttributeCustomInt)(void* data); They both get a data-pointer, it is exactly the data-pointer you can give as the data-member of the HSObject struct. For an example see, add_tag(char*) in src/tag.c, it covers most cases. // vim: nowrap ft=asciidoc tw=80 herbstluftwm-0.7.0/scripts/0000755000175000001440000000000012654701664015533 5ustar thorstenusersherbstluftwm-0.7.0/scripts/windowmenu.sh0000755000175000001440000000166612607454114020270 0ustar thorstenusers#!/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 herbstluftwm-0.7.0/scripts/q3terminal.sh0000755000175000001440000000457212606720407020152 0ustar thorstenusers#!/usr/bin/env bash # a q3-like (or yakuake-like) terminal for arbitrary applications. # # this lets a new monitor called "q3terminal" scroll in from the top into the # current monitor. There the "scratchpad" will be shown (it will be created if # it doesn't exist yet). If the monitor already exists it is scrolled out of # the screen and removed again. # # Warning: this uses much resources because herbstclient is forked for each # animation step. # # If a tag name is supplied, this is used instead of the scratchpad tag="${1:-scratchpad}" hc() { "${herbstclient_command[@]:-herbstclient}" "$@" ;} mrect=( $(hc monitor_rect -p "" ) ) termwidth=$(( (${mrect[2]} * 8) / 10 )) termheight=400 rect=( $termwidth $termheight $(( ${mrect[0]} + (${mrect[2]} - termwidth) / 2 )) $(( ${mrect[1]} - termheight )) ) y_line=${mrect[1]} hc add "$tag" monitor=q3terminal exists=false if ! hc add_monitor $(printf "%dx%d%+d%+d" "${rect[@]}") \ "$tag" $monitor 2> /dev/null ; then exists=true else # remember which monitor was focused previously hc chain \ , new_attr string monitors.by-name."$monitor".my_prev_focus \ , substitute M monitors.focus.index \ set_attr monitors.by-name."$monitor".my_prev_focus M fi update_geom() { local geom=$(printf "%dx%d%+d%+d" "${rect[@]}") hc move_monitor "$monitor" $geom } steps=5 interval=0.01 animate() { progress=( "$@" ) for i in "${progress[@]}" ; do rect[3]=$((y_line - (i * termheight) / steps)) update_geom sleep "$interval" done } show() { hc lock hc raise_monitor "$monitor" hc focus_monitor "$monitor" hc unlock hc lock_tag "$monitor" animate $(seq $steps -1 0) } hide() { rect=( $(hc monitor_rect "$monitor" ) ) local tmp=${rect[0]} rect[0]=${rect[2]} rect[2]=${tmp} local tmp=${rect[1]} rect[1]=${rect[3]} rect[3]=${tmp} termheight=${rect[1]} y_line=${rect[3]} # height of the upper screen border animate $(seq 0 +1 $steps) # if q3terminal still is focused, then focus the previously focused monitor # (that mon which was focused when starting q3terminal) hc substitute M monitors.by-name."$monitor".my_prev_focus \ and + compare monitors.focus.name = "$monitor" \ + focus_monitor M hc remove_monitor "$monitor" } [ $exists = true ] && hide || show herbstluftwm-0.7.0/scripts/maximize.sh0000755000175000001440000000327712607454114017717 0ustar thorstenusers#!/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[@]}" herbstluftwm-0.7.0/scripts/floatmon.sh0000755000175000001440000000357212606720407017711 0ustar thorstenusers#!/usr/bin/env bash monitor=floatmon tag=fl Mod=${Mod:-Mod1} Floatkey=${Floatkey:-Shift-f} hc() { "${herbstclient_command[@]:-herbstclient}" "$@" ;} if which xwininfo &> /dev/null; then size=$(xwininfo -root | sed -n -e '/^ Width: / { s/.*: //; h } /^ Height: / { s/.*: //g; H; x; s/\n/x/p }') else echo "This script requires the xwininfo binary." exit 1 fi hc chain , add "$tag" , floating "$tag" on hc or , add_monitor "$size"+0+0 "$tag" "$monitor" \ , move_monitor "$monitor" "$size"+0+0 hc raise_monitor "$monitor" hc lock_tag "$monitor" cmd=( or case: and # if not on floating monitor . compare monitors.focus.name != "$monitor" # and if a client is focused . get_attr clients.focus.winid # then remember the last monitor of the client . chain try new_attr string clients.focus.my_lastmon try true . substitute M monitors.focus.index set_attr clients.focus.my_lastmon M # and then move the client to the floating tag . shift_to_monitor "$monitor" . focus_monitor "$monitor" . true case: and # if on the floating monitor . compare monitors.focus.name = "$monitor" # and if a client is focused . get_attr clients.focus.winid # then send it back to the original monitor . substitute M clients.focus.my_lastmon chain , shift_to_monitor M , focus_monitor M . true case: and # if the previous things fail, # just move to the first monitor . shift_to_monitor 0 . focus_monitor 0 ) hc keybind $Mod-$Floatkey "${cmd[@]}" herbstluftwm-0.7.0/scripts/dumpbeautify.sh0000755000175000001440000000213212606720407020557 0ustar thorstenusers#!/usr/bin/env bash # aligns the output of dump command as a nice tree # usage: # herbstclient dump | ./dumpbeatify.sh awkcode=' BEGIN { indent=2 ORS="" x=0 open=0 first=1 i=0 color[i++]="\033[1;31m" color[i++]="\033[1;32m" color[i++]="\033[1;33m" color[i++]="\033[1;34m" color[i++]="\033[1;35m" color[i++]="\033[1;36m" color[i++]="\033[1;37m" color[i++]="\033[0;31m" color[i++]="\033[0;32m" color[i++]="\033[0;33m" color[i++]="\033[0;34m" color[i++]="\033[0;35m" color[i++]="\033[0;36m" color[i++]="\033[0;37m" } $1 ~ "^[(]" { if (first == 0) { printf "\n" printf "%"(indent*x)"s" , "" } else { first=0 } color_bracket[x]=open print color[(color_bracket[x]) % length(color)] gsub("[(]", "&" clear) print x++ open++ } $1 ~ "[)]" { x-- print color[(color_bracket[x]) % length(color)] print } END { printf clear "\n" } ' clear=$(tput sgr0) || clear=$(echo -e '\e[0m') sed 's/[()]/\n&/g' | # insert newlines before ( awk -v "clear=$clear" "$awkcode" herbstluftwm-0.7.0/scripts/savestate.sh0000755000175000001440000000054612606720407020067 0ustar thorstenusers#!/usr/bin/env bash hc() { "${herbstclient_command[@]:-herbstclient}" "$@" ;} # prints a machine readable format of all tags and its layouts # one tag with its layout per line # a common usage is: # savestate.sh > mystate # and sometime later: # loadstate.sh < mystate hc complete 1 use | while read tag ; do echo -n "$tag: " hc dump "$tag" done herbstluftwm-0.7.0/scripts/scratchpad.sh0000755000175000001440000000325212606720407020201 0ustar thorstenusers#!/usr/bin/env bash # a i3-like scratchpad for arbitrary applications. # # this lets a new monitor called "scratchpad" appear in from the top into the # current monitor. There the "scratchpad" will be shown (it will be created if # it doesn't exist yet). If the monitor already exists it is scrolled out of # the screen and removed again. # # Warning: this uses much resources because herbstclient is forked for each # animation step. # # If a tag name is supplied, this is used instead of the scratchpad tag="${1:-scratchpad}" hc() { "${herbstclient_command[@]:-herbstclient}" "$@" ;} mrect=( $(hc monitor_rect "" ) ) width=${mrect[2]} height=${mrect[3]} rect=( $((width/2)) $((height/2)) $((${mrect[0]}+(width/4))) $((${mrect[1]}+(height/4))) ) hc add "$tag" monitor=scratchpad exists=false if ! hc add_monitor $(printf "%dx%d%+d%+d" "${rect[@]}") \ "$tag" $monitor 2> /dev/null ; then exists=true else # remember which monitor was focused previously hc chain \ , new_attr string monitors.by-name."$monitor".my_prev_focus \ , substitute M monitors.focus.index \ set_attr monitors.by-name."$monitor".my_prev_focus M fi show() { hc lock hc raise_monitor "$monitor" hc focus_monitor "$monitor" hc unlock hc lock_tag "$monitor" } hide() { # if q3terminal still is focused, then focus the previously focused monitor # (that mon which was focused when starting q3terminal) hc substitute M monitors.by-name."$monitor".my_prev_focus \ and + compare monitors.focus.name = "$monitor" \ + focus_monitor M hc remove_monitor "$monitor" } [ $exists = true ] && hide || show herbstluftwm-0.7.0/scripts/lasttag.sh0000755000175000001440000000136112606720407017523 0ustar thorstenusers#!/usr/bin/env bash # usage: start this script in anywhere your autostart (but *after* the # emit_hook reload line) # to switch to the last tag, call: herbstclient emit_hook goto_last_tag # or bind it: herbstclient keybind Mod1-Escape emit_hook goto_last_tag hc() { "${herbstclient_command[@]:-herbstclient}" "$@" ;} hc --idle '(tag_changed|goto_last_tag|reload)' \ | while read line ; do IFS=$'\t' read -ra args <<< "$line" case ${args[0]} in tag_changed) lasttag="$tag" tag=${args[1]} ;; goto_last_tag) [ "$lasttag" ] && hc use "$lasttag" ;; reload) exit ;; esac done herbstluftwm-0.7.0/scripts/README0000644000175000001440000000076112533670523016412 0ustar thorstenusers _ _ _ _ __ _ | |_ ___ _ _| |__ __| |_| |_ _ / _| |___ __ ___ __ | ' \/ -_) '_| '_ (_-< _| | || | _| _\ V V / ' \ |_||_\___|_| |_.__/__/\__|_|\_,_|_| \__|\_/\_/|_|_|_| ========================================= scripts/ === this dir contains various scripts which interact with herbstluftwm/herbstclient. They are all quite short. Each of them contains a short description. They are put here to give you some ideas what you can do with herbstluftwm. # vim: tw=80 herbstluftwm-0.7.0/scripts/execwith.sh0000755000175000001440000000030012606720407017674 0ustar thorstenusers#!/usr/bin/env bash # exec a script $2... with settings from rc-file $1 # useful for various dmenu scripts, e.g.: # ./execwith.sh ~/.bash_settings ./dmenu.sh use source "$1" shift exec "$@" herbstluftwm-0.7.0/scripts/herbstcommander.sh0000755000175000001440000000541012606720407021240 0ustar thorstenusers#!/usr/bin/env bash # herbstcommander.sh - launch herbstluftwm-commands via dmenu # Written by Florian Bruhin # To customize dmenu-colors, create a file named "herbstcommander" in your # herbstluftwm config-directory, with something like this in it: # # dmenu_command=(dmenu -i -b -nb '#313131' -nf '#686868' -sb '#454545' -sf '#898989') # # You can also directly pass dmenu-arguments to this script instead, as long # as dmenu_command is undefined. config_1="$XDG_CONFIG_HOME/herbstluftwm/herbstcommander" config_2="$HOME/.config/herbstluftwm/herbstcommander" [[ -f "$config_1" ]] && source "$config_1" [[ -f "$config_2" ]] && source "$config_2" dm() { if [[ "${dmenu_command[@]}" ]]; then "${dmenu_command[@]}" "$@" else dmenu -i "$@" fi } hc() { "${herbstclient_command[@]:-herbstclient}" "$@" ;} prompt=${prompt:-herbstluft: } display_reply=${display_reply:-true} cmd=( "$@" ) forceexec=0 while :; do dmenu_args="" if [[ "$forceexec" != 1 ]]; then completion=$(hc complete "${#cmd[@]}" "${cmd[@]}") if [[ "$?" = 7 ]] ; then forceexec=1 fi fi if [[ "$forceexec" == 1 ]]; then echo "Executing ${cmd[@]}" reply=$(hc "${cmd[@]}") status=$? if [[ "$display_reply" && "$reply" ]]; then dm -p "${cmd[*]}" <<< "$reply" >/dev/null fi exit $status else case "${cmd[*]}" in raise|jumpto|bring) IFS=$'\t' read -ra tags <<< "$(hc tag_status)" i=1 completion=$( wmctrl -l | while read line; do IFS=' ' read -ra fields <<< "$line" id=${fields[0]} tag=${tags[ ${fields[1]} ]} class=$(xprop -notype -id $id WM_CLASS | sed 's/.*\?= *//; s/"\(.*\?\)", *"\(.*\?\)".*/\1,\2/') title=${fields[@]:3} printf "%-3s %s %-3s [%s] %s\n" "$i)" "$id" "$tag" "$class" "$title" i=$((i+1)) done ) dmenu_args="-l 10" ;; esac next=$(dm $dmenu_args -p "${prompt}${cmd[*]}" <<< "$completion") (( $? != 0 )) && exit 125 # dmenu was killed if [[ -z "$next" ]]; then forceexec=1 # empty reply instead of completion else case "${cmd[*]}" in raise|jumpto|bring) # add the WINID only (second field) IFS=' ' read -ra fields <<< "$next" cmd+=( ${fields[1]} ) ;; *) cmd+=( $next ) ;; esac fi fi done herbstluftwm-0.7.0/scripts/toggledualhead.sh0000755000175000001440000000460612607454114021042 0ustar thorstenusers#!/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 herbstluftwm-0.7.0/scripts/loadstate.sh0000755000175000001440000000060312606720407020042 0ustar thorstenusers#!/usr/bin/env bash hc() { "${herbstclient_command[@]:-herbstclient}" "$@" ;} # loads layouts for each tag coming from stdin # the format is the one created by savestate.sh # a common usage is: # savestate.sh > mystate # and sometime later: # loadstate.sh < mystate while read line ; do tag="${line%%: *}" tree="${line#*: }" hc add "$tag" hc load "$tag" "$tree" done herbstluftwm-0.7.0/scripts/keychain.sh0000755000175000001440000000370312606720407017661 0ustar thorstenusers#!/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. # # E.g. you can press Mod1-i 1 (i.e. first press Mod1-i and then press the # 1-button) to switch to the first workspace # # The idea of this implementation is: If one presses the prefix (in this case # Mod1-i) except the notification, nothing is really executed but new # keybindings are added to execute the actually commands (like use_index 0) and # to unbind the second key level (1..9 and 0) of this keychain. (If you would # not unbind it, use_index 0 always would be executed when pressing the single # 1-button). hc() { "${herbstclient_command[@]:-herbstclient}" "$@" ;} Mod=Mod1 # Create the array of keysyms, the n'th entry will be used for the n'th # keybinding keys=( {1..9} 0 ) # Build the command to unbind the keys unbind=( ) for k in "${keys[@]}" Escape ; do unbind+=( , keyunbind "$k" ) done # Add the actual bind, after that, no new processes are spawned when using that # key chain. (Except the spawn notify-send of course, which can be deactivated # by only deleting the appropriate line) hc keybind $Mod-i chain \ '->' spawn notify-send "Select a workspace number or press Escape" \ '->' keybind "${keys[0]}" chain "${unbind[@]}" , use_index 0 \ '->' keybind "${keys[1]}" chain "${unbind[@]}" , use_index 1 \ '->' keybind "${keys[2]}" chain "${unbind[@]}" , use_index 2 \ '->' keybind "${keys[3]}" chain "${unbind[@]}" , use_index 3 \ '->' keybind "${keys[4]}" chain "${unbind[@]}" , use_index 4 \ '->' keybind "${keys[5]}" chain "${unbind[@]}" , use_index 5 \ '->' keybind "${keys[6]}" chain "${unbind[@]}" , use_index 6 \ '->' keybind "${keys[7]}" chain "${unbind[@]}" , use_index 7 \ '->' keybind "${keys[8]}" chain "${unbind[@]}" , use_index 8 \ '->' keybind "${keys[9]}" chain "${unbind[@]}" , use_index 9 \ '->' keybind Escape chain "${unbind[@]}" herbstluftwm-0.7.0/scripts/dmenu.sh0000755000175000001440000000042712606720407017176 0ustar thorstenusers#!/usr/bin/env bash dm() { "${dmenu_command[@]:-dmenu}" "$@" ;} hc() { "${herbstclient_command[@]:-herbstclient}" "$@" ;} simple_command() { arg=$(hc complete 1 "$@" | dm -p "$@:") \ && exec "${herbstclient_command[@]:-herbstclient}" "$@" "$arg" } simple_command "$1" herbstluftwm-0.7.0/scripts/wselect.sh0000755000175000001440000000254412607454114017536 0ustar thorstenusers#!/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} "$@" ;} dmenu_lines=${dmenu_lines:-10} case "$1" in bring) # bring the selected window to the current tag and focus it name=Bring: action() { hc bring "$@" ; } ;; 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: ;; esac id=$(wmctrl -l |cat -n| sed 's/\t/) /g'| sed 's/^[ ]*//' \ | dm -i -l $dmenu_lines -p "$name") \ && action $(awk '{ print $2 ; }' <<< "$id") herbstluftwm-0.7.0/scripts/layout.sh0000755000175000001440000000074012606720407017401 0ustar thorstenusers#!/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 hc() { "${herbstclient_command[@]:-herbstclient}" "$@" ;} hc complete 1 use | while read tag ; do echo -n "$tag " indent=$(echo -n "$tag " | sed 's/./ /g') # prepend indent, except in first line hc layout "$tag" \ | sed -e "2,\$ s/^/$indent/" \ -e "s/0x[0-9a-f]\+/$(tput setaf 3)&$(tput sgr0)/g" done herbstluftwm-0.7.0/scripts/exec_on_tag.sh0000755000175000001440000000123012606720407020332 0ustar thorstenusers#!/usr/bin/env bash hc() { "${herbstclient_command[@]:-herbstclient}" "$@" ;} tag="$1" expire="120" # expiry time in seconds shift if [ -z "$1" ] ;then echo "usage: $0 TAG COMMAND [ARGS ...]" >&2 echo "executes a COMMAND on a specific TAG" >&2 echo "if TAG doesnot exist, it will be created" >&2 echo "if TAG is empty, current tag will be used" >&2 fi tag=${tag:-$(hc attr tags.focus.name)} # ensure tag exists hc 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 hc rule prepend maxage="$expire" pid="$$" tag="$tag" once exec "$@" herbstluftwm-0.7.0/NEWS0000644000175000001440000003130412654701655014544 0ustar thorstenusersherbstluftwm 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 --------------------------- * directional shift of floating windows * fix crash of directional focus * document theme.minimal Release 0.6.0 on 2014-03-19 --------------------------- * Add window decorations, configurable via the theme-object * The tag attributes curframe_windex and curframe_wcount have been removed, they are replaced by the more general attributes frames.focus.windex frames.focus.wcount. * new example script: scratchpad.sh * if swap_monitors_to_get_tag is set to 0, then focus the other monitor if the desired tag is shown on another monitor instead of doing nothing * new split mode: auto * new attribute monitors.count indicating the number of monitors * new settings object with an attribute for each setting. * directional focus for floating clients, i.e. switch between floating windows via focus left|right|up|down. It also raises the freshly focused window. * directional monitor focusing * new detect_monitors flags: --list -l --no-disjoin while detect_monitors does disjoin_rects on the detected monitors per default. * For each client a keymask can be given. A keymask is a regular expression, that is matched against the string representation of a keybinding. If it matches, the keybinding is enabled for this client, otherwise not. The default is an empty keymask ("") that matches all keybindings, so no bindings are masked out by default. A keymask is a client attribute, and can be set by a client rule. * add completion to the mousebind command * add mouse function call to call commands on mouse button press * add setting update_dragged_clients * new rule consequence: monitor * new command: try * new command: silent Release 0.5.3 on 2013-12-24 --------------------------- * make window floating faster, by dropping old motion events * new rule flag: prepend * close now accepts an arbitrary window id as a parameter * Also allow decimal window ids for commands accepting a window id, like raise, jump, close * new split modes, i.e. parameters to the split command: bottom, right (indicating the relative location of the new empty frame) * new split mode: explode * make fraction parameter for the split command optional * respect size hints in floating and tiling mode * new setting: frame_transparent_width * frame_bg_transparent: instead of copying the pixmap from the root window achieve transparency by cutting out a rectangle from the center of frames using the Shape Extension of X. * Make respecting of sizehints configurable for tiling and floating mode via the client properties sizehints_tiling and sizehints_floating * new setting: pseudotile_center_threshold * new command: cycle_frame * new object attribute type: color Release 0.5.2 on 2013-06-23 --------------------------- Changes: * cycle_all now raises the focused client. * focus a client on its tag after moving it there (by shift_to_monitor or move) * disallow focus_follows_mouse if the focus change hides another window (e.g. an pseudotiled window in the max layout). In that case an extra click is required to change the focus. * new command complete_shell for posix shell specific completion * add completion for the rule command * add completion for keycombinations in keybind * add completion for setenv, getenv and unsetenv * the hook consequence now also tells the window id that triggerd the rule * new command: echo * new commands: true false * rule labels: rules can be given a label with the 'label' property. The label can be printed to stdout using the 'printlabel' flag to the rule command. Unrule command accepts a label to remove all rules with that label. * new command: list_rules * allow true/false as arguments to commands accepting boolean values. This affects the commands: floating, fullscreen, pseudotile and rule. * new command: new_attr, remove_attr, get_attr, set_attr * new command: substitute, sprintf, mktemp Release 0.5.1 on 2013-01-05 --------------------------- Changes: * new command: use_previous * Makefile: new target: install-nodoc -- install without man/html-docs * fixup of the + flag in tag_status * fixup of the example script q3terminal.sh * announce it if the tag changes due to remove_monitor Release 0.5.0 on 2012-12-31 --------------------------- Incompatible changes: * The setting focus_follows_shift has been removed. The behaviour now is set like it were set to 1; the focus always follows the shift now. Changes: * new settings: wmname, mouse_recenter_gap * hook togglehidepanel: the default panel.sh also accepts the parameters "current" or a certain monitor index * align captions of the tree output (commands: layout, stack) better by inserting a space in front of every caption * let new clients and monitors (and other items that are stacked) appear on top of the stacking order * chaining of commands now is possible with the new command: chain * new commands: and or negate * tag switch locking for monitors. The new lock_tag and unlock_tag commands control, whether tag switching operations for a monitor are allowed. * set the urgent flag on _NET_WM_STATE_DEMANDS_ATTENTION * clear the urgent flag on window focus * new command: list_padding * new commands: getenv/setenv/unsetenv * new rule: ewmhnotify * floating, fullscreen, pseudotile: default to toggle if no argument is given * add error messages for herbstclient * new commands: focus_edge, shift_edge * new command: shift_to_monitor * optional names for monitors, new command rename_monitor * new consequence: hook * new example script: q3terminal.sh which provides a q3-like terminal Release: 0.4.1 on 2012-08-30 ---------------------------- This fixes some build system issues: * Separate CFLAGS and CPPFLAGS * Add patch level to the version number * Only use __MACH__ if really needed * Honor CPPFLAGS Release: 0.4 on 2012-08-18 -------------------------- Changes that require user interaction on upgrade: * the setting window_gap is now called frame_gap Other changes: * new setting window_gap which controls the gap between windows within one frame * new setting: frame_padding * new command: close_or_remove * new flags '-' and '%' for tag_status for multi monitor handling * toggle default panel on hook togglehidepanel * new setting: window_border_urgent_color * new command: set_monitors * new command: disjoin_rects * new command: jumpto * use clock_get_time on systems with a mach kernel (typically Mac OS X). This lets herbstluftwm run on Mac systems again. * fix many memory leaks. * new command line flag --skip-visible for use_index and move_index * new command: detect_monitors, to detect Xinerama monitors automatically * new ewmh feature: react to _NET_WM_DESKTOP client messages * new command: rotate * new setting: auto_detect_monitors * only one Makefile for herbstluftwm and herbstclient. The herbstclient binary now is put in the main directory. * new settings: smart_frame_surroundings and smart_window_surroundings * new settings: window_border_inner_color and window_border_inner_width * new settings: frame_border_inner_color and frame_border_inner_width * new option --skip-invisible for cycle_all * cycle_layout now offers to cycle through a custom list of layouts * add completion for +1 and -1 to many commands: cycle, cycle_all, cycle_monitor, cycle_layout, split and use_index * start system wide autostart file if there is no working user defined one * clients are restored (i.e. managed) from _NET_CLIENT_LIST even if they are not visible * do proper window and monitor stacking, this also introduces: - new command: raise_monitor - new setting: raise_on_focus_temporarily - new command: stack * new command: focus_nth * new command: bring * respect the WM_TAKE_FOCUS atom, this fixes keyboard focus issues with many (mostly Java/swing based) applications * new rule consequences: switchtag, ewmhrequests Release 0.3 on 2012-04-12 ------------------------- Changes: * new hook: window_title_changed * hook focus_changed now also reports the window title * the setting ignore_class is removed, because this also can be done by rules. You can replace a line like 'set ignore_class "$foo"' in your autostart by 'rule class~"$foo" manage=off' * remember the value when toggling a setting and restore it on next toggle * new command: cycle_value * new commands: use_index, move_index * recursive command completion for keybind * new rule condition: title * in the default autostart: - new green and gray color theme - use/move was replaced by use_index/move_index * proper signal handling: map all windows on SIGINT,SIGQUIT,SIGTERM * respect the initial fullscreen state of new clients on startup (as it is set in the _NET_WM_STATE property) * monitor locking mechanism, i.e. a new setting: monitors_locked with its accessors lock and unlock and the command line argument -l and --locked Release 0.2 on 2012-01-25 ------------------------- Small bugfixes and major features: * new command: monitor_rect * let panel.sh fork less often * set clients to fullscreen * new client layouting algorithm: grid layout * new command argument: keyunbind --all * new command: pseudotile (tile client but keep its floating size) * new command: list_keybinds * new hook: focus_changed * client rules with: - condition: class - condition: instance - condition: pid - condition: maxage - condition: windowtype - condition: windowrole - operator: = (equals) - operator: ~ (regex-matching) - flag: once - flag: not (negation of conditions) - consequence: tag - consequence: focus - consequence: index (where to insert a new client in layout tree) - consequence: pseudotile - consequence: fullscreen - consequence: manage (whether window will be managed) * basic ewmh features: - the following properties will be set: _NET_ACTIVE_WINDOW _NET_CLIENT_LIST _NET_CURRENT_DESKTOP _NET_DESKTOP_NAMES _NET_NUMBER_OF_DESKTOPS _NET_SUPPORTING_WM_CHECK _NET_WM_DESKTOP _NET_WM_STATE: fullscreen - the following client messages are handled: _NET_ACTIVE_WINDOW _NET_CURRENT_DESKTOP _NET_WM_STATE: fullscreen Release 0.1 on 2011-10-02 ------------------------- Initial release. The tiling algorithm and many things are working. But some things like EWMH or rules aren't implemented yet (See BUGS file for a list of planned features). Currently implemented features are: * basic tiling concept * floating mode (with border snapping) * calling herbstluftwm internal commands with herbstclient * change keybindings/mousebindings at runtime * change/get settings (e.g. colors) at runtime * add/remove monitors at runtime * wmexec into other window manager * dump/load tiling layouts to/from a string * bash/zsh tab completion for herbstclient * hook system: let herbstclient listen for internal events * provide an easy way to build an own panel (using herbstclient hooks/commands) (there is also an example panel.sh) * give information about urgent state of clients herbstluftwm-0.7.0/src/0000755000175000001440000000000012654701664014633 5ustar thorstenusersherbstluftwm-0.7.0/src/floating.cpp0000644000175000001440000002637012607454114017143 0ustar thorstenusers#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; } herbstluftwm-0.7.0/src/x11-utils.h0000644000175000001440000000072612607454114016551 0ustar thorstenusers #ifndef __HERBST_X11_UTILS_H_ #define __HERBST_X11_UTILS_H_ #include #include #include #include #include "x11-types.h" // 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); void window_make_intransparent(Window win, int width, int height); Point2D get_cursor_position(); #endif herbstluftwm-0.7.0/src/clientlist.cpp0000644000175000001440000010637212607454114017513 0ustar thorstenusers/** 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); } } herbstluftwm-0.7.0/src/layout.h0000644000175000001440000001513312607454114016315 0ustar thorstenusers/** Copyright 2011-2013 Thorsten Wißmann. All rights reserved. * * This software is licensed under the "Simplified BSD License". * See LICENSE for details */ #ifndef __HERBSTLUFT_LAYOUT_H_ #define __HERBSTLUFT_LAYOUT_H_ #include "glib-backports.h" #include "utils.h" #include #include #include #include "monitor.h" #include "tag.h" #include "floating.h" #define LAYOUT_DUMP_BRACKETS "()" /* must consist of exactly two chars */ #define LAYOUT_DUMP_WHITESPACES " \t\n" /* must be at least one char */ #define LAYOUT_DUMP_SEPARATOR_STR ":" /* must be a string with one char */ #define LAYOUT_DUMP_SEPARATOR LAYOUT_DUMP_SEPARATOR_STR[0] #define TAG_SET_FLAG(tag, flag) \ ((tag)->flags |= (flag)) enum { TAG_FLAG_URGENT = 0x01, // is there a urgent window? TAG_FLAG_USED = 0x02, // the opposite of empty }; enum { ALIGN_VERTICAL = 0, ALIGN_HORIZONTAL, // temporary values in split_command ALIGN_EXPLODE, }; enum { LAYOUT_VERTICAL = 0, LAYOUT_HORIZONTAL, LAYOUT_MAX, LAYOUT_GRID, LAYOUT_COUNT, }; extern const char* g_align_names[]; extern const char* g_layout_names[]; enum { TYPE_CLIENTS = 0, TYPE_FRAMES, }; // execute an action on an client // returns Success or failure. struct HSClient; typedef int (*ClientAction)(struct HSClient*, void* data); #define FRACTION_UNIT 10000 struct HSFrame; struct HSSlice; struct HSTag; typedef struct HSLayout { int align; // ALIGN_VERTICAL or ALIGN_HORIZONTAL struct HSFrame* a; // first child struct HSFrame* b; // second child int selection; int fraction; // size of first child relative to whole size // FRACTION_UNIT means full size // FRACTION_UNIT/2 means 50% } HSLayout; typedef struct HSFrame { union { HSLayout layout; struct { struct HSClient** buf; size_t count; int selection; int layout; } clients; } content; int type; struct HSFrame* parent; struct HSTag* tag; struct HSSlice* slice; Window window; int window_transparent; bool window_visible; Rectangle last_rect; // last rectangle when being drawn } HSFrame; // globals 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(); void layout_destroy(); // for frames HSFrame* frame_create_empty(HSFrame* parent, HSTag* parenttag); void frame_insert_client(HSFrame* frame, struct HSClient* client); 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 HSFrame* find_frame_with_client(HSFrame* frame, struct HSClient* client); // removes window from a frame/subframes // returns true, if window was found. else: false bool frame_remove_client(HSFrame* frame, struct HSClient* client); // destroys a frame and all its childs // then all Windows in it are collected and returned // YOU have to g_free the resulting window-buf void frame_destroy(HSFrame* frame, struct HSClient*** buf, size_t* count); bool frame_split(HSFrame* frame, int align, int fraction); int frame_split_command(int argc, char** argv, GString* output); int frame_change_fraction_command(int argc, char** argv, GString* output); void frame_apply_layout(HSFrame* frame, Rectangle rect); void frame_apply_floating_layout(HSFrame* frame, struct HSMonitor* m); void frame_update_frame_window_visibility(HSFrame* frame); void reset_frame_colors(); HSFrame* get_toplevel_frame(HSFrame* frame); void print_frame_tree(HSFrame* frame, GString* output); void dump_frame_tree(HSFrame* frame, GString* output); // create apply a described layout to a frame and its subframes // returns pointer to string that was not parsed yet // or NULL on an error char* load_frame_tree(HSFrame* frame, char* description, GString* errormsg); int find_layout_by_name(char* name); int find_align_by_name(char* name); int frame_current_bring(int argc, char** argv, GString* output); int frame_current_set_selection(int argc, char** argv); int frame_current_cycle_selection(int argc, char** argv); int cycle_all_command(int argc, char** argv); int cycle_frame_command(int argc, char** argv); void cycle_frame(int direction, int new_window_index, bool skip_invisible); void frame_unfocus(); // unfocus currently focused window // get neighbour in a specific direction 'l' 'r' 'u' 'd' (left, right,...) // returns the neighbour or NULL if there is no one HSFrame* frame_neighbour(HSFrame* frame, char direction); int frame_inner_neighbour_index(HSFrame* frame, char direction); int frame_focus_command(int argc, char** argv, GString* output); // follow selection to leaf and focus this frame int frame_focus_recursive(HSFrame* frame); void frame_do_recursive(HSFrame* frame, void (*action)(HSFrame*), int order); void frame_do_recursive_data(HSFrame* frame, void (*action)(HSFrame*,void*), int order, void* data); void frame_hide_recursive(HSFrame* frame); void frame_show_recursive(HSFrame* frame); int layout_rotate_command(); // do an action for each client in frame tree // returns success or failure int frame_foreach_client(HSFrame* frame, ClientAction action, void* data); void frame_apply_client_layout_linear(HSFrame* frame, Rectangle rect, bool vertical); void frame_apply_client_layout_horizontal(HSFrame* frame, Rectangle rect); void frame_apply_client_layout_vertical(HSFrame* frame, Rectangle rect); int frame_current_cycle_client_layout(int argc, char** argv, GString* output); int frame_current_set_client_layout(int argc, char** argv, GString* output); int frame_split_count_to_root(HSFrame* frame, int align); // returns the Window that is focused // returns 0 if there is none struct HSClient* frame_focused_client(HSFrame* frame); bool frame_focus_client(HSFrame* frame, struct HSClient* client); bool focus_client(struct HSClient* client, bool switch_tag, bool switch_monitor); // moves a window to an other frame int frame_move_window_command(int argc, char** argv, GString* output); /// 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); int frame_focus_edge(int argc, char** argv, GString* output); int frame_move_window_edge(int argc, char** argv, GString* output); bool smart_window_surroundings_active(HSFrame* frame); #endif herbstluftwm-0.7.0/src/monitor.h0000644000175000001440000001074012607454114016466 0ustar thorstenusers/** Copyright 2011-2013 Thorsten Wißmann. All rights reserved. * * This software is licensed under the "Simplified BSD License". * See LICENSE for details */ #ifndef __HERBSTLUFT_MONITOR_H_ #define __HERBSTLUFT_MONITOR_H_ #include "glib-backports.h" #include #include #ifdef XINERAMA #include #endif /* XINERAMA */ #include "floating.h" #include "object.h" #include "utils.h" struct HSTag; struct HSFrame; struct HSSlice; struct HSStack; typedef struct HSMonitor { struct HSTag* tag; // currently viewed tag struct HSTag* tag_previous; // previously viewed tag struct HSSlice* slice; // slice in the monitor stack HSObject object; GString* name; GString* display_name; // name used for object IO int pad_up; int pad_right; int pad_down; int pad_left; bool dirty; bool lock_frames; bool lock_tag; struct { // last saved mouse position int x; int y; } mouse; Rectangle rect; // area for this monitor Window stacking_window; // window used for making stacking easy } HSMonitor; void monitor_init(); void monitor_destroy(); // adds a new monitor to g_monitors and returns a pointer to it HSMonitor* monitor_with_frame(struct HSFrame* frame); HSMonitor* monitor_with_coordinate(int x, int y); HSMonitor* monitor_with_index(int index); HSMonitor* find_monitor_with_tag(struct HSTag* tag); HSMonitor* add_monitor(Rectangle rect, struct HSTag* tag, char* name); Rectangle monitor_get_floating_area(HSMonitor* m); void monitor_focus_by_index(int new_selection); int monitor_get_relative_x(HSMonitor* m, int x_root); int monitor_get_relative_y(HSMonitor* m, int y_root); int monitor_index_of(HSMonitor* monitor); int monitor_cycle_command(int argc, char** argv); int monitor_focus_command(int argc, char** argv, GString* output); int find_monitor_index_by_name(char* name); HSMonitor* find_monitor_by_name(char* name); HSMonitor* string_to_monitor(char* string); int string_to_monitor_index(char* string); int monitor_index_in_direction(HSMonitor* m, enum HSDirection dir); int add_monitor_command(int argc, char** argv, GString* output); int monitor_raise_command(int argc, char** argv, GString* output); int remove_monitor_command(int argc, char** argv, GString* output); int remove_monitor(int index); int list_monitors(int argc, char** argv, GString* output); int list_padding(int argc, char** argv, GString* output); int set_monitor_rects_command(int argc, char** argv, GString* output); int disjoin_rects_command(int argc, char** argv, GString* output); int set_monitor_rects(Rectangle* templates, size_t count); int move_monitor_command(int argc, char** argv, GString* output); int rename_monitor_command(int argc, char** argv, GString* output); int monitor_rect_command(int argc, char** argv, GString* output); HSMonitor* get_current_monitor(); int monitor_count(); int monitor_set_tag(HSMonitor* monitor, struct HSTag* tag); int monitor_set_pad_command(int argc, char** argv, GString* output); int monitor_set_tag_command(int argc, char** argv, GString* output); int monitor_set_tag_by_index_command(int argc, char** argv, GString* output); int monitor_set_previous_tag_command(int argc, char** argv, GString* output); void monitors_lock(); void monitors_unlock(); 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* newmon); void drop_enternotify_events(); void monitor_restack(HSMonitor* monitor); int monitor_stack_window_count(bool real_clients); void monitor_stack_to_window_buf(Window* buf, int len, bool real_clients, int* remain_len); struct HSStack* get_monitor_stack(); void monitor_update_focus_objects(); 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, const char **argv, GString* output); int shift_to_monitor(int argc, char** argv, GString* output); #endif herbstluftwm-0.7.0/src/layout.cpp0000644000175000001440000021223512607454114016652 0ustar thorstenusers/** 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); } herbstluftwm-0.7.0/src/tag.h0000644000175000001440000000341012607454114015546 0ustar thorstenusers/** Copyright 2011-2013 Thorsten Wißmann. All rights reserved. * * This software is licensed under the "Simplified BSD License". * See LICENSE for details */ #ifndef __HERBSTLUFT_TAG_H_ #define __HERBSTLUFT_TAG_H_ #include "glib-backports.h" #include struct HSFrame; struct HSClient; struct HSStack; typedef struct HSTag { GString* name; // name of this tag GString* display_name; // name used for object-io struct HSFrame* frame; // the master frame bool floating; int flags; struct HSStack* stack; struct HSObject* object; } HSTag; void tag_init(); void tag_destroy(); // for tags 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); int tag_move_window_by_index_command(int argc, char** argv, GString* output); void tag_move_focused_client(HSTag* target); void tag_move_client(struct HSClient* client,HSTag* target); int tag_remove_command(int argc, char** argv, GString* output); int tag_set_floating_command(int argc, char** argv, GString* output); void tag_update_focus_layer(HSTag* tag); void tag_foreach(void (*action)(HSTag*,void*), void* data); void tag_update_each_focus_layer(); void tag_update_focus_objects(); void tag_force_update_flags(); void tag_update_flags(); void tag_set_flags_dirty(); void ensure_tags_are_available(); #endif herbstluftwm-0.7.0/src/desktopwindow.cpp0000644000175000001440000000166112607454114020235 0ustar thorstenusers#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()); } herbstluftwm-0.7.0/src/command.h0000644000175000001440000001126712607454114016422 0ustar thorstenusers/** Copyright 2011-2013 Thorsten Wißmann. All rights reserved. * * This software is licensed under the "Simplified BSD License". * See LICENSE for details */ #ifndef __HERBSTLUFT_COMMAND_H_ #define __HERBSTLUFT_COMMAND_H_ #include "glib-backports.h" #include typedef int (*HerbstCmd)(int argc, // number of arguments const char** argv, // array of args GString* output // result-data/stdout ); typedef int (*HerbstCmdNoOutput)(int argc, // number of arguments const char** argv // array of args ); #define CMD_BIND(NAME, FUNC) \ { CommandBindingCB(FUNC), (NAME), 1 } #define CMD_BIND_NO_OUTPUT(NAME, FUNC) \ { 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 { CommandBindingCB cmd; const char* name; bool has_output; } CommandBinding; extern CommandBinding g_commands[]; int call_command(int argc, char** argv, GString* output); int call_command_no_output(int argc, char** argv); int call_command_substitute(char* needle, char* replacement, int argc, char** argv, GString* output); // commands int list_commands(int argc, char** argv, GString* output); int complete_command(int argc, char** argv, 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); void complete_against_tags(int argc, char** argv, int pos, GString* output); void complete_against_monitors(int argc, char** argv, int pos, GString* output); void complete_against_objects(int argc, char** argv, int pos, GString* output); void complete_against_attributes(int argc, char** argv, int pos, GString* output); void complete_against_user_attributes(int argc, char** argv, int pos, GString* output); void complete_against_attribute_values(int argc, char** argv, int pos, GString* output); void complete_against_comparators(int argc, char** argv, int pos, GString* output); void complete_against_winids(int argc, char** argv, int pos, GString* output); void complete_merge_tag(int argc, char** argv, int pos, GString* output); void complete_negate(int argc, char** argv, int pos, GString* output); void complete_against_settings(int argc, char** argv, int pos, GString* output); void complete_against_keybinds(int argc, char** argv, int pos, GString* output); int complete_against_commands(int argc, char** argv, int position, GString* output); void complete_against_commands_1(int argc, char** argv, int position, GString* output); void complete_against_commands_3(int argc, char** argv, int position, GString* output); void complete_against_arg_1(int argc, char** argv, int position, GString* output); void complete_against_arg_2(int argc, char** argv, int position, GString* output); void complete_against_keybind_command(int argc, char** argv, int position, GString* output); void complete_against_mouse_combinations(int argc, char** argv, int position, GString* output); void complete_against_env(int argc, char** argv, int position, GString* output); void complete_chain(int argc, char** argv, int position, GString* output); int command_chain(char* separator, bool (*condition)(int laststatus), int argc, char** argv, GString* output); void complete_sprintf(int argc, char** argv, int position, GString* output); void complete_against_user_attr_prefix(int argc, char** argv, int position, GString* output); int command_chain_command(int argc, char** argv, GString* output); int negate_command(int argc, char** argv, GString* output); #endif herbstluftwm-0.7.0/src/settings.cpp0000644000175000001440000003236612607454114017202 0ustar thorstenusers/** 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; } herbstluftwm-0.7.0/src/rules.cpp0000644000175000001440000006262512607454114016475 0ustar thorstenusers/** 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); } } herbstluftwm-0.7.0/src/mouse.cpp0000644000175000001440000004334612607454114016472 0ustar thorstenusers/** 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; } } herbstluftwm-0.7.0/src/monitor.cpp0000644000175000001440000012771712607454114017036 0ustar thorstenusers/** 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; } herbstluftwm-0.7.0/src/stack.h0000644000175000001440000000451312607454114016105 0ustar thorstenusers/** Copyright 2011-2013 Thorsten Wißmann. All rights reserved. * * This software is licensed under the "Simplified BSD License". * See LICENSE for details */ #ifndef __HERBST_STACK_H_ #define __HERBST_STACK_H_ #include #include "glib-backports.h" #include #include enum HSLayer { /* layers on each monitor, from top to bottom */ LAYER_FOCUS, LAYER_FULLSCREEN, LAYER_NORMAL, LAYER_FRAMES, LAYER_COUNT, }; extern const std::array g_layer_names; typedef enum SliceType { SLICE_CLIENT, SLICE_WINDOW, SLICE_MONITOR, } HSSliceType; struct HSClient; struct HSMonitor; typedef struct HSSlice { HSSliceType type; HSLayer layer[LAYER_COUNT]; /* layers this slice is contained in */ size_t layer_count; /* count of those layers */ union { struct HSClient* client; Window window; struct HSMonitor* monitor; } data; } HSSlice; typedef struct HSStack { GList* top[LAYER_COUNT]; bool dirty; /* stacking order changed but it wasn't restacked yet */ } HSStack; void stacklist_init(); void stacklist_destroy(); HSSlice* slice_create_window(Window window); HSSlice* slice_create_frame(Window window); HSSlice* slice_create_client(struct HSClient* client); HSSlice* slice_create_monitor(struct HSMonitor* monitor); void slice_destroy(HSSlice* slice); HSLayer slice_highest_layer(HSSlice* slice); void stack_insert_slice(HSStack* s, HSSlice* elem); void stack_remove_slice(HSStack* s, HSSlice* elem); void stack_raise_slide(HSStack* stack, HSSlice* slice); void stack_mark_dirty(HSStack* s); void stack_slice_add_layer(HSStack* stack, HSSlice* slice, HSLayer layer); void stack_slice_remove_layer(HSStack* stack, HSSlice* slice, HSLayer layer); bool stack_is_layer_empty(HSStack* s, HSLayer layer); void stack_clear_layer(HSStack* stack, HSLayer layer); int print_stack_command(int argc, char** argv, GString* output); // returns the number of windows in this stack int stack_window_count(HSStack* stack, bool real_clients); void stack_to_window_buf(HSStack* stack, Window* buf, int len, bool real_clients, int* remain_len); void stack_restack(HSStack* stack); Window stack_lowest_window(HSStack* stack); HSStack* stack_create(); void stack_destroy(HSStack* s); #endif herbstluftwm-0.7.0/src/utils.h0000644000175000001440000001142112607454114016134 0ustar thorstenusers/** Copyright 2011-2013 Thorsten Wißmann. All rights reserved. * * This software is licensed under the "Simplified BSD License". * See LICENSE for details */ #ifndef __HERBST_UTILS_H_ #define __HERBST_UTILS_H_ #include "glib-backports.h" #include #include #include #include #include "x11-types.h" #include #define LENGTH(X) (sizeof(X)/sizeof(*X)) #define SHIFT(ARGC, ARGV) (--(ARGC) && ++(ARGV)) #define MOD(X, N) ((((X) % (signed)(N)) + (signed)(N)) % (signed)(N)) #define container_of(ptr, type, member) \ ((type *)( (char *)(ptr)- offsetof(type,member) )) // control structures #define FOR(i,a,b) for (int i = (a); i < (b); i++) #define SWAP(TYPE,a,b) do { \ TYPE TMPNAME = (a); \ (a) = (b); \ (b) = TMPNAME; \ } while(0); /// print a printf-like message to stderr and exit void die(const char *errstr, ...); // get X11 color from color string HSColor getcolor(const char *colstr); bool getcolor_error(const char *colstr, HSColor* color); #define ATOM(A) XInternAtom(g_display, (A), False) GString* window_property_to_g_string(Display* dpy, Window window, Atom atom); GString* window_class_to_g_string(Display* dpy, Window window); GString* window_instance_to_g_string(Display* dpy, Window window); int window_pid(Display* dpy, Window window); typedef void* HSTree; struct HSTreeInterface; typedef struct HSTreeInterface { struct HSTreeInterface (*nth_child)(HSTree root, size_t idx); size_t (*child_count)(HSTree root); void (*append_caption)(HSTree root, GString* output); HSTree data; void (*destructor)(HSTree data); /* how to free the data tree */ } HSTreeInterface; void tree_print_to(HSTreeInterface* intface, GString* output); bool is_herbstluft_window(Display* dpy, Window window); bool is_window_mapable(Display* dpy, Window window); bool is_window_mapped(Display* dpy, Window window); bool window_has_property(Display* dpy, Window window, char* prop_name); bool string_to_bool_error(const char* string, bool oldvalue, bool* error); bool string_to_bool(const char* string, bool oldvalue); const char* strlasttoken(const char* str, const char* delim); time_t get_monotonic_timestamp(); // duplicates an argument-vector char** argv_duplicate(int argc, char** argv); // frees all entries in argument-vector and then the vector itself void argv_free(int argc, char** argv); Rectangle parse_rectangle(char* string); 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(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, 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, const void* needle); void set_window_double_border(Display *dpy, Window win, int ibw, unsigned long inner_color, unsigned long outer_color); #define STATIC_TABLE_FIND(TYPE, TABLE, MEMBER, EQUALS, NEEDLE) \ ((TYPE*) table_find((TABLE), \ sizeof(TABLE[0]), \ LENGTH((TABLE)), \ offsetof(TYPE, MEMBER), \ EQUALS, \ (NEEDLE))) #define STATIC_TABLE_FIND_STR(TYPE, TABLE, MEMBER, NEEDLE) \ STATIC_TABLE_FIND(TYPE, TABLE, MEMBER, memberequals_string, NEEDLE) #define INDEX_OF(ARRAY, PELEM) \ (((char*)(PELEM) - (char*)(ARRAY)) / (sizeof (*ARRAY))) // returns the unichar in GSTR at position GSTR #define UTF8_STRING_AT(GSTR, OFFS) \ g_utf8_get_char( \ g_utf8_offset_to_pointer((GSTR), (OFFS))) \ #define RECTANGLE_EQUALS(a, b) (\ (a).x == (b).x && \ (a).y == (b).y && \ (a).width == (b).width && \ (a).height == (b).height \ ) // 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(const char* source); // does the reverse action to posix_sh_escape by modifing the string void posix_sh_compress_inplace(char* str); #endif herbstluftwm-0.7.0/src/floating.h0000644000175000001440000000206112607454114016577 0ustar thorstenusers/** Copyright 2011-2014 Thorsten Wißmann. All rights reserved. * * This software is licensed under the "Simplified BSD License". * See LICENSE for details */ #ifndef __HERBST_FLOATING_H_ #define __HERBST_FLOATING_H_ #include #include "x11-types.h" #include enum HSDirection { DirRight, DirLeft, DirUp, DirDown, }; typedef struct { Rectangle r; int idx; } RectangleIdx; void floating_init(); void floating_destroy(); // utilities enum HSDirection char_to_direction(char ch); int find_rectangle_in_direction(RectangleIdx* rects, size_t cnt, int idx, enum HSDirection dir); int find_rectangle_right_of(RectangleIdx* rects, size_t cnt, int idx); int find_edge_in_direction(RectangleIdx* rects, size_t cnt, int idx, enum HSDirection dir); int find_edge_right_of(RectangleIdx* rects, size_t cnt, int idx); // actual implementations bool floating_focus_direction(enum HSDirection dir); bool floating_shift_direction(enum HSDirection dir); #endif herbstluftwm-0.7.0/src/mouse.h0000644000175000001440000000511612607454114016130 0ustar thorstenusers/** Copyright 2011-2013 Thorsten Wißmann. All rights reserved. * * This software is licensed under the "Simplified BSD License". * See LICENSE for details */ #ifndef __HERBSTLUFT_MOUSE_H_ #define __HERBSTLUFT_MOUSE_H_ #include #include #include "glib-backports.h" // various snap-flags enum SnapFlags { // which edges are considered to snap SNAP_EDGE_TOP = 0x01, SNAP_EDGE_BOTTOM = 0x02, SNAP_EDGE_LEFT = 0x04, SNAP_EDGE_RIGHT = 0x08, SNAP_EDGE_ALL = SNAP_EDGE_TOP | SNAP_EDGE_BOTTOM | SNAP_EDGE_LEFT | SNAP_EDGE_RIGHT, }; // forward declarations struct HSClient; struct HSMonitor; struct HSTag; void mouse_init(); void mouse_destroy(); typedef void (*MouseDragFunction)(XMotionEvent*); typedef void (*MouseFunction)(struct HSClient* client, int argc, char** argv); typedef struct MouseBinding { unsigned int modifiers; unsigned int button; MouseFunction action; int argc; // additional arguments char** argv; } MouseBinding; int mouse_binding_equals(MouseBinding* a, MouseBinding* b); int mouse_bind_command(int argc, char** argv, GString* output); int mouse_unbind_all(); MouseBinding* mouse_binding_find(unsigned int modifiers, unsigned int button); unsigned int string2button(const char* name); MouseFunction string2mousefunction(char* name); void grab_client_buttons(struct HSClient* client, bool focused); void mouse_handle_event(XEvent* ev); void mouse_initiate_drag(struct HSClient* client, MouseDragFunction function); void mouse_stop_drag(); bool mouse_is_dragging(); void handle_motion_event(XEvent* ev); // 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); bool is_point_between(int point, int left, int right); // 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); void mouse_initiate_move(struct HSClient* client, int argc, char** argv); void mouse_initiate_zoom(struct HSClient* client, int argc, char** argv); void mouse_initiate_resize(struct HSClient* client, int argc, char** argv); void mouse_call_command(struct HSClient* client, int argc, char** argv); /* some mouse drag functions */ void mouse_function_move(XMotionEvent* me); void mouse_function_resize(XMotionEvent* me); void mouse_function_zoom(XMotionEvent* me); void complete_against_mouse_buttons(const char* needle, char* prefix, GString* output); #endif herbstluftwm-0.7.0/src/glib-backports.h0000644000175000001440000000237412533670523017710 0ustar thorstenusers/** Copyright 2011-2013 Thorsten Wißmann. All rights reserved. * * This software is licensed under the "Simplified BSD License". * See LICENSE for details */ #ifndef __HS_GLIB_BACKPORTS_H_ #define __HS_GLIB_BACKPORTS_H_ /** * This file re-implements newer glib functions that are missing in glib on * older systems. Note that this mostly works correctly but isn't as * efficient as the new glib functions. */ #include #ifndef G_QUEUE_INIT #define G_QUEUE_INIT { NULL, NULL, 0 } #endif #if !(GLIB_CHECK_VERSION(2,14,0)) /* implement g_queue_clear for glib older than 2.14 */ #define g_queue_clear(Q) do { \ g_list_free((Q)->head); \ (Q)->head = NULL; \ (Q)->length = 0; \ (Q)->tail = NULL; \ } while(0) #endif #if !(GLIB_CHECK_VERSION(2, 28, 0)) /** * g_list_free_full * * actually this is not c-standard-compatible because of casting * an one-parameter-function to an 2-parameter-function. * but it should work on almost all architectures (maybe not amd64?) */ #define g_list_free_full(LIST, DESTROY) do { \ g_list_foreach((LIST), (GFunc)(DESTROY), 0); \ g_list_free((LIST)); \ } while(0) #endif #endif herbstluftwm-0.7.0/src/tag.cpp0000644000175000001440000004305612622352142016106 0ustar thorstenusers/** 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"); } herbstluftwm-0.7.0/src/clientlist.h0000644000175000001440000001036412607454114017153 0ustar thorstenusers/** Copyright 2011-2013 Thorsten Wißmann. All rights reserved. * * This software is licensed under the "Simplified BSD License". * See LICENSE for details */ #ifndef __CLIENTLIST_H_ #define __CLIENTLIST_H_ #include #include #include #include #include "glib-backports.h" #include #include "layout.h" #include "object.h" #include "utils.h" #include "decoration.h" struct HSSlice; typedef struct HSClient { Window window; GString* window_str; // the window id as a string Rectangle last_size; // last size excluding the window border HSTag* tag; Rectangle float_size; // floating size without the window border GString* title; // or also called window title; this is never NULL GString* keymask; // keymask applied to mask out keybindins bool urgent; bool fullscreen; bool ewmhfullscreen; // ewmh fullscreen state bool pseudotile; // only move client but don't resize (if possible) bool neverfocus; // do not give the focus via XSetInputFocus bool ewmhrequests; // accept ewmh-requests for this client bool ewmhnotify; // send ewmh-notifications for this client bool sizehints_floating; // respect size hints regarding this client in floating mode bool sizehints_tiling; // respect size hints regarding this client in tiling mode bool dragged; // if this client is dragged currently int pid; int ignore_unmaps; // Ignore one unmap for each reparenting // action, because reparenting creates an unmap // notify event bool visible; // for size hints float mina, maxa; int basew, baseh, incw, inch, maxw, maxh, minw, minh; // for other modules HSObject object; struct HSSlice* slice; HSDecoration dec; } HSClient; void clientlist_init(); void clientlist_destroy(); void clientlist_end_startup(); bool clientlist_ignore_unmapnotify(Window win); void clientlist_foreach(GHFunc func, gpointer data); void client_window_focus(HSClient* client); void client_window_unfocus(HSClient* client); void client_window_unfocus_last(); void reset_client_colors(); void reset_client_settings(); // adds a new client to list of managed client windows HSClient* manage_client(Window win); void client_fuzzy_fix_initial_position(HSClient* client); void unmanage_client(Window win); void window_enforce_last_size(Window in); // destroys a special client void client_destroy(HSClient* client); HSClient* get_client_from_window(Window window); HSClient* get_current_client(); HSClient* get_urgent_client(); Rectangle client_outer_floating_rect(HSClient* 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); void client_resize_floating(HSClient* client, HSMonitor* m); bool is_client_floated(HSClient* client); bool client_needs_minimal_dec(HSClient* client, HSFrame* frame); void client_set_urgent(HSClient* client, bool state); void client_update_wm_hints(HSClient* client); void client_update_title(HSClient* client); void client_raise(HSClient* client); int close_command(int argc, char** argv, GString* output); void window_close(Window window); void client_set_dragged(HSClient* client, bool drag_state); void client_send_configure(HSClient *c); bool applysizehints(HSClient *c, int *w, int *h); bool applysizehints_xy(HSClient *c, int *x, int *y, int *w, int *h); void updatesizehints(HSClient *c); bool client_sendevent(HSClient *client, Atom proto); void client_set_fullscreen(HSClient* client, bool state); void client_set_pseudotile(HSClient* client, bool state); // sets a client property, depending on argv[0] int client_set_property_command(int argc, char** argv); bool is_window_class_ignored(char* window_class); bool is_window_ignored(Window win); void client_set_visible(HSClient* client, bool visible); void window_set_visible(Window win, bool visible); unsigned long get_window_border_color(HSClient* client); #endif herbstluftwm-0.7.0/src/ewmh.cpp0000644000175000001440000004743712607454114016307 0ustar thorstenusers/** 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)); } herbstluftwm-0.7.0/src/desktopwindow.h0000644000175000001440000000146412607454114017703 0ustar thorstenusers/** 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 herbstluftwm-0.7.0/src/object.cpp0000644000175000001440000010543112607454114016602 0ustar thorstenusers/** 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; } herbstluftwm-0.7.0/src/decoration.cpp0000644000175000001440000005504612607454114017471 0ustar thorstenusers #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); } herbstluftwm-0.7.0/src/key.h0000644000175000001440000000345212607454114015571 0ustar thorstenusers/** Copyright 2011-2013 Thorsten Wißmann. All rights reserved. * * This software is licensed under the "Simplified BSD License". * See LICENSE for details */ #ifndef __HERBST_KEY_H_ #define __HERBST_KEY_H_ #include #include #include "glib-backports.h" #include "clientlist.h" #define KEY_COMBI_SEPARATORS "+-" typedef struct KeyBinding { KeySym keysym; unsigned int modifiers; int cmd_argc; // number of arguments for command char** cmd_argv; // arguments for command to call bool enabled; // Is the keybinding already grabbed } KeyBinding; unsigned int modifiername2mask(const char* name); const char* modifiermask2name(unsigned int mask); bool string2modifiers(char* string, unsigned int* modmask); bool string2key(char* string, unsigned int* modmask, KeySym* keysym); int keybind(int argc, char** argv, GString* output); int keyunbind(int argc, char** argv, GString* output); //removes a keybinding void keybinding_free(KeyBinding* binding); int key_list_binds(int argc, char** argv, GString* output); int list_keysyms(int argc, char** argv, GString* output); 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(const char* needle, GString* output); void complete_against_modifiers(const char* needle, char seperator, 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(); unsigned int* get_numlockmask_ptr(); void key_set_keymask(HSTag * tag, HSClient *client); void handle_key_press(XEvent* ev); void key_init(); void key_destroy(); #endif herbstluftwm-0.7.0/src/decoration.h0000644000175000001440000000666212607454114017136 0ustar thorstenusers/** Copyright 2011-2013 Thorsten Wißmann. All rights reserved. * * This software is licensed under the "Simplified BSD License". * See LICENSE for details */ #ifndef __DECORATION_H_ #define __DECORATION_H_ #include #include #include #include #include "glib-backports.h" #include "x11-utils.h" #include "object.h" #include #include "utils.h" struct HSClient; typedef struct { int border_width; HSColor border_color; bool tight_decoration; // if set, there is no space between the // decoration and the window content HSColor inner_color; int inner_width; HSColor outer_color; int outer_width; int padding_top; // additional window border int padding_right; // additional window border int padding_bottom; // additional window border int padding_left; // additional window border HSColor background_color; // color behind client contents } HSDecorationScheme; typedef struct { struct HSClient* client; // the client to decorate Window decwin; // the decoration winodw HSDecorationScheme last_scheme; bool last_rect_inner; // whether last_rect is inner size Rectangle last_inner_rect; // only valid if width >= 0 Rectangle last_outer_rect; // only valid if width >= 0 Rectangle last_actual_rect; // last actual client rect, relative to decoration /* X specific things */ Colormap colormap; unsigned int depth; Pixmap pixmap; int pixmap_height; int pixmap_width; // fill the area behind client with another window that does nothing, // especially not repainting or background filling to avoid flicker on // unmap Window bgwin; } HSDecoration; typedef struct { HSDecorationScheme normal; HSDecorationScheme active; HSDecorationScheme urgent; HSObject obj_normal; HSObject obj_active; HSObject obj_urgent; HSDecorationScheme propagate; // meta-scheme for propagating values to members HSObject object; } HSDecTriple; enum { HSDecSchemeFullscreen, HSDecSchemeTiling, HSDecSchemeFloating, HSDecSchemeMinimal, HSDecSchemeCount, }; extern HSDecTriple g_decorations[]; void decorations_init(); void decorations_destroy(); void decoration_init(HSDecoration* dec, struct HSClient* client); void decoration_setup_frame(struct HSClient* client); void decoration_free(HSDecoration* dec); // resize such that the decorated outline of the window fits into rect void decoration_resize_outline(struct HSClient* client, Rectangle rect, HSDecorationScheme scheme); // resize such that the window content fits into rect void decoration_resize_inner(struct HSClient* client, Rectangle rect, HSDecorationScheme scheme); void decoration_change_scheme(struct HSClient* client, HSDecorationScheme scheme); void decoration_redraw_pixmap(struct HSClient* client); struct HSClient* get_client_from_decoration(Window decwin); Rectangle inner_rect_to_outline(Rectangle rect, HSDecorationScheme scheme); Rectangle outline_to_inner_rect(Rectangle rect, HSDecorationScheme scheme); #endif herbstluftwm-0.7.0/src/key.cpp0000644000175000001440000003240312607454114016122 0ustar thorstenusers/** 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); } herbstluftwm-0.7.0/src/ipc-server.h0000644000175000001440000000076312533670523017064 0ustar thorstenusers/** Copyright 2011-2013 Thorsten Wißmann. All rights reserved. * * This software is licensed under the "Simplified BSD License". * See LICENSE for details */ #ifndef __HERBSTLUFT_IPC_SERVER_H_ #define __HERBSTLUFT_IPC_SERVER_H_ #include #include void ipc_init(); void ipc_destroy(); void ipc_add_connection(Window win); // returns true if property was received successfully bool ipc_handle_connection(Window window); bool is_ipc_connectable(Window window); #endif herbstluftwm-0.7.0/src/ewmh.h0000644000175000001440000000747412607454114015751 0ustar thorstenusers/** Copyright 2011-2013 Thorsten Wißmann. All rights reserved. * * This software is licensed under the "Simplified BSD License". * See LICENSE for details */ #ifndef __HERBSTLUFT_EWMH_H_ #define __HERBSTLUFT_EWMH_H_ #include #include #include #include #include #define ENUM_WITH_ALIAS(Identifier, Alias) \ Identifier, Alias = Identifier /* actions on NetWmState */ #define _NET_WM_STATE_REMOVE 0 /* remove/unset property */ #define _NET_WM_STATE_ADD 1 /* add/set property */ #define _NET_WM_STATE_TOGGLE 2 /* toggle property */ enum { NetSupported = 0, NetClientList, NetClientListStacking, NetNumberOfDesktops, NetCurrentDesktop, NetDesktopNames, NetWmDesktop, NetDesktopViewport, NetActiveWindow, NetWmName, NetSupportingWmCheck, NetWmWindowType, NetWmState, NetWmWindowOpacity, NetMoveresizeWindow, NetWmMoveresize, NetFrameExtents, /* window states */ NetWmStateFullscreen, NetWmStateDemandsAttention, /* window types */ ENUM_WITH_ALIAS(NetWmWindowTypeDesktop, NetWmWindowTypeFIRST), NetWmWindowTypeDock, NetWmWindowTypeToolbar, NetWmWindowTypeMenu, NetWmWindowTypeUtility, NetWmWindowTypeSplash, NetWmWindowTypeDialog, NetWmWindowTypeDropdownMenu, NetWmWindowTypePopupMenu, NetWmWindowTypeTooltip, NetWmWindowTypeNotification, NetWmWindowTypeCombo, NetWmWindowTypeDnd, ENUM_WITH_ALIAS(NetWmWindowTypeNormal, NetWmWindowTypeLAST), /* the count of hints */ 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; extern Atom g_netatom[NetCOUNT]; extern const std::array g_netatom_names; void ewmh_init(); void ewmh_destroy(); void ewmh_update_all(); void ewmh_add_client(Window win); void ewmh_remove_client(Window win); void ewmh_set_wmname(char* name); void ewmh_update_wmname(); void ewmh_update_client_list(); void ewmh_get_original_client_list(Window** buf, unsigned long *count); void ewmh_update_client_list_stacking(); void ewmh_update_desktops(); void ewmh_update_desktop_names(); void ewmh_update_active_window(Window win); void ewmh_update_current_desktop(); void ewmh_update_window_state(struct HSClient* client); 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 void ewmh_window_update_tag(Window win, struct HSTag* tag); void ewmh_handle_client_message(XEvent* event); void ewmh_set_window_opacity(Window win, double opacity); typedef enum { // see icccm: // http://www.x.org/releases/X11R7.7/doc/xorg-docs/icccm/icccm.html#WM_STATE_Property WmStateWithdrawnState = 0, WmStateNormalState = 1, WmStateIconicState = 3, } WmState; void window_update_wm_state(Window win, WmState state); #endif herbstluftwm-0.7.0/src/main.cpp0000644000175000001440000010764112607454114016265 0ustar thorstenusers/** 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; } herbstluftwm-0.7.0/src/hook.h0000644000175000001440000000065512607454114015743 0ustar thorstenusers/** Copyright 2011-2013 Thorsten Wißmann. All rights reserved. * * This software is licensed under the "Simplified BSD License". * See LICENSE for details */ #ifndef __HERBSTLUFT_HOOK_H_ #define __HERBSTLUFT_HOOK_H_ #include "layout.h" void hook_init(); void hook_destroy(); void hook_emit(int argc, const char** argv); void emit_tag_changed(HSTag* tag, int monitor); void hook_emit_list(const char* name, ...); #endif herbstluftwm-0.7.0/src/settings.h0000644000175000001440000000242612607454114016641 0ustar thorstenusers/** Copyright 2011-2013 Thorsten Wißmann. All rights reserved. * * This software is licensed under the "Simplified BSD License". * See LICENSE for details */ #ifndef __HERBSTLUFT_SETTINGS_H_ #define __HERBSTLUFT_SETTINGS_H_ #include "glib-backports.h" enum { HS_String = 0, HS_Int, HS_Compatiblity, }; typedef struct { const char* name; union { int i; const char* s_init; GString* str; struct { const char* read; // attribute address for reading const char* write; // attribute address for writing } compat; } value; int old_value_i; int type; void (*on_change)(); // what to call on change } SettingsPair; extern int g_initial_monitors_locked; void settings_init(); void settings_destroy(); 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, 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(); int settings_get(int argc, char** argv, GString* output); #endif herbstluftwm-0.7.0/src/rules.h0000644000175000001440000000440612607454114016133 0ustar thorstenusers/** Copyright 2011-2013 Thorsten Wißmann. All rights reserved. * * This software is licensed under the "Simplified BSD License". * See LICENSE for details */ #ifndef __HS_RULES_H_ #define __HS_RULES_H_ #include #include #include "glib-backports.h" struct HSClient; struct HSTag; enum { CONDITION_VALUE_TYPE_STRING, CONDITION_VALUE_TYPE_REGEX, CONDITION_VALUE_TYPE_INTEGER, }; enum { CONSEQUENCE_VALUE_TYPE_STRING, }; typedef struct { int condition_type; int value_type; bool negated; union { char* str; struct { regex_t exp; char* str; } reg; int integer; } value; } HSCondition; typedef struct { int type; int value_type; union { char* str; } value; } HSConsequence; typedef struct { char* label; HSCondition** conditions; int condition_count; HSConsequence** consequences; int consequence_count; bool once; time_t birth_time; // timestamp of at creation } HSRule; typedef struct { GString* tag_name; GString* monitor_name; GString* tree_index; bool focus; // if client should get focus bool switchtag; // if the tag may be switched for focusing it bool manage; // whether we should manage it bool fullscreen; bool ewmhnotify; // whether to send ewmh-notifications GString* keymask; // Which keymask rule should be applied for this client } HSClientChanges; void rules_init(); void rules_destroy(); void rules_apply(struct HSClient* client, HSClientChanges* changes); void client_changes_init(HSClientChanges* changes, struct HSClient* client); void client_changes_free_members(HSClientChanges* changes); HSRule* rule_create(); void rule_destroy(HSRule* rule); void rule_complete(int argc, char** argv, int pos, GString* output); int rule_add_command(int argc, char** argv, GString* output); int rule_remove_command(int argc, char** argv, GString* output); int rule_print_all_command(int argc, char** argv, GString* output); void complete_against_rule_names(int argc, char** argv, int pos, GString* output); #endif herbstluftwm-0.7.0/src/utils.cpp0000644000175000001440000003771312607454114016503 0ustar thorstenusers/** 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; } } } herbstluftwm-0.7.0/src/ipc-protocol.h0000644000175000001440000000206312533670523017412 0ustar thorstenusers/** Copyright 2011-2013 Thorsten Wißmann. All rights reserved. * * This software is licensed under the "Simplified BSD License". * See LICENSE for details */ #ifndef __HERBST_IPC_PROTOCOL_H_ #define __HERBST_IPC_PROTOCOL_H_ #define HERBST_IPC_CLASS "HERBST_IPC_CLASS" //#define HERBST_IPC_READY "HERBST_IPC_READY" //#define HERBST_IPC_ATOM "_HERBST_IPC" #define HERBST_IPC_ARGS_ATOM "_HERBST_IPC_ARGS" #define HERBST_IPC_OUTPUT_ATOM "_HERBST_IPC_OUTPUT" #define HERBST_IPC_STATUS_ATOM "_HERBST_IPC_EXIT_STATUS" #define HERBST_HOOK_CLASS "HERBST_HOOK_CLASS" #define HERBST_HOOK_WIN_ID_ATOM "__HERBST_HOOK_WIN_ID" #define HERBST_HOOK_PROPERTY_FORMAT "__HERBST_HOOK_ARGUMENTS_%d" // maximum number of hooks to buffer #define HERBST_HOOK_PROPERTY_COUNT 10 // function exit codes enum { HERBST_EXIT_SUCCESS = 0, HERBST_UNKNOWN_ERROR, HERBST_COMMAND_NOT_FOUND, HERBST_INVALID_ARGUMENT, HERBST_SETTING_NOT_FOUND, HERBST_TAG_IN_USE, HERBST_FORBIDDEN, HERBST_NO_PARAMETER_EXPECTED, HERBST_ENV_UNSET, HERBST_NEED_MORE_ARGS, }; #endif herbstluftwm-0.7.0/src/command.cpp0000644000175000001440000011347512607454114016761 0ustar thorstenusers/** 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); } herbstluftwm-0.7.0/src/ipc-server.cpp0000644000175000001440000000475212607454114017417 0ustar thorstenusers/** 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; } herbstluftwm-0.7.0/src/stack.cpp0000644000175000001440000003024512607454114016441 0ustar thorstenusers#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; } } herbstluftwm-0.7.0/src/x11-utils.cpp0000644000175000001440000000530512607454114017102 0ustar thorstenusers #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; } herbstluftwm-0.7.0/src/x11-types.h0000644000175000001440000000054312607454114016552 0ustar thorstenusers #ifndef __HERBST_X11_TYPES_H_ #define __HERBST_X11_TYPES_H_ typedef unsigned long HSColor; struct Rectangle { int x; int y; int width; int height; Rectangle() {}; Rectangle(int x, int y, int width, int height) : x(x), y(y), width(width), height(height) {}; }; typedef struct { int x; int y; } Point2D; #endif herbstluftwm-0.7.0/src/hook.cpp0000644000175000001440000000543012607454114016272 0ustar thorstenusers/** 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); } herbstluftwm-0.7.0/src/object.h0000644000175000001440000002055312607454114016250 0ustar thorstenusers/** Copyright 2011-2013 Thorsten Wißmann. All rights reserved. * * This software is licensed under the "Simplified BSD License". * See LICENSE for details */ #ifndef __HS_OBJECT_H_ #define __HS_OBJECT_H_ #include #include "glib-backports.h" #include "x11-types.h" #define OBJECT_PATH_SEPARATOR '.' #define USER_ATTRIBUTE_PREFIX "my_" #define TMP_OBJECT_PATH "tmp" class HSAttribute; typedef struct HSObject { HSAttribute* attributes; size_t attribute_count; GList* children; // list of HSObjectChild void* data; // user data pointer } HSObject; // data pointer is the data pointer of the attribute // 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; int* i; unsigned int* u; GString** str; 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 { bool b; int i; unsigned int u; GString* str; HSColor color; } HSAttributeValue; typedef GString* (*HSAttrCallback)(HSAttribute* attr); enum HSAttributeType { HSATTR_TYPE_BOOL, HSATTR_TYPE_UINT, HSATTR_TYPE_INT, HSATTR_TYPE_COLOR, HSATTR_TYPE_STRING, HSATTR_TYPE_CUSTOM, HSATTR_TYPE_CUSTOM_INT, }; 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: * on_change is called after the user changes the value. If this * function returns NULL, the value is accepted. If this function returns * some error message, the old value is restored automatically and the * message first is displayed to the user and then freed. * * if type is custom: * on_change will never be called. Instead, change_custom is called with * the new value requested by the user. If the pointer is NULL, it is * treaten read-only * */ HSAttrCallback on_change; 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 */ /* save the user_data at a constant position that is not shifted when * 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 */ #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 HSAttribute::LAST() void object_tree_init(); void object_tree_destroy(); HSObject* hsobject_root(); bool hsobject_init(HSObject* obj); void hsobject_free(HSObject* obj); HSObject* hsobject_create(); HSObject* hsobject_create_and_link(HSObject* parent, const char* name); 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, 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(const char* path, const char** unparsable); HSObject* hsobject_parse_path_verbose(const char* path, const char** unparsable, 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); GString* ATTR_ACCEPT_ALL(HSAttribute* attr); #define ATTR_READ_ONLY NULL HSObject* hsobject_find_child(HSObject* obj, const char* name); HSAttribute* hsobject_find_attribute(HSObject* obj, const char* name); void hsobject_set_attributes_always_callback(HSObject* obj); char hsattribute_type_indicator(int type); 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, 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, const char* needle, const char* prefix, GString* output); void hsobject_complete_attributes(HSObject* obj, bool user_only, 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); int compare_command(int argc, char* argv[], GString* output); int userattribute_command(int argc, char* argv[], GString* output); int userattribute_remove_command(int argc, char* argv[], GString* output); 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); #endif herbstluftwm-0.7.0/src/globals.h0000644000175000001440000000734312607454114016427 0ustar thorstenusers/** Copyright 2011-2013 Thorsten Wißmann. All rights reserved. * * This software is licensed under the "Simplified BSD License". * See LICENSE for details */ #ifndef __HERBSTLUFT_GLOBALS_H_ #define __HERBSTLUFT_GLOBALS_H_ #include #include #define HERBSTLUFT_AUTOSTART "herbstluftwm/autostart" #define WINDOW_MANAGER_NAME "herbstluftwm" #define HERBSTLUFT_VERSION_STRING \ WINDOW_MANAGER_NAME " " HERBSTLUFT_VERSION " (built on " __DATE__ ")" #define HERBST_FRAME_CLASS "_HERBST_FRAME" #define HERBST_DECORATION_CLASS "_HERBST_DECORATION" #define WINDOW_MIN_HEIGHT 32 #define WINDOW_MIN_WIDTH 32 #define ROOT_EVENT_MASK (SubstructureRedirectMask|SubstructureNotifyMask|ButtonPressMask|EnterWindowMask|LeaveWindowMask|StructureNotifyMask) //#define CLIENT_EVENT_MASK (PropertyChangeMask | FocusChangeMask | StructureNotifyMask) #define CLIENT_EVENT_MASK (StructureNotifyMask|FocusChangeMask|EnterWindowMask|PropertyChangeMask) // minimum relative fraction of split frames #define FRAME_MIN_FRACTION 0.1 #define HERBST_MAX_TREE_HEIGHT 3 // connection to x-server 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 // size for some normal string buffers #define STRING_BUF_SIZE 1000 #define HSDebug(...) \ do { \ if (g_verbose) { \ fprintf(stderr, "%s: %d: ", __FILE__, __LINE__); \ fprintf(stderr, __VA_ARGS__); \ } \ } while(0) #define HSError(...) \ do { \ fprintf(stderr, "%s: %d: ", __FILE__, __LINE__); \ fprintf(stderr, __VA_ARGS__); \ } while(0) #define HSWarning(...) \ do { \ fprintf(stderr, "%s: %d: ", __FILE__, __LINE__); \ fprintf(stderr, __VA_ARGS__); \ } while(0) // macro for very slow asserts, which are only executed if DEBUG is defined #ifdef DEBUG #define slow_assert(X) \ do { \ if (!(X)) { \ fprintf(stderr, "%s:%d: %s: Slow assertion `%s\' failed.", \ __FILE__, __LINE__, __func__, #X); \ abort(); \ } \ } while (0) #else // DEBUG #define slow_assert(ignore)((void) 0) #endif // DEBUG #define HSWeakAssert(X) \ do { \ if (!(X)) { \ fprintf(stderr, "%s:%d: %s: assertion `%s\' failed.", \ __FILE__, __LINE__, __func__, #X); \ } \ } while (0) #define HSAssert(X) \ do { \ if (!(X)) { \ fprintf(stderr, "%s:%d: %s: assertion `%s\' failed.", \ __FILE__, __LINE__, __func__, #X); \ exit(1); \ } \ } while (0) // characters that need to be escaped // http://pubs.opengroup.org/onlinepubs/009695399/utilities/xcu_chap02.html #define ESCAPE_CHARACTERS "|&;<>()$`\\\"\' \t\n" #endif herbstluftwm-0.7.0/LICENSE0000644000175000001440000000306612533670523015051 0ustar thorstenusersCopyright 2011-2013 Thorsten Wißmann. All rights reserved. This software is licensed under the "Simplified BSD License": Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER(S) ''AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. The views and conclusions contained in the software and documentation are those of the authors and should not be interpreted as representing official policies, either expressed or implied, of the copyright holder(s). herbstluftwm-0.7.0/config.mk0000644000175000001440000000337512607454114015643 0ustar thorstenusers# paths 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` 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 endif # 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) \ -D HERBSTLUFT_VERSION_MINOR=$(VERSION_MINOR) \ -D HERBSTLUFT_VERSION_PATCH=$(VERSION_PATCH) CPPFLAGS ?= CPPFLAGS += $(INCS) -D _XOPEN_SOURCE=600 $(VERSIONFLAGS) $(XINERAMAFLAGS) CPPFLAGS += -D HERBSTLUFT_GLOBAL_AUTOSTART=\"$(CONFIGDIR)/autostart\" LDFLAGS ?= -g DESTDIR = PREFIX = /usr/local BINDIR = $(PREFIX)/bin DATADIR = $(PREFIX)/share MANDIR = $(DATADIR)/man MAN1DIR = $(MANDIR)/man1 MAN7DIR = $(MANDIR)/man7 DOCDIR = $(DATADIR)/doc/herbstluftwm EXAMPLESDIR = $(DOCDIR)/examples LICENSEDIR = $(DOCDIR) SYSCONFDIR = /etc CONFIGDIR = $(SYSCONFDIR)/xdg/herbstluftwm XSESSIONSDIR = $(DATADIR)/xsessions ZSHCOMPLETIONDIR = $(DATADIR)/zsh/functions/Completion/X BASHCOMPLETIONDIR = $(SYSCONFDIR)/bash_completion.d TARFILE = herbstluftwm-$(SHORTVERSION).tar.gz A2X = a2x ASCIIDOC = asciidoc TMPTARDIR = herbstluftwm-$(SHORTVERSION) MKDIR = mkdir -p INSTALL = install RM = rm -f RMDIR = rmdir # Controls verbose build # Remove the @ to see the actual compiler invocations VERBOSE = @ # Set this to 0 to disable colors COLOR = 1 herbstluftwm-0.7.0/colors.mk0000644000175000001440000000064312533670523015674 0ustar thorstenusers# colors ifeq ($(COLOR),1) TPUT = tput COLOR_CLEAR = `$(TPUT) sgr0` COLOR_NORMAL = $(COLOR_CLEAR) COLOR_ACTION = `$(TPUT) bold``$(TPUT) setaf 3` COLOR_FILE = `$(TPUT) bold``$(TPUT) setaf 2` COLOR_BRACKET = $(COLOR_CLEAR)`$(TPUT) setaf 4` endif ifneq ($(VERBOSE),) define colorecho @echo $(COLOR_BRACKET)" ["$(COLOR_ACTION)$1$(COLOR_BRACKET)"] " $(COLOR_FILE)$2$(COLOR_BRACKET)... $(COLOR_NORMAL) endef endif herbstluftwm-0.7.0/share/0000755000175000001440000000000012654701664015146 5ustar thorstenusersherbstluftwm-0.7.0/share/dmenu_run_hlwm0000755000175000001440000000200512533670523020107 0ustar thorstenusers#!/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 herbstluftwm-0.7.0/share/herbstclient-completion0000644000175000001440000000067212533670523021726 0ustar thorstenusers# bash completion for herbstclient _herbstclient_complete() { local IFS=$'\n' # do not split at =, because BASH would not split at a '='. COMP_WORDBREAKS=${COMP_WORDBREAKS//=} COMPREPLY=( # just call the herbstclient complete .. but without herbstclient as argument $(herbstclient -q complete_shell "$((COMP_CWORD-1))" "${COMP_WORDS[@]:1}") ) } complete -F _herbstclient_complete -o nospace herbstclient herbstluftwm-0.7.0/share/herbstluftwm.xpm0000644000175000001440000000100512533670523020411 0ustar thorstenusers/* XPM */ static char* HERBSTLUFTWM[] = { "16 16 4 1", " c None", ". c #020202", "# c #9fbc00", "l c #8f5902", " .##.. ", " .##.##.. ", " .##.##.##.. ", " ..##.##.##. ", " .##.##.##. ", " .##.##.##l. ", " ..##.##ll. ", " .##.##.ll. ", " .##.##..ll. ", " ..##. .ll. ", " .##. .ll. ", " .##. .ll. ", " . ..ll. ", " .llll. ", " .lllll. ", " ..... "}; herbstluftwm-0.7.0/share/autostart0000755000175000001440000001216412607454114017117 0ustar thorstenusers#!/usr/bin/env bash # this is a simple config for herbstluftwm hc() { herbstclient "$@" } hc emit_hook reload xsetroot -solid '#5A8E3A' # remove all existing keybindings hc keyunbind --all # keybindings # if you have a super key you will be much happier with Mod set to Mod4 Mod=Mod1 # Use alt as the main modifier #Mod=Mod4 # Use the super key as the main modifier hc keybind $Mod-Shift-q quit hc keybind $Mod-Shift-r reload hc keybind $Mod-Shift-c close hc keybind $Mod-Return spawn ${TERMINAL:-xterm} # use your $TERMINAL with xterm as fallback # basic movement # focusing clients hc keybind $Mod-Left focus left hc keybind $Mod-Down focus down hc keybind $Mod-Up focus up hc keybind $Mod-Right focus right hc keybind $Mod-h focus left hc keybind $Mod-j focus down hc keybind $Mod-k focus up hc keybind $Mod-l focus right # moving clients hc keybind $Mod-Shift-Left shift left hc keybind $Mod-Shift-Down shift down hc keybind $Mod-Shift-Up shift up hc keybind $Mod-Shift-Right shift right hc keybind $Mod-Shift-h shift left hc keybind $Mod-Shift-j shift down hc keybind $Mod-Shift-k shift up hc keybind $Mod-Shift-l shift right # splitting frames # create an empty frame at the specified direction hc keybind $Mod-u split bottom 0.5 hc keybind $Mod-o split right 0.5 # let the current frame explode into subframes hc keybind $Mod-Control-space split explode # resizing frames resizestep=0.05 hc keybind $Mod-Control-h resize left +$resizestep hc keybind $Mod-Control-j resize down +$resizestep hc keybind $Mod-Control-k resize up +$resizestep hc keybind $Mod-Control-l resize right +$resizestep hc keybind $Mod-Control-Left resize left +$resizestep hc keybind $Mod-Control-Down resize down +$resizestep hc keybind $Mod-Control-Up resize up +$resizestep hc keybind $Mod-Control-Right resize right +$resizestep # tags tag_names=( {1..9} ) tag_keys=( {1..9} 0 ) hc rename default "${tag_names[0]}" || true for i in ${!tag_names[@]} ; do hc add "${tag_names[$i]}" key="${tag_keys[$i]}" if ! [ -z "$key" ] ; then hc keybind "$Mod-$key" use_index "$i" hc keybind "$Mod-Shift-$key" move_index "$i" fi done # cycle through tags hc keybind $Mod-period use_index +1 --skip-visible hc keybind $Mod-comma use_index -1 --skip-visible # layouting hc keybind $Mod-r remove 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 hc mousebind $Mod-Button1 move hc mousebind $Mod-Button2 zoom hc mousebind $Mod-Button3 resize # focus hc keybind $Mod-BackSpace cycle_monitor hc keybind $Mod-Tab cycle_all +1 hc keybind $Mod-Shift-Tab cycle_all -1 hc keybind $Mod-c cycle hc keybind $Mod-i jumpto urgent # theme hc attr theme.tiling.reset 1 hc attr theme.floating.reset 1 hc set frame_border_active_color '#222222' hc set frame_border_normal_color '#101010' hc set frame_bg_normal_color '#565656' hc set frame_bg_active_color '#345F0C' hc set frame_border_width 1 hc set always_show_frame 1 hc set frame_bg_transparent 1 hc set frame_transparent_width 5 hc set frame_gap 4 hc attr theme.active.color '#9fbc00' hc attr theme.normal.color '#454545' hc attr theme.urgent.color orange hc attr theme.inner_width 1 hc attr theme.inner_color black hc attr theme.border_width 3 hc attr theme.floating.border_width 4 hc attr theme.floating.outer_width 1 hc attr theme.floating.outer_color black hc attr theme.active.inner_color '#3E4A00' hc attr theme.active.outer_color '#3E4A00' hc attr theme.background_color '#141414' hc set window_gap 0 hc set frame_padding 0 hc set smart_window_surroundings 0 hc set smart_frame_surroundings 1 hc set mouse_recenter_gap 0 # rules hc unrule -F #hc rule class=XTerm tag=3 # move all xterms to tag 3 hc rule focus=on # normally focus new clients #hc rule focus=off # normally do not focus new clients # give focus to most common terminals #hc rule class~'(.*[Rr]xvt.*|.*[Tt]erm|Konsole)' focus=on hc rule windowtype~'_NET_WM_WINDOW_TYPE_(DIALOG|UTILITY|SPLASH)' pseudotile=on hc rule windowtype='_NET_WM_WINDOW_TYPE_DIALOG' focus=on hc rule windowtype~'_NET_WM_WINDOW_TYPE_(NOTIFICATION|DOCK|DESKTOP)' manage=off # unlock, just to be sure hc unlock herbstclient set tree_style '╾│ ├└╼─┐' # do multi monitor setup here, e.g.: # hc set_monitors 1280x1024+0+0 1280x1024+1280+0 # or simply: # hc detect_monitors # find the panel panel=~/.config/herbstluftwm/panel.sh [ -x "$panel" ] || panel=/etc/xdg/herbstluftwm/panel.sh for monitor in $(herbstclient list_monitors | cut -d: -f1) ; do # start it on each monitor "$panel" $monitor & done herbstluftwm-0.7.0/share/panel.sh0000755000175000001440000001403012533670523016575 0ustar thorstenusers#!/usr/bin/env bash hc() { "${herbstclient_command[@]:-herbstclient}" "$@" ;} monitor=${1:-0} geometry=( $(herbstclient monitor_rect "$monitor") ) if [ -z "$geometry" ] ;then echo "Invalid monitor $monitor" exit 1 fi # geometry has the format W H X Y x=${geometry[0]} y=${geometry[1]} panel_width=${geometry[2]} panel_height=16 font="-*-fixed-medium-*-*-*-12-*-*-*-*-*-*-*" bgcolor=$(hc get frame_border_normal_color) selbg=$(hc get window_border_active_color) selfg='#101010' #### # Try to find textwidth binary. # In e.g. Ubuntu, this is named dzen2-textwidth. if which textwidth &> /dev/null ; then textwidth="textwidth"; elif which dzen2-textwidth &> /dev/null ; then textwidth="dzen2-textwidth"; else echo "This script requires the textwidth tool of the dzen2 project." exit 1 fi #### # true if we are using the svn version of dzen2 # depending on version/distribution, this seems to have version strings like # "dzen-" or "dzen-x.x.x-svn" if dzen2 -v 2>&1 | head -n 1 | grep -q '^dzen-\([^,]*-svn\|\),'; then dzen2_svn="true" else dzen2_svn="" fi if awk -Wv 2>/dev/null | head -1 | grep -q '^mawk'; then # mawk needs "-W interactive" to line-buffer stdout correctly # http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=593504 uniq_linebuffered() { awk -W interactive '$0 != l { print ; l=$0 ; fflush(); }' "$@" } else # other awk versions (e.g. gawk) issue a warning with "-W interactive", so # we don't want to use it there. uniq_linebuffered() { awk '$0 != l { print ; l=$0 ; fflush(); }' "$@" } fi hc pad $monitor $panel_height { ### Event generator ### # based on different input data (mpc, date, hlwm hooks, ...) this generates events, formed like this: # \t [...] # e.g. # date ^fg(#efefef)18:33^fg(#909090), 2013-10-^fg(#efefef)29 #mpc idleloop player & while true ; do # "date" output is checked once a second, but an event is only # generated if the output changed compared to the previous run. date +$'date\t^fg(#efefef)%H:%M^fg(#909090), %Y-%m-^fg(#efefef)%d' sleep 1 || break done > >(uniq_linebuffered) & childpid=$! hc --idle kill $childpid } 2> /dev/null | { IFS=$'\t' read -ra tags <<< "$(hc tag_status $monitor)" visible=true date="" windowtitle="" while true ; do ### Output ### # This part prints dzen data based on the _previous_ data handling run, # and then waits for the next event to happen. bordercolor="#26221C" separator="^bg()^fg($selbg)|" # draw tags for i in "${tags[@]}" ; do case ${i:0:1} in '#') echo -n "^bg($selbg)^fg($selfg)" ;; '+') echo -n "^bg(#9CA668)^fg(#141414)" ;; ':') echo -n "^bg()^fg(#ffffff)" ;; '!') echo -n "^bg(#FF0675)^fg(#141414)" ;; *) echo -n "^bg()^fg(#ababab)" ;; esac if [ ! -z "$dzen2_svn" ] ; then # clickable tags if using SVN dzen echo -n "^ca(1,\"${herbstclient_command[@]:-herbstclient}\" " echo -n "focus_monitor \"$monitor\" && " echo -n "\"${herbstclient_command[@]:-herbstclient}\" " echo -n "use \"${i:1}\") ${i:1} ^ca()" else # non-clickable tags if using older dzen echo -n " ${i:1} " fi done echo -n "$separator" echo -n "^bg()^fg() ${windowtitle//^/^^}" # small adjustments right="$separator^bg() $date $separator" right_text_only=$(echo -n "$right" | sed 's.\^[^(]*([^)]*)..g') # get width of right aligned text.. and add some space.. width=$($textwidth "$font" "$right_text_only ") echo -n "^pa($(($panel_width - $width)))$right" echo ### Data handling ### # This part handles the events generated in the event loop, and sets # internal variables based on them. The event and its arguments are # read into the array cmd, then action is taken depending on the event # name. # "Special" events (quit_panel/togglehidepanel/reload) are also handled # here. # wait for next event IFS=$'\t' read -ra cmd || break # find out event origin case "${cmd[0]}" in tag*) #echo "resetting tags" >&2 IFS=$'\t' read -ra tags <<< "$(hc tag_status $monitor)" ;; date) #echo "resetting date" >&2 date="${cmd[@]:1}" ;; quit_panel) exit ;; togglehidepanel) currentmonidx=$(hc list_monitors | sed -n '/\[FOCUS\]$/s/:.*//p') if [ "${cmd[1]}" -ne "$monitor" ] ; then continue fi if [ "${cmd[1]}" = "current" ] && [ "$currentmonidx" -ne "$monitor" ] ; then continue fi echo "^togglehide()" if $visible ; then visible=false hc pad $monitor 0 else visible=true hc pad $monitor $panel_height fi ;; reload) exit ;; focus_changed|window_title_changed) windowtitle="${cmd[@]:2}" ;; #player) # ;; esac done ### dzen2 ### # After the data is gathered and processed, the output of the previous block # gets piped to dzen2. } 2> /dev/null | dzen2 -w $panel_width -x $x -y $y -fn "$font" -h $panel_height \ -e 'button3=;button4=exec:herbstclient use_index -1;button5=exec:herbstclient use_index +1' \ -ta l -bg "$bgcolor" -fg '#efefef' herbstluftwm-0.7.0/share/_herbstclient0000644000175000001440000000061312533670523017711 0ustar thorstenusers#compdef herbstclient # zsh completion for herbstclient # add this to your zshrc before sourcing this #autoload -U compinit #compinit _herbstclient() { local IFS=$'\n' # compadd is documented in zshcompwid(1) compadd -QS '' -- "$@" $(herbstclient -q complete_shell "$((CURRENT-2))" "${(@)words[@]:1}") } # compdef is documented in zshcompsys(1) compdef _herbstclient herbstclient herbstluftwm-0.7.0/share/restartpanels.sh0000755000175000001440000000057312533670523020374 0ustar thorstenusers#!/usr/bin/env bash installdir=/ XDG_CONFIG_HOME="${XDG_CONFIG_HOME:-$HOME/.config}" defaultpanel="$XDG_CONFIG_HOME/herbstluftwm/panel.sh" [ -x "$defaultpanel" ] || defaultpanel="$installdir/etc/xdg/herbstluftwm/panel.sh" panelcmd="${1:-$defaultpanel}" herbstclient emit_hook quit_panel for i in $(herbstclient list_monitors | cut -d':' -f1) ; do "$panelcmd" $i & done herbstluftwm-0.7.0/share/herbstluftwm.svg0000644000175000001440000001367712533670523020426 0ustar thorstenusers image/svg+xml herbstluftwm-0.7.0/share/herbstluftwm.desktop0000644000175000001440000000016612533670523021265 0ustar thorstenusers[Desktop Entry] Encoding=UTF-8 Name=herbstluftwm Comment=Manual tiling window manager Exec=herbstluftwm Type=XSession herbstluftwm-0.7.0/BUGS0000644000175000001440000001171612533670523014530 0ustar thorstenusersBUGS ==== Some BUGS: - On some (currently not reproducable) situations the keyboard focus is not in the same client as the one that is marked to be focused. This happens with some applications (e.g. sakura) on some machines. It also happens when switching the monitor focus quickly. - keyboard focus stays in window, if cursor is over it and herbstluftwm-focus goes away - don't always re-layout all if only one thing changes (e.g. border_color) - don't reset border width on each layouting - don't call monitor_apply_layout() if only the focus changes, e.g. on an enternotify event. - herbstclient: add timeout if server does not respond - herbstclient: errorstatus2string2stderr - add function to put window into tag (and use it in manage, merge_tag, move) - the default panel.sh spawns too many shells and does not clean them up properly at quit. you should do something like killall panel.sh when logging out (this is only a workaround!). - raise_on_click doesn't work with gnome-settings-daemon - window_focus is called twice when switching desktops (before mapping windows and after mapping them). TODO: only do it once. - also update window title if *only* the _NET_WM_NAME changes - make focus_window(client->window, true, false); work - do not flicker if there are three monitors and the tag of the topmost is swapped with the tag of the most bottom monitor (flickering can be noticed at the middle monitor) - Fullscreen clients must not be above the panel of another monitor which actually is above that fullscreen window in the stacking order. (But it currently is, because we can't distinguish which monitor a panel is associated to or if a window is a panel at all). There is no clean solution yet, because we can't easily find out, which monitor a panel is associated to. One could automatically detect the monitor association of a panel by a panel's and a monitor's geometry, but those heuristics mostly fail if two monitors overlap heavily (which is the *only* case in which this bug is triggered). Steps to reproduce: Create to monitors which overlap (Use add_monitor, move_monitor and raise_monitor). Focus a fullscreen client on the lower monitor and focus a non-fullscreen client on the higher monitor and select the higher monitor. Effect: the non-fullscreen client on the higher monitor should be above the fullscreen window but is displayed below the fullscreen window on the lower monitor. - If gnome-terminal is mapped (e.g. by switching the tag) it sends a ConfigureRequest which is rejected. This causes flickering because the terminal content is drawn with the requested size first and again with the actual size. This bug only occurs at certain widths of gnome-terminal. - Do not flicker when removing a frame. Currently first the children frame is destroyed (especially hidden) and then the parent frame is shown again. The fix is to first show the parent frame (above of the children frames) and then hide the children. Planned features: - dump/load floating values - set range for some integer-setting-values - (optional) move cursor after focus change - optional focus selection after frame_split - focus_follows_mouse based on frames/monitors - mouse bindings in tiling mode - better xcompmgr support - keybind with keycodes - chain keys/key chains/... - lastfocused{window,frame,tag} - commands - own X-Properties to indicate window state (fullscreen, pseudotile, float,...) - alias commands: alias shortcmd = cmd default arg, alias shortcmd --delete - provide multiple mechanisms to split clients to child frames: * 100:0 (and vice versa) * 50:50 * clients before focus to the first child, others to the second - automatic split command which splits along the longest side - catchall for frames (this frame will catch all new clients) - try to find a empty frame for new clients - (currently unknown) handling of WM_TRANSIENT_FOR - move clients in pseudotile mode - make it configurable if clients are allowed to change their own floating size or even position - undo command which undos the last layout changes on a certain tag - load frame without changing it's properties to just change the child-frames. Something like: (split unchanged $child1 child2) - a more verbose tag_status: list_tags with one line per tag - cycle through all "invisible" (or non-focused) clients - setting to make focus_follows_mouse not apply for the empty area of pseudotiled frames - consequence: stack={top,underfocus,bottom} and setting to set the initial stacking position on manage or after moving a client to a tag - maybe shift_nth: like focus_nth except that it swaps those two windows inside the current frame herbstluftwm-0.7.0/Makefile0000644000175000001440000001326612533670523015507 0ustar thorstenusersinclude version.mk include config.mk include colors.mk HLWMSRC = $(wildcard src/*.cpp) HLWMOBJ = $(HLWMSRC:.cpp=.o) HLWMTARGET = herbstluftwm HCSRC = $(wildcard ipc-client/*.c) HCOBJ = $(HCSRC:.c=.o) HCTARGET = herbstclient TARGETS = $(HLWMTARGET) $(HCTARGET) OBJ = $(HLWMOBJ) $(HCOBJ) DEPS = $(OBJ:.o=.d) HERBSTCLIENTDOC = doc/herbstclient.txt HERBSTLUFTWMDOC = doc/herbstluftwm.txt TUTORIAL = doc/herbstluftwm-tutorial.txt .PHONY: depend all all-nodoc doc install install-nodoc info www .PHONY: cleandoc cleanwww cleandeps clean all: $(TARGETS) doc all-nodoc: $(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 $(call colorecho,CC,$<) $(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 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 $(call colorecho,RM,$(TARGETS)) $(VERBOSE) rm -f $(TARGETS) $(call colorecho,RM,$(OBJ)) $(VERBOSE) rm -f $(OBJ) cleandeps: $(call colorecho,RM,$(DEPS)) $(VERBOSE) rm -f $(DEPS) cleandoc: $(call colorecho,RM,doc/herbstclient.1) $(VERBOSE) rm -f doc/herbstclient.1 $(call colorecho,RM,doc/herbstclient.html) $(VERBOSE) rm -f doc/herbstclient.html $(call colorecho,RM,doc/herbstluftwm.1) $(VERBOSE) rm -f doc/herbstluftwm.1 $(call colorecho,RM,doc/herbstluftwm.html) $(VERBOSE) rm -f doc/herbstluftwm.html $(call colorecho,RM,doc/herbstluftwm-tutorial.7) $(VERBOSE) rm -f doc/herbstluftwm-tutorial.7 $(call colorecho,RM,doc/herbstluftwm-tutorial.html) $(VERBOSE) rm -f doc/herbstluftwm-tutorial.html doc: doc/herbstclient.1 \ doc/herbstclient.html \ doc/herbstluftwm.1 \ doc/herbstluftwm.html \ doc/herbstluftwm-tutorial.7 \ doc/herbstluftwm-tutorial.html tar: doc tar -czf $(TARFILE) `git ls-files` doc/*.html doc/*.[0-9] rm -rf $(TMPTARDIR) mkdir -p $(TMPTARDIR) 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,$@) $(VERBOSE) $(A2X) -f manpage -a "herbstluftwmversion=herbstluftwm $(VERSION)" -a "date=`date +%Y-%m-%d`" $< doc/%.html: doc/%.txt version.mk $(call colorecho,DOC,$@) $(VERBOSE) $(ASCIIDOC) $< install: all install-nodoc @echo "==> creating dirs..." $(MKDIR) '$(DESTDIR)$(MAN1DIR)' $(MKDIR) '$(DESTDIR)$(MAN7DIR)' $(MKDIR) '$(DESTDIR)$(DOCDIR)' @echo "==> copying files..." $(INSTALL) -m 644 doc/herbstclient.1 '$(DESTDIR)$(MAN1DIR)/' $(INSTALL) -m 644 doc/herbstluftwm.1 '$(DESTDIR)$(MAN1DIR)/' $(INSTALL) -m 644 doc/herbstluftwm-tutorial.7 '$(DESTDIR)$(MAN7DIR)/' $(INSTALL) -m 644 doc/herbstclient.html '$(DESTDIR)$(DOCDIR)/' $(INSTALL) -m 644 doc/herbstluftwm.html '$(DESTDIR)$(DOCDIR)/' $(INSTALL) -m 644 doc/herbstluftwm-tutorial.html '$(DESTDIR)$(DOCDIR)/' install-nodoc: all-nodoc @echo "==> creating dirs..." $(MKDIR) '$(DESTDIR)$(LICENSEDIR)' $(MKDIR) '$(DESTDIR)$(BINDIR)' $(MKDIR) '$(DESTDIR)$(DOCDIR)' $(MKDIR) '$(DESTDIR)$(EXAMPLESDIR)' $(MKDIR) '$(DESTDIR)$(BASHCOMPLETIONDIR)' $(MKDIR) '$(DESTDIR)$(CONFIGDIR)' $(MKDIR) '$(DESTDIR)$(ZSHCOMPLETIONDIR)' $(MKDIR) '$(DESTDIR)$(XSESSIONSDIR)' @echo "==> copying files..." $(INSTALL) $(TARGETS) '$(DESTDIR)$(BINDIR)/' $(INSTALL) -m 644 LICENSE '$(DESTDIR)$(LICENSEDIR)/' $(INSTALL) -m 644 BUGS '$(DESTDIR)$(DOCDIR)/' $(INSTALL) -m 644 NEWS '$(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)/' 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' -$(RM) '$(DESTDIR)$(MAN7DIR)/herbstluftwm-tutorial.7' -$(RM) '$(DESTDIR)$(DOCDIR)/herbstclient.html' -$(RM) '$(DESTDIR)$(DOCDIR)/herbstluftwm.html' -$(RM) '$(DESTDIR)$(DOCDIR)/herbstluftwm-tutorial.html' -$(RM) '$(DESTDIR)$(DOCDIR)/BUGS' -$(RM) '$(DESTDIR)$(DOCDIR)/NEWS' -$(RM) '$(DESTDIR)$(DOCDIR)/INSTALL' -$(RM) '$(DESTDIR)$(CONFIGDIR)/autostart' -$(RM) '$(DESTDIR)$(CONFIGDIR)/panel.sh' -$(RM) '$(DESTDIR)$(CONFIGDIR)/restartpanels.sh' -$(RM) '$(DESTDIR)$(BASHCOMPLETIONDIR)/herbstclient-completion' -$(RM) '$(DESTDIR)$(ZSHCOMPLETIONDIR)/_herbstclient' -$(RM) '$(DESTDIR)$(XSESSIONSDIR)/herbstluftwm.desktop' -$(RM) '$(DESTDIR)$(EXAMPLESDIR)/README' -$(RM) '$(DESTDIR)$(EXAMPLESDIR)'/*.sh @echo "==> deleting directories..." -$(RMDIR) '$(DESTDIR)$(EXAMPLESDIR)/' -$(RMDIR) '$(DESTDIR)$(DOCDIR)/' -$(RMDIR) '$(DESTDIR)$(CONFIGDIR)/' www: make -C www cleanwww: make -C www clean herbstluftwm-0.7.0/INSTALL0000644000175000001440000000615112533670523015073 0ustar thorstenusers===== 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 ---