about summary refs log tree commit diff stats
path: root/dwm.1
blob: 5402be3baf574625306f17318e7d8c406bebedb0 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
.TH DWM 1 dwm-VERSION
.SH NAME
dwm \- dynamic window manager
.SH SYNOPSIS
.B dwm
.RB [ \-v ]
.SH DESCRIPTION
.B dwm
is a dynamic window manager for X. It manages windows in tiling and floating
modes. Either mode can be applied dynamically, optimizing the environment for
the application in use and the task performed.
.P
In tiling mode windows are managed in a master and stacking column. The master
column contains the window which currently needs most attention, whereas the
stacking column contains all other windows. In floating mode windows can be
resized and moved freely. Dialog windows are always managed floating,
regardless of the mode selected.
.P
Windows are grouped by tags. Each window can be tagged with one or multiple
tags. Selecting a certain tag for viewing will display all windows with that
tag.
.P
.B dwm
contains a small status bar which displays all available tags, the mode, the
title of the focused window, and the text read from standard input. The tags of
the focused window are highlighted with a small point.
.P
.B dwm
draws a 1-pixel border around windows to indicate the focus state.
Unfocused windows contain a small bar in front of them displaying their title.
.SH OPTIONS
.TP
.B \-v
prints version information to standard output, then exits.
.SH USAGE
.SS Status bar
.TP
.B Standard input
is read and displayed in the status text area.
.TP
.B Button1
click on a tag label views all windows with that
.BR tag ,
click on the mode label toggles between
.B tiled
and
.B floating
mode.
.TP
.B Button3
click on a tag label adds/removes all windows with that
.B tag
to/from the view.
.TP
.B Mod1-Button1
click on a tag label applies that
.B tag
to the focused
.BR window .
.TP
.B Mod1-Button3
click on a tag label adds/removes that
.B tag
to/from the focused
.BR window .
.SS Keyboard commands
.TP
.B Mod1-Shift-Return
Start
.BR xterm (1).
.TP
.B Mod1-Tab
Focus next
.BR window .
.TP
.B Mod1-Shift-Tab
Focus previous
.BR window .
.TP
.B Mod1-Return
Zoom current
.B window
to the 
.B master
column
.RB ( tiling
mode only).
.TP
.B Mod1-m
Maximize current
.BR window .
.TP
.B Mod1-g
Grow current
.BR column
.RB ( tiling
mode only).
.TP
.B Mod1-s
Shrink current
.BR column
.RB ( tiling
mode only).
.TP
.B Mod1-Shift-[1..n]
Apply
.B nth tag
to current
.BR window .
.TP
.B Mod1-Control-Shift-[1..n]
Add/remove
.B nth tag
to/from current
.BR window .
.TP
.B Mod1-Shift-c
Close focused
.B window.
.TP
.B Mod1-space
Toggle between
.B tiled
and
.B floating
mode (affects
.BR "all windows" ).
.TP
.B Mod1-[1..n]
View all windows with
.BR "tag n" .
.TP
.B Mod1-0
View all windows with any
.BR "tag" .
.TP
.B Mod1-Control-[1..n]
Add/remove all windows with
.B tag n
to/from the view.
.TP
.B Mod1-Shift-q
Quit
.B dwm.
.SS Mouse commands
.TP
.B Mod1-Button1
Move current
.B window
while dragging
.RB ( floating
mode only).
.TP
.B Mod1-Button2
Zoom current
.B window
to the 
.B master
column
.RB ( tiling
mode only).
.TP
.B Mod1-Button3
Resize current
.B window
while dragging
.RB ( floating
mode only).
.SH CUSTOMIZATION
.B dwm
is customized by creating a custom config.h and (re)compiling the source
code. This keeps it fast, secure and simple.
.SH CAVEATS
The status bar may display
.B broken pipe
when
.B dwm
has been started by
.BR xdm (1),
because it closes standard output before executing
.BR dwm .
.SH SEE ALSO
.BR dmenu (1)
12'>612 613 614 615 616 617 618 619 620 621 622 623 624 625
/*
 * common.c
 * vim: expandtab:ts=4:sts=4:sw=4
 *
 * Copyright (C) 2012 - 2019 James Booth <boothj5@gmail.com>
 * Copyright (C) 2019 - 2023 Michael Vetter <jubalh@iodoru.org>
 *
 * This file is part of Profanity.
 *
 * Profanity is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * Profanity is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with Profanity.  If not, see <https://www.gnu.org/licenses/>.
 *
 * In addition, as a special exception, the copyright holders give permission to
 * link the code of portions of this program with the OpenSSL library under
 * certain conditions as described in each individual source file, and
 * distribute linked combinations including the two.
 *
 * You must obey the GNU General Public License in all respects for all of the
 * code used other than OpenSSL. If you modify file(s) with this exception, you
 * may extend this exception to your version of the file(s), but you are not
 * obligated to do so. If you do not wish to do so, delete this exception
 * statement from your version. If you delete this exception statement from all
 * source files in the program, then also delete it here.
 *
 */

#include "config.h"

#include <errno.h>
#include <sys/select.h>
#include <assert.h>
#include <stdlib.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>

#include <curl/curl.h>
#include <curl/easy.h>
#include <glib.h>
#include <gio/gio.h>

#ifdef HAVE_NCURSESW_NCURSES_H
#include <ncursesw/ncurses.h>
#elif HAVE_NCURSES_H
#include <ncurses.h>
#elif HAVE_CURSES_H
#include <curses.h>
#endif

#include "log.h"
#include "common.h"

struct curl_data_t
{
    char* buffer;
    size_t size;
};

static size_t _data_callback(void* ptr, size_t size, size_t nmemb, void* data);

void
auto_free_gchar(gchar** str)
{
    if (str == NULL)
        return;
    g_free(*str);
}

void
auto_free_gcharv(gchar*** args)
{
    if (args == NULL)
        return;
    g_strfreev(*args);
}

void
auto_free_char(char** str)
{
    if (str == NULL)
        return;
    free(*str);
}

gboolean
string_to_verbosity(const char* cmd, int* verbosity, gchar** err_msg)
{
    return strtoi_range(cmd, verbosity, 0, 3, err_msg);
}

gboolean
create_dir(const char* name)
{
    if (g_mkdir_with_parents(name, S_IRWXU) != 0) {
        log_error("Failed to create directory at '%s' with error '%s'", name, strerror(errno));
        return FALSE;
    }
    return TRUE;
}

gboolean
copy_file(const char* const sourcepath, const char* const targetpath, const gboolean overwrite_existing)
{
    GFile* source = g_file_new_for_path(sourcepath);
    GFile* dest = g_file_new_for_path(targetpath);
    GError* error = NULL;
    GFileCopyFlags flags = overwrite_existing ? G_FILE_COPY_OVERWRITE : G_FILE_COPY_NONE;
    gboolean success = g_file_copy(source, dest, flags, NULL, NULL, NULL, &error);
    if (error != NULL)
        g_error_free(error);
    g_object_unref(source);
    g_object_unref(dest);
    return success;
}

char*
str_replace(const char* string, const char* substr,
            const char* replacement)
{
    char* tok = NULL;
    char* newstr = NULL;
    char* head = NULL;

    if (string == NULL)
        return NULL;

    if (substr == NULL || replacement == NULL || (strcmp(substr, "") == 0))
        return strdup(string);

    newstr = strdup(string);
    head = newstr;

    while ((tok = strstr(head, substr))) {
        char* oldstr = newstr;
        newstr = malloc(strlen(oldstr) - strlen(substr) + strlen(replacement) + 1);

        if (newstr == NULL) {
            free(oldstr);
            return NULL;
        }

        memcpy(newstr, oldstr, tok - oldstr);
        memcpy(newstr + (tok - oldstr), replacement, strlen(replacement));
        memcpy(newstr + (tok - oldstr) + strlen(replacement),
               tok + strlen(substr),
               strlen(oldstr) - strlen(substr) - (tok - oldstr));
        memset(newstr + strlen(oldstr) - strlen(substr) + strlen(replacement), 0, 1);

        head = newstr + (tok - oldstr) + strlen(replacement);
        free(oldstr);
    }

    return newstr;
}

gboolean
strtoi_range(const char* str, int* saveptr, int min, int max, gchar** err_msg)
{
    char* ptr;
    int val;
    if (str == NULL) {
        if (err_msg)
            *err_msg = g_strdup_printf("'str' input pointer can not be NULL");
        return FALSE;
    }
    errno = 0;
    val = (int)strtol(str, &ptr, 0);
    if (errno != 0 || *str == '\0' || *ptr != '\0') {
        if (err_msg)
            *err_msg = g_strdup_printf("Could not convert \"%s\" to a number.", str);
        return FALSE;
    } else if (val < min || val > max) {
        if (err_msg)
            *err_msg = g_strdup_printf("Value %s out of range. Must be in %d..%d.", str, min, max);
        return FALSE;
    }

    *saveptr = val;

    return TRUE;
}

int
utf8_display_len(const char* const str)
{
    if (!str) {
        return 0;
    }

    int len = 0;
    gchar* curr = g_utf8_offset_to_pointer(str, 0);
    while (*curr != '\0') {
        gunichar curru = g_utf8_get_char(curr);
        if (g_unichar_iswide(curru)) {
            len += 2;
        } else {
            len++;
        }
        curr = g_utf8_next_char(curr);
    }

    return len;
}

char*
release_get_latest(void)
{
    char* url = "https://profanity-im.github.io/profanity_version.txt";

    CURL* handle = curl_easy_init();
    struct curl_data_t output;
    output.buffer = NULL;
    output.size = 0;

    curl_easy_setopt(handle, CURLOPT_URL, url);
    curl_easy_setopt(handle, CURLOPT_WRITEFUNCTION, _data_callback);
    curl_easy_setopt(handle, CURLOPT_TIMEOUT, 2);
    curl_easy_setopt(handle, CURLOPT_WRITEDATA, (void*)&output);

    curl_easy_perform(handle);
    curl_easy_cleanup(handle);

    if (output.buffer) {
        output.buffer[output.size++] = '\0';
        return output.buffer;
    } else {
        return NULL;
    }
}

gboolean
release_is_new(char* found_version)
{
    int curr_maj, curr_min, curr_patch, found_maj, found_min, found_patch;

    int parse_curr = sscanf(PACKAGE_VERSION, "%d.%d.%d", &curr_maj, &curr_min,
                            &curr_patch);
    int parse_found = sscanf(found_version, "%d.%d.%d", &found_maj, &found_min,
                             &found_patch);

    if (parse_found == 3 && parse_curr == 3) {
        if (found_maj > curr_maj) {
            return TRUE;
        } else if (found_maj == curr_maj && found_min > curr_min) {
            return TRUE;
        } else if (found_maj == curr_maj && found_min == curr_min
                   && found_patch > curr_patch) {
            return TRUE;
        } else {
            return FALSE;
        }
    } else {
        return FALSE;
    }
}

static size_t
_data_callback(void* ptr, size_t size, size_t nmemb, void* data)
{
    size_t realsize = size * nmemb;
    struct curl_data_t* mem = (struct curl_data_t*)data;
    mem->buffer = realloc(mem->buffer, mem->size + realsize + 1);

    if (mem->buffer) {
        memcpy(&(mem->buffer[mem->size]), ptr, realsize);
        mem->size += realsize;
        mem->buffer[mem->size] = 0;
    }

    return realsize;
}

char*
get_file_or_linked(char* loc, char* basedir)
{
    char* true_loc = NULL;

    // check for symlink
    if (g_file_test(loc, G_FILE_TEST_IS_SYMLINK)) {
        true_loc = g_file_read_link(loc, NULL);

        // if relative, add basedir
        if (!g_str_has_prefix(true_loc, "/") && !g_str_has_prefix(true_loc, "~")) {
            char* tmp = g_strdup_printf("%s/%s", basedir, true_loc);
            free(true_loc);
            true_loc = tmp;
        }
        // use given location
    } else {
        true_loc = strdup(loc);
    }

    return true_loc;
}

char*
strip_arg_quotes(const char* const input)
{
    char* unquoted = strdup(input);

    // Remove starting quote if it exists
    if (strchr(unquoted, '"')) {
        if (strchr(unquoted, ' ') + 1 == strchr(unquoted, '"')) {
            memmove(strchr(unquoted, '"'), strchr(unquoted, '"') + 1, strchr(unquoted, '\0') - strchr(unquoted, '"'));
        }
    }

    // Remove ending quote if it exists
    if (strchr(unquoted, '"')) {
        if (strchr(unquoted, '\0') - 1 == strchr(unquoted, '"')) {
            memmove(strchr(unquoted, '"'), strchr(unquoted, '"') + 1, strchr(unquoted, '\0') - strchr(unquoted, '"'));
        }
    }

    return unquoted;
}

gboolean
is_notify_enabled(void)
{
    gboolean notify_enabled = FALSE;

#ifdef HAVE_OSXNOTIFY
    notify_enabled = TRUE;
#endif
#ifdef HAVE_LIBNOTIFY
    notify_enabled = TRUE;
#endif
#ifdef PLATFORM_CYGWIN
    notify_enabled = TRUE;
#endif

    return notify_enabled;
}

GSList*
prof_occurrences(const char* const needle, const char* const haystack, int offset, gboolean whole_word, GSList** result)
{
    if (needle == NULL || haystack == NULL) {
        return *result;
    }

    do {
        gchar* haystack_curr = g_utf8_offset_to_pointer(haystack, offset);
        if (g_str_has_prefix(haystack_curr, needle)) {
            if (whole_word) {
                gunichar before = 0;
                gchar* haystack_before_ch = g_utf8_find_prev_char(haystack, haystack_curr);
                if (haystack_before_ch) {
                    before = g_utf8_get_char(haystack_before_ch);
                }

                gunichar after = 0;
                gchar* haystack_after_ch = haystack_curr + strlen(needle);
                if (haystack_after_ch[0] != '\0') {
                    after = g_utf8_get_char(haystack_after_ch);
                }

                if (!g_unichar_isalnum(before) && !g_unichar_isalnum(after)) {
                    *result = g_slist_append(*result, GINT_TO_POINTER(offset));
                }
            } else {
                *result = g_slist_append(*result, GINT_TO_POINTER(offset));
            }
        }

        offset++;
    } while (g_strcmp0(g_utf8_offset_to_pointer(haystack, offset), "\0") != 0);

    return *result;
}

int
is_regular_file(const char* path)
{
    struct stat st;
    int ret = stat(path, &st);
    if (ret != 0) {
        perror(NULL);
        return 0;
    }
    return S_ISREG(st.st_mode);
}

int
is_dir(const char* path)
{
    struct stat st;
    int ret = stat(path, &st);
    if (ret != 0) {
        perror(NULL);
        return 0;
    }
    return S_ISDIR(st.st_mode);
}

void
get_file_paths_recursive(const char* path, GSList** contents)
{
    if (!is_dir(path)) {
        return;
    }

    GDir* directory = g_dir_open(path, 0, NULL);
    const gchar* entry = g_dir_read_name(directory);
    gchar* full;
    while (entry) {
        if (g_str_has_suffix(path, "/")) {
            full = g_strdup_printf("%s%s", path, entry);
        } else {
            full = g_strdup_printf("%s/%s", path, entry);
        }

        if (is_dir(full)) {
            get_file_paths_recursive(full, contents);
        } else if (is_regular_file(full)) {
            *contents = g_slist_append(*contents, full);
        }

        entry = g_dir_read_name(directory);
    }
}

char*
get_random_string(int length)
{
    GRand* prng;
    char* rand;
    char alphabet[] = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
    int endrange = sizeof(alphabet) - 1;

    rand = calloc(length + 1, sizeof(char));

    prng = g_rand_new();

    for (int i = 0; i < length; i++) {
        rand[i] = alphabet[g_rand_int_range(prng, 0, endrange)];
    }
    g_rand_free(prng);

    return rand;
}

GSList*
get_mentions(gboolean whole_word, gboolean case_sensitive, const char* const message, const char* const nick)
{
    GSList* mentions = NULL;
    gchar* message_search = case_sensitive ? g_strdup(message) : g_utf8_strdown(message, -1);
    gchar* mynick_search = case_sensitive ? g_strdup(nick) : g_utf8_strdown(nick, -1);

    mentions = prof_occurrences(mynick_search, message_search, 0, whole_word, &mentions);

    g_free(message_search);
    g_free(mynick_search);

    return mentions;
}

gboolean
call_external(gchar** argv)
{
    GError* spawn_error;
    gboolean is_successful;

    GSpawnFlags flags = G_SPAWN_SEARCH_PATH | G_SPAWN_STDOUT_TO_DEV_NULL | G_SPAWN_STDERR_TO_DEV_NULL;

    is_successful = g_spawn_async(NULL, // Inherit the parent PWD
                                  argv,
                                  NULL, // Inherit the parent environment
                                  flags,
                                  NULL, NULL, NULL,
                                  &spawn_error);
    if (!is_successful) {
        gchar* cmd = g_strjoinv(" ", argv);
        log_error("Spawning '%s' failed with error '%s'", cmd, spawn_error ? spawn_error->message : "Unknown, spawn_error is NULL");

        g_error_free(spawn_error);
        g_free(cmd);
    }

    return is_successful;
}

gchar**
format_call_external_argv(const char* template, const char* url, const char* filename)
{
    gchar** argv = g_strsplit(template, " ", 0);

    guint num_args = 0;
    while (argv[num_args]) {
        if (0 == g_strcmp0(argv[num_args], "%u") && url != NULL) {
            g_free(argv[num_args]);
            argv[num_args] = g_strdup(url);
        } else if (0 == g_strcmp0(argv[num_args], "%p") && filename != NULL) {
            g_free(argv[num_args]);
            argv[num_args] = strdup(filename);
        }
        num_args++;
    }

    return argv;
}

gchar*
_unique_filename(const char* filename)
{
    gchar* unique = g_strdup(filename);

    unsigned int i = 0;
    while (g_file_test(unique, G_FILE_TEST_EXISTS)) {
        g_free(unique);

        if (i > 1000) { // Give up after 1000 attempts.
            return NULL;
        }

        unique = g_strdup_printf("%s.%u", filename, i);
        if (!unique) {
            return NULL;
        }

        i++;
    }

    return unique;
}

bool
_has_directory_suffix(const char* path)
{
    return (g_str_has_suffix(path, ".")
            || g_str_has_suffix(path, "..")
            || g_str_has_suffix(path, G_DIR_SEPARATOR_S));
}

char*
_basename_from_url(const char* url)
{
    const char* default_name = "index";

    GFile* file = g_file_new_for_commandline_arg(url);
    char* basename = g_file_get_basename(file);

    if (_has_directory_suffix(basename)) {
        g_free(basename);
        basename = strdup(default_name);
    }

    g_object_unref(file);

    return basename;
}

gchar*
get_expanded_path(const char* path)
{
    if (g_str_has_prefix(path, "file://")) {
        path += strlen("file://");
    }
    if (strlen(path) >= 2 && path[0] == '~' && path[1] == '/') {
        return g_strdup_printf("%s/%s", getenv("HOME"), path + 2);
    } else {
        return g_strdup_printf("%s", path);
    }
}

gchar*
unique_filename_from_url(const char* url, const char* path)
{
    gchar* realpath;

    // Default to './' as path when none has been provided.
    if (path == NULL) {
        realpath = g_strdup("./");
    } else {
        realpath = get_expanded_path(path);
    }

    // Resolves paths such as './../.' for path.
    GFile* target = g_file_new_for_commandline_arg(realpath);
    gchar* filename = NULL;

    if (_has_directory_suffix(realpath) || g_file_test(realpath, G_FILE_TEST_IS_DIR)) {
        // The target should be used as a directory. Assume that the basename
        // should be derived from the URL.
        char* basename = _basename_from_url(url);
        filename = g_build_filename(g_file_peek_path(target), basename, NULL);
        g_free(basename);
    } else {
        // Just use the target as filename.
        filename = g_build_filename(g_file_peek_path(target), NULL);
    }

    gchar* unique_filename = _unique_filename(filename);
    if (unique_filename == NULL) {
        g_free(filename);
        g_free(realpath);
        return NULL;
    }

    g_object_unref(target);
    g_free(filename);
    g_free(realpath);

    return unique_filename;
}

void
glib_hash_table_free(GHashTable* hash_table)
{
    g_hash_table_remove_all(hash_table);
    g_hash_table_unref(hash_table);
}
include <stdlib.h> #include <string.h> #include <X11/Xatom.h> #include <X11/Xutil.h> /* static functions */ static void resizetitle(Client *c) { int i; c->bw = 0; for(i = 0; i < TLast; i++) if(c->tags[i]) c->bw += textw(c->tags[i]); c->bw += textw(c->name); if(c->bw > *c->w) c->bw = *c->w + 2; c->bx = *c->x + *c->w - c->bw + 2; c->by = *c->y; if(c->tags[tsel]) XMoveResizeWindow(dpy, c->title, c->bx, c->by, c->bw, c->bh); else XMoveResizeWindow(dpy, c->title, c->bx + 2 * sw, c->by, c->bw, c->bh); } static int xerrordummy(Display *dsply, XErrorEvent *ee) { return 0; } /* extern functions */ void ban(Client *c) { XMoveWindow(dpy, c->win, *c->x + 2 * sw, *c->y); XMoveWindow(dpy, c->title, c->bx + 2 * sw, c->by); } void focus(Client *c) { Client *old = sel; XEvent ev; sel = c; if(old && old != c) drawtitle(old); drawtitle(c); XSetInputFocus(dpy, c->win, RevertToPointerRoot, CurrentTime); XSync(dpy, False); while(XCheckMaskEvent(dpy, EnterWindowMask, &ev)); } void focusnext(Arg *arg) { Client *c; if(!sel) return; if(!(c = getnext(sel->next, tsel))) c = getnext(clients, tsel); if(c) { higher(c); c->revert = sel; focus(c); } } void focusprev(Arg *arg) { Client *c; if(!sel) return; if((c = sel->revert && sel->revert->tags[tsel] ? sel->revert : NULL)) { higher(c); focus(c); } } Client * getclient(Window w) { Client *c; for(c = clients; c; c = c->next) if(c->win == w) return c; return NULL; } Client * getctitle(Window w) { Client *c; for(c = clients; c; c = c->next) if(c->title == w) return c; return NULL; } void gravitate(Client *c, Bool invert) { int dx = 0, dy = 0; switch(c->grav) { case StaticGravity: case NorthWestGravity: case NorthGravity: case NorthEastGravity: dy = c->border; break; case EastGravity: case CenterGravity: case WestGravity: dy = -(*c->h / 2) + c->border; break; case SouthEastGravity: case SouthGravity: case SouthWestGravity: dy = -(*c->h); break; default: break; } switch (c->grav) { case StaticGravity: case NorthWestGravity: case WestGravity: case SouthWestGravity: dx = c->border; break; case NorthGravity: case CenterGravity: case SouthGravity: dx = -(*c->w / 2) + c->border; break; case NorthEastGravity: case EastGravity: case SouthEastGravity: dx = -(*c->w + c->border); break; default: break; } if(invert) { dx = -dx; dy = -dy; } *c->x += dx; *c->y += dy; } void higher(Client *c) { XRaiseWindow(dpy, c->win); XRaiseWindow(dpy, c->title); } void killclient(Arg *arg) { if(!sel) return; if(sel->proto & WM_PROTOCOL_DELWIN) sendevent(sel->win, wmatom[WMProtocols], wmatom[WMDelete]); else XKillClient(dpy, sel->win); } void lower(Client *c) { XLowerWindow(dpy, c->title); XLowerWindow(dpy, c->win); } void manage(Window w, XWindowAttributes *wa) { int diff; Client *c; XSetWindowAttributes twa; Window trans; c = emallocz(sizeof(Client)); c->win = w; c->bx = c->fx = c->tx = wa->x; c->by = c->fy = c->ty = wa->y; c->bw = c->fw = c->tw = wa->width; c->fh = c->th = wa->height; c->bh = bh; diff = sw - c->fw; c->fx = random() % (diff ? diff : 1); diff = sh - c->fh - bh; c->fy = random() % (diff ? diff : 1); if(c->fy < bh) c->by = c->fy = c->ty = bh; c->border = 1; c->proto = getproto(c->win); setsize(c); XSelectInput(dpy, c->win, StructureNotifyMask | PropertyChangeMask | EnterWindowMask); XGetTransientForHint(dpy, c->win, &trans); twa.override_redirect = 1; twa.background_pixmap = ParentRelative; twa.event_mask = ExposureMask; c->title = XCreateWindow(dpy, root, c->bx, c->by, c->bw, c->bh, 0, DefaultDepth(dpy, screen), CopyFromParent, DefaultVisual(dpy, screen), CWOverrideRedirect | CWBackPixmap | CWEventMask, &twa); settags(c); c->next = clients; clients = c; XGrabButton(dpy, Button1, ControlMask, c->win, False, ButtonPressMask, GrabModeAsync, GrabModeSync, None, None); XGrabButton(dpy, Button1, MODKEY, c->win, False, ButtonPressMask, GrabModeAsync, GrabModeSync, None, None); XGrabButton(dpy, Button2, MODKEY, c->win, False, ButtonPressMask, GrabModeAsync, GrabModeSync, None, None); XGrabButton(dpy, Button3, MODKEY, c->win, False, ButtonPressMask, GrabModeAsync, GrabModeSync, None, None); if(!c->isfloat) c->isfloat = trans || ((c->maxw == c->minw) && (c->maxh == c->minh)); setgeom(c); settitle(c); arrange(NULL); /* mapping the window now prevents flicker */ if(c->tags[tsel]) { XMapRaised(dpy, c->win); XMapRaised(dpy, c->title); focus(c); } else { XMapRaised(dpy, c->win); XMapRaised(dpy, c->title); } } void maximize(Arg *arg) { if(!sel) return; *sel->x = sx; *sel->y = sy + bh; *sel->w = sw - 2 * sel->border; *sel->h = sh - 2 * sel->border - bh; higher(sel); resize(sel, False, TopLeft); } void pop(Client *c) { Client **l; for(l = &clients; *l && *l != c; l = &(*l)->next); *l = c->next; c->next = clients; /* pop */ clients = c; arrange(NULL); } void resize(Client *c, Bool inc, Corner sticky) { XConfigureEvent e; int right = *c->x + *c->w; int bottom = *c->y + *c->h; if(inc) { if(c->incw) *c->w -= (*c->w - c->basew) % c->incw; if(c->inch) *c->h -= (*c->h - c->baseh) % c->inch; } if(*c->x > sw) /* might happen on restart */ *c->x = sw - *c->w; if(*c->y > sh) *c->y = sh - *c->h; if(c->minw && *c->w < c->minw) *c->w = c->minw; if(c->minh && *c->h < c->minh) *c->h = c->minh; if(c->maxw && *c->w > c->maxw) *c->w = c->maxw; if(c->maxh && *c->h > c->maxh) *c->h = c->maxh; if(sticky == TopRight || sticky == BotRight) *c->x = right - *c->w; if(sticky == BotLeft || sticky == BotRight) *c->y = bottom - *c->h; resizetitle(c); XSetWindowBorderWidth(dpy, c->win, 1); XMoveResizeWindow(dpy, c->win, *c->x, *c->y, *c->w, *c->h); e.type = ConfigureNotify; e.event = c->win; e.window = c->win; e.x = *c->x; e.y = *c->y; e.width = *c->w; e.height = *c->h; e.border_width = c->border; e.above = None; e.override_redirect = False; XSendEvent(dpy, c->win, False, StructureNotifyMask, (XEvent *)&e); XSync(dpy, False); } void setgeom(Client *c) { if((arrange == dotile) && !c->isfloat) { c->x = &c->tx; c->y = &c->ty; c->w = &c->tw; c->h = &c->th; } else { c->x = &c->fx; c->y = &c->fy; c->w = &c->fw; c->h = &c->fh; } } void setsize(Client *c) { XSizeHints size; long msize; if(!XGetWMNormalHints(dpy, c->win, &size, &msize) || !size.flags) size.flags = PSize; c->flags = size.flags; if(c->flags & PBaseSize) { c->basew = size.base_width; c->baseh = size.base_height; } else c->basew = c->baseh = 0; if(c->flags & PResizeInc) { c->incw = size.width_inc; c->inch = size.height_inc; } else c->incw = c->inch = 0; if(c->flags & PMaxSize) { c->maxw = size.max_width; c->maxh = size.max_height; } else c->maxw = c->maxh = 0; if(c->flags & PMinSize) { c->minw = size.min_width; c->minh = size.min_height; } else c->minw = c->minh = 0; if(c->flags & PWinGravity) c->grav = size.win_gravity; else c->grav = NorthWestGravity; } void settitle(Client *c) { XTextProperty name; int n; char **list = NULL; name.nitems = 0; c->name[0] = 0; XGetTextProperty(dpy, c->win, &name, netatom[NetWMName]); if(!name.nitems) XGetWMName(dpy, c->win, &name); if(!name.nitems) return; if(name.encoding == XA_STRING) strncpy(c->name, (char *)name.value, sizeof(c->name)); else { if(XmbTextPropertyToTextList(dpy, &name, &list, &n) >= Success && n > 0 && *list) { strncpy(c->name, *list, sizeof(c->name)); XFreeStringList(list); } } XFree(name.value); resizetitle(c); } void unmanage(Client *c) { Client **l; XGrabServer(dpy); XSetErrorHandler(xerrordummy); XUngrabButton(dpy, AnyButton, AnyModifier, c->win); XDestroyWindow(dpy, c->title); for(l = &clients; *l && *l != c; l = &(*l)->next); *l = c->next; for(l = &clients; *l; l = &(*l)->next) if((*l)->revert == c) (*l)->revert = NULL; if(sel == c) sel = sel->revert ? sel->revert : clients; free(c); XSync(dpy, False); XSetErrorHandler(xerror); XUngrabServer(dpy); arrange(NULL); if(sel) focus(sel); } void zoom(Arg *arg) { Client *c; if(!sel) return; if(sel == getnext(clients, tsel) && sel->next) { if((c = getnext(sel->next, tsel))) sel = c; } pop(sel); focus(sel); }