@ -0,0 +1,3 @@ | |||
[submodule "gtk/themes/material-ocean"] | |||
path = gtk/themes/material-ocean | |||
url = https://github.com/material-ocean/Gtk-Theme.git |
@ -1,37 +1,48 @@ | |||
activitywatch-bin | |||
aic94xx-firmware | |||
antibody | |||
betterdiscord | |||
betterlockscreen | |||
capt-src | |||
checkupdates+aur | |||
checkupdates-aur | |||
cordless-bin | |||
direnv | |||
gconf | |||
ghidra-darcula | |||
git-secret | |||
gitkraken | |||
gksu | |||
glxinfo | |||
google-cloud-sdk | |||
i3lock-color | |||
ifuse | |||
jetbrains-toolbox | |||
libgksu | |||
ly | |||
mailspring | |||
mconnect-git | |||
nerd-fonts-hack | |||
ngrok | |||
numix-icon-theme-git | |||
perl-checkupdates-aur | |||
perl-www-aur | |||
plymouth-git | |||
plymouth-theme-cubes-git | |||
plymouth-theme-darth-vader-git | |||
postman-bin | |||
qt5-styleplugins | |||
rofi-bluetooth-git | |||
rofi-dmenu | |||
ruby-erubis | |||
ruby-xdg | |||
spicetify-cli | |||
spotify | |||
spotify-tui | |||
sublime-text-3 | |||
surf | |||
terminal_dimensions-git | |||
termpdf-git | |||
ttf-material-design-icons | |||
ttf-symbola | |||
vue-cli | |||
wd719x-firmware | |||
whatsapp-nativefier-dark | |||
yay | |||
xmenu | |||
yaft | |||
zoom |
@ -0,0 +1 @@ | |||
Subproject commit aaa7a1d92ce53ecddeab35faaf4d0648ae7f4e93 |
@ -1,149 +0,0 @@ | |||
configuration { | |||
modi: "window,run,ssh,drun"; | |||
/* width: 50;*/ | |||
lines: 15; | |||
/* columns: 1;*/ | |||
font: "Cascadia Code 12"; | |||
/* bw: 1;*/ | |||
/* location: 0;*/ | |||
/* padding: 5;*/ | |||
/* yoffset: 0;*/ | |||
/* xoffset: 0;*/ | |||
/* fixed-num-lines: true;*/ | |||
/* show-icons: false;*/ | |||
/* terminal: "rofi-sensible-terminal";*/ | |||
/* ssh-client: "ssh";*/ | |||
/* ssh-command: "{terminal} -e {ssh-client} {host} [-p {port}]";*/ | |||
/* run-command: "{cmd}";*/ | |||
/* run-list-command: "";*/ | |||
/* run-shell-command: "{terminal} -e {cmd}";*/ | |||
/* window-command: "wmctrl -i -R {window}";*/ | |||
/* window-match-fields: "all";*/ | |||
/* icon-theme: ;*/ | |||
/* drun-match-fields: "name,generic,exec,categories";*/ | |||
/* drun-show-actions: false;*/ | |||
/* drun-display-format: "{name} [<span weight='light' size='small'><i>({generic})</i></span>]";*/ | |||
/* disable-history: false;*/ | |||
/* ignored-prefixes: "";*/ | |||
/* sort: false;*/ | |||
/* sorting-method: ;*/ | |||
/* case-sensitive: false;*/ | |||
/* cycle: true;*/ | |||
/* sidebar-mode: false;*/ | |||
/* eh: 1;*/ | |||
/* auto-select: false;*/ | |||
/* parse-hosts: false;*/ | |||
/* parse-known-hosts: true;*/ | |||
/* combi-modi: "window,run";*/ | |||
/* matching: "normal";*/ | |||
/* tokenize: true;*/ | |||
/* m: "-5";*/ | |||
/* line-margin: 2;*/ | |||
/* line-padding: 1;*/ | |||
/* filter: ;*/ | |||
/* separator-style: "dash";*/ | |||
/* hide-scrollbar: false;*/ | |||
/* fullscreen: false;*/ | |||
/* fake-transparency: false;*/ | |||
/* dpi: -1;*/ | |||
/* threads: 0;*/ | |||
/* scrollbar-width: 8;*/ | |||
/* scroll-method: 0;*/ | |||
/* fake-background: "screenshot";*/ | |||
/* window-format: "{w} {c} {t}";*/ | |||
/* click-to-exit: true;*/ | |||
/* show-match: true;*/ | |||
/* theme: ;*/ | |||
/* color-normal: ;*/ | |||
/* color-urgent: ;*/ | |||
/* color-active: ;*/ | |||
/* color-window: ;*/ | |||
/* max-history-size: 25;*/ | |||
/* combi-hide-mode-prefix: false;*/ | |||
/* matching-negate-char: '-' /* unsupported */;*/ | |||
/* cache-dir: ;*/ | |||
/* pid: "/run/user/1000/rofi.pid";*/ | |||
/* display-window: ;*/ | |||
/* display-windowcd: ;*/ | |||
/* display-run: ;*/ | |||
/* display-ssh: ;*/ | |||
/* display-drun: ;*/ | |||
/* display-combi: ;*/ | |||
/* display-keys: ;*/ | |||
/* kb-primary-paste: "Control+V,Shift+Insert";*/ | |||
/* kb-secondary-paste: "Control+v,Insert";*/ | |||
/* kb-clear-line: "Control+w";*/ | |||
/* kb-move-front: "Control+a";*/ | |||
/* kb-move-end: "Control+e";*/ | |||
/* kb-move-word-back: "Alt+b,Control+Left";*/ | |||
/* kb-move-word-forward: "Alt+f,Control+Right";*/ | |||
/* kb-move-char-back: "Left,Control+b";*/ | |||
/* kb-move-char-forward: "Right,Control+f";*/ | |||
/* kb-remove-word-back: "Control+Alt+h,Control+BackSpace";*/ | |||
/* kb-remove-word-forward: "Control+Alt+d";*/ | |||
/* kb-remove-char-forward: "Delete,Control+d";*/ | |||
/* kb-remove-char-back: "BackSpace,Shift+BackSpace,Control+h";*/ | |||
/* kb-remove-to-eol: "Control+k";*/ | |||
/* kb-remove-to-sol: "Control+u";*/ | |||
/* kb-accept-entry: "Control+j,Control+m,Return,KP_Enter";*/ | |||
/* kb-accept-custom: "Control+Return";*/ | |||
/* kb-accept-alt: "Shift+Return";*/ | |||
/* kb-delete-entry: "Shift+Delete";*/ | |||
/* kb-mode-next: "Shift+Right,Control+Tab";*/ | |||
/* kb-mode-previous: "Shift+Left,Control+ISO_Left_Tab";*/ | |||
/* kb-row-left: "Control+Page_Up";*/ | |||
/* kb-row-right: "Control+Page_Down";*/ | |||
/* kb-row-up: "Up,Control+p,ISO_Left_Tab";*/ | |||
/* kb-row-down: "Down,Control+n";*/ | |||
/* kb-row-tab: "Tab";*/ | |||
/* kb-page-prev: "Page_Up";*/ | |||
/* kb-page-next: "Page_Down";*/ | |||
/* kb-row-first: "Home,KP_Home";*/ | |||
/* kb-row-last: "End,KP_End";*/ | |||
/* kb-row-select: "Control+space";*/ | |||
/* kb-screenshot: "Alt+S";*/ | |||
/* kb-ellipsize: "Alt+period";*/ | |||
/* kb-toggle-case-sensitivity: "grave,dead_grave";*/ | |||
/* kb-toggle-sort: "Alt+grave";*/ | |||
/* kb-cancel: "Escape,Control+g,Control+bracketleft";*/ | |||
/* kb-custom-1: "Alt+1";*/ | |||
/* kb-custom-2: "Alt+2";*/ | |||
/* kb-custom-3: "Alt+3";*/ | |||
/* kb-custom-4: "Alt+4";*/ | |||
/* kb-custom-5: "Alt+5";*/ | |||
/* kb-custom-6: "Alt+6";*/ | |||
/* kb-custom-7: "Alt+7";*/ | |||
/* kb-custom-8: "Alt+8";*/ | |||
/* kb-custom-9: "Alt+9";*/ | |||
/* kb-custom-10: "Alt+0";*/ | |||
/* kb-custom-11: "Alt+exclam";*/ | |||
/* kb-custom-12: "Alt+at";*/ | |||
/* kb-custom-13: "Alt+numbersign";*/ | |||
/* kb-custom-14: "Alt+dollar";*/ | |||
/* kb-custom-15: "Alt+percent";*/ | |||
/* kb-custom-16: "Alt+dead_circumflex";*/ | |||
/* kb-custom-17: "Alt+ampersand";*/ | |||
/* kb-custom-18: "Alt+asterisk";*/ | |||
/* kb-custom-19: "Alt+parenleft";*/ | |||
/* kb-select-1: "Super+1";*/ | |||
/* kb-select-2: "Super+2";*/ | |||
/* kb-select-3: "Super+3";*/ | |||
/* kb-select-4: "Super+4";*/ | |||
/* kb-select-5: "Super+5";*/ | |||
/* kb-select-6: "Super+6";*/ | |||
/* kb-select-7: "Super+7";*/ | |||
/* kb-select-8: "Super+8";*/ | |||
/* kb-select-9: "Super+9";*/ | |||
/* kb-select-10: "Super+0";*/ | |||
/* ml-row-left: "ScrollLeft";*/ | |||
/* ml-row-right: "ScrollRight";*/ | |||
/* ml-row-up: "ScrollUp";*/ | |||
/* ml-row-down: "ScrollDown";*/ | |||
/* me-select-entry: "MousePrimary";*/ | |||
/* me-accept-entry: "MouseDPrimary";*/ | |||
/* me-accept-custom: "Control+MouseDPrimary";*/ | |||
} | |||
@import "/home/yigit/.config/rofi/themes/dmenu.rasi" | |||
/* vim:ft=css |
@ -1,10 +0,0 @@ | |||
action=$(echo ' | |||
' | rofi -dmenu -theme music_actions ) | |||
if [ $action = '' ] | |||
then | |||
playerctl previous & | |||
elif [ $action = '' ] | |||
then | |||
playerctl next & | |||
fi |
@ -1,109 +0,0 @@ | |||
/** | |||
* User: qball | |||
* Copyright: Dave Davenport | |||
*/ | |||
* { | |||
text-color: #f4f2f0; | |||
background-color: rgba(0,0,0,0); | |||
dark: #2c2541; | |||
// Black | |||
black: #3d335c; | |||
lightblack: #483c6c; | |||
// | |||
// Red | |||
red: #cd5c5c; | |||
lightred: #cc5533; | |||
// | |||
// Green | |||
green: #86af80; | |||
lightgreen: #88cc22; | |||
// | |||
// Yellow | |||
yellow: #e8ae5b; | |||
lightyellow: #ffa75d; | |||
// | |||
// Blue | |||
blue: #6495ed; | |||
lightblue: #87ceeb; | |||
// | |||
// Magenta | |||
magenta: #deb887; | |||
lightmagenta: #996600; | |||
// | |||
// Cyan | |||
cyan: #b0c4de; | |||
lightcyan: #b0c4de; | |||
// | |||
// White | |||
white: #bbaa99; | |||
lightwhite: #ddccbb; | |||
// | |||
// Bold, Italic, Underline | |||
highlight: bold #ffffff; | |||
} | |||
#window { | |||
height: 50%; | |||
width: 15em; | |||
location: north east; | |||
anchor: north east; | |||
border: 0px 0px 0px 0px; | |||
x-offset: -30px; | |||
y-offset: 39px; | |||
text-color: @lightwhite; | |||
border-radius: 0px 0px 19px 19px; | |||
} | |||
#mode-switcher { | |||
border: 2px 0px 0px 0px; | |||
background-color: @lightblack; | |||
padding: 4px; | |||
} | |||
#button selected { | |||
border-color: @lightgreen; | |||
text-color: @lightgreen; | |||
} | |||
#inputbar { | |||
background-color: #2c2541; | |||
text-color: @lightgreen; | |||
padding: 4px 4px 4px 4px; | |||
border: 0px 0px 0px 0px; | |||
} | |||
#mainbox { | |||
expand: true; | |||
background-color: #1c1c1cee; | |||
spacing: 1em; | |||
} | |||
#listview { | |||
padding: 0em 1em 0em 1em; | |||
dynamic: false; | |||
lines: 0; | |||
} | |||
#prompt { | |||
margin: 0px 6px 0px 0px; | |||
} | |||
#element selected normal { | |||
background-color: #da0f7a; | |||
} | |||
#element normal active { | |||
text-color: @lightblue; | |||
} | |||
#element normal urgent { | |||
text-color: @lightred; | |||
} | |||
#element alternate normal { | |||
} | |||
#element alternate active { | |||
text-color: @lightblue; | |||
} | |||
#element alternate urgent { | |||
text-color: @lightred; | |||
} | |||
#element selected active { | |||
background-color: @lightblue; | |||
text-color: @dark; | |||
} | |||
#element selected urgent { | |||
background-color: @lightred; | |||
text-color: @dark; | |||
} |
@ -1,149 +0,0 @@ | |||
/* vim:ft=css */ | |||
/************************************************ | |||
* ROFI Color theme | |||
* User: leofa | |||
* Copyright: 2017 leofa | |||
***********************************************/ | |||
* { | |||
selected-normal-foreground: rgba ( 249, 249, 249, 100 % ); | |||
foreground: rgba ( 248, 248, 242, 100 % ); | |||
normal-foreground: @foreground; | |||
alternate-normal-background: rgba ( 35, 49, 63, 95%); | |||
red: rgba ( 189, 147, 249, 100 % ); | |||
selected-urgent-foreground: rgba ( 249, 249, 249, 100 % ); | |||
blue: rgba ( 38, 139, 210, 100 % ); | |||
urgent-foreground: rgba ( 204, 102, 102, 100 % ); | |||
alternate-urgent-background: rgba ( 75, 81, 96, 90 % ); | |||
active-foreground: rgba ( 101, 172, 255, 100 % ); | |||
lightbg: rgba ( 238, 232, 213, 100 % ); | |||
selected-active-foreground: rgba ( 249, 249, 249, 100 % ); | |||
alternate-active-background: rgba ( 52,73,94, 60%); | |||
background: rgba ( 189, 147, 249, 95 % ); | |||
alternate-normal-foreground: @foreground; | |||
normal-background: @background; | |||
lightfg: rgba ( 88, 104, 117, 100 % ); | |||
selected-normal-background: rgba ( 189, 147, 249, 100 % ); | |||
border-color: rgba ( 124, 131, 137, 100 % ); | |||
spacing: 2; | |||
separatorcolor: rgba ( 29, 31, 33, 100 % ); | |||
urgent-background: rgba ( 29, 31, 33, 17 % ); | |||
selected-urgent-background: rgba ( 189, 147, 249, 100 % ); | |||
alternate-urgent-foreground: @urgent-foreground; | |||
background-color: rgba ( 0, 0, 0, 0 % ); | |||
alternate-active-foreground: @active-foreground; | |||
active-background: rgba ( 29, 31, 33, 17 % ); | |||
selected-active-background: rgba ( 189, 147, 249, 100 % ); | |||
} | |||
#window { | |||
background-color: @background; | |||
border: 1; | |||
padding: 5; | |||
} | |||
#mainbox { | |||
border: 0; | |||
padding: 0; | |||
} | |||
#message { | |||
border: 2px 0px 0px ; | |||
border-color: @separatorcolor; | |||
padding: 1px ; | |||
} | |||
#textbox { | |||
text-color: @foreground; | |||
} | |||
#listview { | |||
fixed-height: 0; | |||
border: 0px 0px 0px ; | |||
border-color: @separatorcolor; | |||
spacing: 2px ; | |||
scrollbar: true; | |||
padding: 2px 0px 0px ; | |||
} | |||
#element { | |||
border: 0; | |||
padding: 1px; | |||
} | |||
#element.normal.normal { | |||
background-color: @normal-background; | |||
text-color: @normal-foreground; | |||
} | |||
#element.normal.urgent { | |||
background-color: @urgent-background; | |||
text-color: @urgent-foreground; | |||
} | |||
#element.normal.active { | |||
background-color: @active-background; | |||
text-color: @active-foreground; | |||
} | |||
#element.selected.normal { | |||
background-color: @selected-normal-background; | |||
text-color: @selected-normal-foreground; | |||
} | |||
#element.selected.urgent { | |||
background-color: @selected-urgent-background; | |||
text-color: @selected-urgent-foreground; | |||
} | |||
#element.selected.active { | |||
background-color: @selected-active-background; | |||
text-color: @selected-active-foreground; | |||
} | |||
#element.alternate.normal { | |||
background-color: @alternate-normal-background; | |||
text-color: @alternate-normal-foreground; | |||
} | |||
#element.alternate.urgent { | |||
background-color: @alternate-urgent-background; | |||
text-color: @alternate-urgent-foreground; | |||
} | |||
#element.alternate.active { | |||
background-color: @alternate-active-background; | |||
text-color: @alternate-active-foreground; | |||
} | |||
#scrollbar { | |||
width: 4px ; | |||
border: 0; | |||
handle-color: @normal-foreground; | |||
handle-width: 8px ; | |||
padding: 0; | |||
} | |||
#sidebar { | |||
border: 0px 0px 0px; | |||
padding: 5px 0 0; | |||
border-color: @separatorcolor; | |||
} | |||
#button { | |||
spacing: 0; | |||
text-color: @normal-foreground; | |||
} | |||
#button.selected { | |||
background-color: @selected-normal-background; | |||
text-color: @selected-normal-foreground; | |||
} | |||
#inputbar { | |||
spacing: 0; | |||
text-color: @normal-foreground; | |||
padding: 1px ; | |||
} | |||
#case-indicator { | |||
spacing: 0; | |||
text-color: @normal-foreground; | |||
} | |||
#entry { | |||
spacing: 0; | |||
text-color: @normal-foreground; | |||
} | |||
#prompt { | |||
spacing: 0; | |||
text-color: @normal-foreground; | |||
} | |||
#inputbar { | |||
children: [ prompt,textbox-prompt-colon,entry,case-indicator ]; | |||
} | |||
#textbox-prompt-colon { | |||
expand: false; | |||
str: ":"; | |||
margin: 0px 0.3em 0em 0em ; | |||
text-color: @normal-foreground; | |||
} |
@ -1,44 +0,0 @@ | |||
/** | |||
* ROFI Color theme | |||
* User: Qball | |||
* Copyright: Dave Davenport | |||
*/ | |||
* { | |||
background-color: rgba(40, 42, 54, 1); | |||
border-color: rgba(248, 248, 242, 1); | |||
text-color: rgba(248, 248, 242, 1); | |||
font: "Hack Nerd Font 10"; | |||
} | |||
window { | |||
anchor: north; | |||
location: north; | |||
width: 100%; | |||
padding: 6px; | |||
children: [ horibox ]; | |||
} | |||
horibox { | |||
orientation: horizontal; | |||
children: [ prompt, entry, listview ]; | |||
} | |||
listview { | |||
layout: horizontal; | |||
spacing: 5px; | |||
lines: 100; | |||
} | |||
entry { | |||
expand: false; | |||
width: 10em; | |||
} | |||
element { | |||
padding: 0px 2px; | |||
} | |||
element selected { | |||
background-color: rgba(189, 147, 249, 1); | |||
} | |||
/* vim:ft=css |
@ -0,0 +1,290 @@ | |||
#!/usr/bin/env bash | |||
# _ _ _ _ _ _ | |||
# __| |_ __ ___ ___ _ __ _ _ | |__ | |_ _ ___| |_ ___ ___ | |_ | |__ | |||
# / _` | '_ ` _ \ / _ \ '_ \| | | |_____| '_ \| | | | |/ _ \ __/ _ \ / _ \| __|| '_ \ | |||
# | (_| | | | | | | __/ | | | |_| |_____| |_) | | |_| | __/ || (_) | (_) | |_ | | | | | |||
# \__,_|_| |_| |_|\___|_| |_|\__,_| |_.__/|_|\__,_|\___|\__\___/ \___/ \__||_| |_| | |||
# | |||
# Author: Nick Clyde (clydedroid) | |||
# dmenu support by: Layerex | |||
# | |||
# A script that generates a dmenu menu that uses bluetoothctl to | |||
# connect to bluetooth devices and display status info. | |||
# | |||
# Inspired by networkmanager-dmenu (https://github.com/firecat53/networkmanager-dmenu) | |||
# Thanks to x70b1 (https://github.com/polybar/polybar-scripts/tree/master/polybar-scripts/system-bluetooth-bluetoothctl) | |||
# | |||
# Depends on: | |||
# Arch repositories: dmenu, bluez-utils (contains bluetoothctl) | |||
# Checks if bluetooth controller is powered on | |||
power_on() { | |||
if bluetoothctl show | grep -q "Powered: yes"; then | |||
return 0 | |||
else | |||
return 1 | |||
fi | |||
} | |||
# Toggles power state | |||
toggle_power() { | |||
if power_on; then | |||
bluetoothctl power off | |||
else | |||
bluetoothctl power on | |||
fi | |||
} | |||
# Checks if controller is scanning for new devices | |||
scan_on() { | |||
if bluetoothctl show | grep -q "Discovering: yes"; then | |||
echo "Scan: on" | |||
return 0 | |||
else | |||
echo "Scan: off" | |||
return 1 | |||
fi | |||
} | |||
# Toggles scanning state | |||
toggle_scan() { | |||
if scan_on; then | |||
kill $(pgrep -f "bluetoothctl scan on") | |||
bluetoothctl scan off | |||
else | |||
bluetoothctl scan on & | |||
fi | |||
} | |||
# Checks if controller is able to pair to devices | |||
pairable_on() { | |||
if bluetoothctl show | grep -q "Pairable: yes"; then | |||
echo "Pairable: on" | |||
return 0 | |||
else | |||
echo "Pairable: off" | |||
return 1 | |||
fi | |||
} | |||
# Toggles pairable state | |||
toggle_pairable() { | |||
if pairable_on; then | |||
bluetoothctl pairable off | |||
else | |||
bluetoothctl pairable on | |||
fi | |||
} | |||
# Checks if controller is discoverable by other devices | |||
discoverable_on() { | |||
if bluetoothctl show | grep -q "Discoverable: yes"; then | |||
echo "Discoverable: on" | |||
return 0 | |||
else | |||
echo "Discoverable: off" | |||
return 1 | |||
fi | |||
} | |||
# Toggles discoverable state | |||
toggle_discoverable() { | |||
if discoverable_on; then | |||
bluetoothctl discoverable off | |||
else | |||
bluetoothctl discoverable on | |||
fi | |||
} | |||
# Checks if a device is connected | |||
device_connected() { | |||
device_info=$(bluetoothctl info "$1") | |||
if echo "$device_info" | grep -q "Connected: yes"; then | |||
return 0 | |||
else | |||
return 1 | |||
fi | |||
} | |||
# Toggles device connection | |||
toggle_connection() { | |||
if device_connected $1; then | |||
bluetoothctl disconnect $1 | |||
else | |||
bluetoothctl connect $1 | |||
fi | |||
} | |||
# Checks if a device is paired | |||
device_paired() { | |||
device_info=$(bluetoothctl info "$1") | |||
if echo "$device_info" | grep -q "Paired: yes"; then | |||
echo "Paired: yes" | |||
return 0 | |||
else | |||
echo "Paired: no" | |||
return 1 | |||
fi | |||
} | |||
# Toggles device paired state | |||
toggle_paired() { | |||
if device_paired $1; then | |||
bluetoothctl remove $1 | |||
else | |||
bluetoothctl pair $1 | |||
fi | |||
} | |||
# Checks if a device is trusted | |||
device_trusted() { | |||
device_info=$(bluetoothctl info "$1") | |||
if echo "$device_info" | grep -q "Trusted: yes"; then | |||
echo "Trusted: yes" | |||
return 0 | |||
else | |||
echo "Trusted: no" | |||
return 1 | |||
fi | |||
} | |||
# Toggles device connection | |||
toggle_trust() { | |||
if device_trusted $1; then | |||
bluetoothctl untrust $1 | |||
else | |||
bluetoothctl trust $1 | |||
fi | |||
} | |||
# Prints a short string with the current bluetooth status | |||
# Useful for status bars like polybar, etc. | |||
print_status() { | |||
if power_on; then | |||
printf '' | |||
mapfile -t paired_devices < <(bluetoothctl paired-devices | grep Device | cut -d ' ' -f 2) | |||
counter=0 | |||
for device in "${paired_devices[@]}"; do | |||
if device_connected $device; then | |||
device_alias=$(bluetoothctl info $device | grep "Alias" | cut -d ' ' -f 2-) | |||
if [ $counter -gt 0 ]; then | |||
printf ", %s" "$device_alias" | |||
else | |||
printf " %s" "$device_alias" | |||
fi | |||
((counter++)) | |||
fi | |||
done | |||
if [ $counter -eq 0 ]; then | |||
printf " On" | |||
fi | |||
else | |||
echo " Off" | |||
fi | |||
} | |||
# A submenu for a specific device that allows connecting, pairing, and trusting | |||
device_menu() { | |||
device=$1 | |||
# Get device name and mac address | |||
device_name=$(echo $device | cut -d ' ' -f 3-) | |||
mac=$(echo $device | cut -d ' ' -f 2) | |||
# Build options | |||
if device_connected $mac; then | |||
connected="Connected: yes" | |||
else | |||
connected="Connected: no" | |||
fi | |||
paired=$(device_paired $mac) | |||
trusted=$(device_trusted $mac) | |||
options="$connected\n$paired\n$trusted" | |||
# Open dmenu menu, read chosen option | |||
chosen="$(echo -e "$options" | $dmenu_command "$device_name")" | |||
# Match chosen option to command | |||
case $chosen in | |||
"") | |||
echo "No option chosen." | |||
;; | |||
$connected) | |||
toggle_connection $mac | |||
;; | |||
$paired) | |||
toggle_paired $mac | |||
;; | |||
$trusted) | |||
toggle_trust $mac | |||
;; | |||
esac | |||
} | |||
# Opens a dmenu menu with current bluetooth status and options to connect | |||
show_menu() { | |||
# Get menu options | |||
if power_on; then | |||
power="Power: on" | |||
# Human-readable names of devices, one per line | |||
# If scan is off, will only list paired devices | |||
devices=$(bluetoothctl devices | grep Device | cut -d ' ' -f 3-) | |||
# Get controller flags | |||
scan=$(scan_on) | |||
pairable=$(pairable_on) | |||
discoverable=$(discoverable_on) | |||
divider="---------" | |||
# Options passed to dmenu | |||
options="$devices\n$divider\n$power\n$scan\n$pairable\n$discoverable" | |||
else | |||
power="Power: off" | |||
options="$power" | |||
fi | |||
# Open dmenu menu, read chosen option | |||
chosen="$(echo -e "$options" | $dmenu_command "Bluetooth")" | |||
# Match chosen option to command | |||
case $chosen in | |||
"" | $divider) | |||
echo "No option chosen." | |||
;; | |||
$power) | |||
toggle_power | |||
;; | |||
$scan) | |||
toggle_scan | |||
;; | |||
$discoverable) | |||
toggle_discoverable | |||
;; | |||
$pairable) | |||
toggle_pairable | |||
;; | |||
*) | |||
device=$(bluetoothctl devices | grep "$chosen") | |||
# Open a submenu if a device is selected | |||
if [[ $device ]]; then device_menu "$device"; fi | |||
;; | |||
esac | |||
} | |||
# dmenu command to pipe into, can add any options here | |||
dmenu_command="dmenu -z 1900 -x 10 -y 10 -i -p" | |||
case "$1" in | |||
--status) | |||
print_status | |||
;; | |||
*) | |||
show_menu | |||
;; | |||
esac |
@ -0,0 +1,5 @@ | |||
#!/bin/sh | |||
tmpfile=$(mktemp /tmp/st-edit.XXXXXX) | |||
trap 'rm "$tmpfile"' 0 1 15 | |||
cat > "$tmpfile" | |||
st -e "$EDITOR" "$tmpfile" |
@ -0,0 +1,7 @@ | |||
#!/bin/sh | |||
cat <<EOF | xmenu | |||
[]= Tiled Layout 0 | |||
><> Floating Layout 1 | |||
[M] Monocle Layout 2 | |||
EOF |
@ -0,0 +1,874 @@ | |||
#!/usr/bin/env python3 | |||
# encoding:utf8 | |||
"""NetworkManager command line dmenu script. | |||
To add new connections or enable/disable networking requires policykit | |||
permissions setup per: | |||
https://wiki.archlinux.org/index.php/NetworkManager#Set_up_PolicyKit_permissions | |||
OR running the script as root | |||
Add dmenu formatting options and default terminal if desired to | |||
~/.config/networkmanager-dmenu/config.ini | |||
""" | |||
import pathlib | |||
import struct | |||
import configparser | |||
import itertools | |||
import locale | |||
import os | |||
from os.path import expanduser | |||
import shlex | |||
import sys | |||
import uuid | |||
from subprocess import Popen, PIPE | |||
import gi | |||
gi.require_version('NM', '1.0') | |||
from gi.repository import GLib, NM # pylint: disable=wrong-import-position | |||
ENV = os.environ.copy() | |||
ENV['LC_ALL'] = 'C' | |||
ENC = locale.getpreferredencoding() | |||
CLIENT = NM.Client.new(None) | |||
LOOP = GLib.MainLoop() | |||
CONNS = CLIENT.get_connections() | |||
CONF = configparser.ConfigParser() | |||
CONF.read(expanduser("~/.config/networkmanager-dmenu/config.ini")) | |||
def dmenu_cmd(num_lines, prompt="Networks", active_lines=None): # pylint: disable=too-many-branches | |||
"""Parse config.ini if it exists and add options to the dmenu command | |||
Args: args - num_lines: number of lines to display | |||
prompt: prompt to show | |||
Returns: command invocation (as a list of strings) for | |||
dmenu -l <num_lines> -p <prompt> -i ... | |||
""" | |||
dmenu_command = "dmenu" | |||
if not CONF.sections(): | |||
res = [dmenu_command, "-z", "1900", "-x", "10", "-y", "10", "-i", "-l", str(num_lines), "-p", str(prompt)] | |||
res.extend(sys.argv[1:]) | |||
return res | |||
if CONF.has_section('dmenu'): | |||
args = CONF.items('dmenu') | |||
args_dict = dict(args) | |||
dmenu_args = [] | |||
if "dmenu_command" in args_dict: | |||
command = shlex.split(args_dict["dmenu_command"]) | |||
dmenu_command = command[0] | |||
dmenu_args = command[1:] | |||
del args_dict["dmenu_command"] | |||
if "p" in args_dict and prompt == "Networks": | |||
prompt = args_dict["p"] | |||
del args_dict["p"] | |||
elif "p" in args_dict: | |||
del args_dict["p"] | |||
if "rofi" in dmenu_command: | |||
lines = "-i -dmenu -lines" | |||
# rofi doesn't support 0 length line, it requires at least -lines=1 | |||
# see https://github.com/DaveDavenport/rofi/issues/252 | |||
num_lines = num_lines or 1 | |||
else: | |||
lines = "-i -l" | |||
if "l" in args_dict: | |||
# rofi doesn't support 0 length line, it requires at least -lines=1 | |||
# see https://github.com/DaveDavenport/rofi/issues/252 | |||
if "rofi" in dmenu_command: | |||
args_dict['l'] = min(num_lines, int(args_dict['l'])) or 1 | |||
lines = "{} {}".format(lines, args_dict['l']) | |||
del args_dict['l'] | |||
else: | |||
lines = "{} {}".format(lines, num_lines) | |||
if "pinentry" in args_dict: | |||
del args_dict["pinentry"] | |||
if "compact" in args_dict: | |||
del args_dict["compact"] | |||
if "wifi_chars" in args_dict: | |||
del args_dict["wifi_chars"] | |||
rofi_highlight = CONF.getboolean('dmenu', 'rofi_highlight', fallback=False) | |||
if CONF.has_option('dmenu', 'rofi_highlight'): | |||
del args_dict["rofi_highlight"] | |||
if rofi_highlight is True and "rofi" in dmenu_command: | |||
if active_lines: | |||
dmenu_args.extend(["-a", ",".join([str(num) | |||
for num in active_lines])]) | |||
if prompt == "Passphrase": | |||
if CONF.has_section('dmenu_passphrase'): | |||
args = CONF.items('dmenu_passphrase') | |||
args_dict.update(args) | |||
rofi_obscure = CONF.getboolean('dmenu_passphrase', 'rofi_obscure', fallback=True) | |||
if CONF.has_option('dmenu_passphrase', 'rofi_obscure'): | |||
del args_dict["rofi_obscure"] | |||
if rofi_obscure is True and "rofi" in dmenu_command: | |||
dmenu_args.extend(["-password"]) | |||
dmenu_password = CONF.getboolean('dmenu_passphrase', 'dmenu_password', fallback=False) | |||
if CONF.has_option('dmenu_passphrase', 'dmenu_password'): | |||
del args_dict["dmenu_password"] | |||
if dmenu_password is True: | |||
dmenu_args.extend(["-P"]) | |||
extras = (["-" + str(k), str(v)] for (k, v) in args_dict.items()) | |||
res = [dmenu_command, "-p", str(prompt)] | |||
res.extend(dmenu_args) | |||
res += list(itertools.chain.from_iterable(extras)) | |||
res[1:1] = lines.split() | |||
res = list(filter(None, res)) # Remove empty list elements | |||
res.extend(sys.argv[1:]) | |||
return res | |||
def choose_adapter(client): | |||
"""If there is more than one wifi adapter installed, ask which one to use | |||
""" | |||
devices = client.get_devices() | |||
devices = [i for i in devices if i.get_device_type() == NM.DeviceType.WIFI] | |||
if not devices: # pylint: disable=no-else-return | |||
return None | |||
elif len(devices) == 1: | |||
return devices[0] | |||
device_names = "\n".join([d.get_iface() for d in devices]).encode(ENC) | |||
sel = Popen(dmenu_cmd(len(devices), "CHOOSE ADAPTER:"), | |||
stdin=PIPE, | |||
stdout=PIPE, | |||
env=ENV).communicate(input=device_names)[0].decode(ENC) | |||
if not sel.strip(): | |||
sys.exit() | |||
devices = [i for i in devices if i.get_iface() == sel.strip()] | |||
assert len(devices) == 1 | |||
return devices[0] | |||
def is_modemmanager_installed(): | |||
"""Check if ModemManager is installed""" | |||
with open(os.devnull) as devnull: | |||
try: | |||
Popen(["ModemManager"], stdout=devnull, stderr=devnull).communicate() | |||
except OSError: | |||
return False | |||
return True | |||
def bluetooth_get_enabled(): | |||
"""Check if bluetooth is enabled via rfkill. | |||
Returns None if no bluetooth device was found. | |||
""" | |||
# See https://www.kernel.org/doc/Documentation/ABI/stable/sysfs-class-rfkill | |||
for path in pathlib.Path('/sys/class/rfkill/').glob('rfkill*'): | |||
if (path / 'type').read_text().strip() == 'bluetooth': | |||
return (path / 'soft').read_text().strip() == '0' | |||
return None | |||
def create_other_actions(client): | |||
"""Return list of other actions that can be taken | |||
""" | |||
networking_enabled = client.networking_get_enabled() | |||
networking_action = "Disable" if networking_enabled else "Enable" | |||
wifi_enabled = client.wireless_get_enabled() | |||
wifi_action = "Disable" if wifi_enabled else "Enable" | |||
bluetooth_enabled = bluetooth_get_enabled() | |||
bluetooth_action = "Disable" if bluetooth_enabled else "Enable" | |||
actions = [Action("{} Wifi".format(wifi_action), toggle_wifi, | |||
not wifi_enabled), | |||
Action("{} Networking".format(networking_action), | |||
toggle_networking, not networking_enabled)] | |||
if bluetooth_enabled is not None: | |||
actions.append(Action("{} Bluetooth".format(bluetooth_action), | |||
toggle_bluetooth, not bluetooth_enabled)) | |||
actions += [Action("Launch Connection Manager", launch_connection_editor), | |||
Action("Delete a Connection", delete_connection)] | |||
if wifi_enabled: | |||
actions.append(Action("Rescan Wifi Networks", rescan_wifi)) | |||
return actions | |||
def rescan_wifi(): | |||
""" | |||
Rescan Wifi Access Points | |||
""" | |||
for dev in CLIENT.get_devices(): | |||
if gi.repository.NM.DeviceWifi == type(dev): | |||
try: | |||
dev.request_scan_async(None, rescan_cb, None) | |||
LOOP.run() | |||
except gi.repository.GLib.Error as err: | |||
# Too frequent rescan error | |||
notify("Wifi rescan failed", urgency="critical") | |||
if not err.code == 6: # pylint: disable=no-member | |||
raise err | |||
def rescan_cb(dev, res, data): | |||
"""Callback for rescan_wifi. Just for notifications | |||
""" | |||
if dev.request_scan_finish(res) is True: | |||
notify("Wifi scan complete") | |||
else: | |||
notify("Wifi scan failed", urgency="critical") | |||
LOOP.quit() | |||
def ssid_to_utf8(nm_ap): | |||
""" Convert binary ssid to utf-8 """ | |||
ssid = nm_ap.get_ssid() | |||
if not ssid: | |||
return "" | |||
ret = NM.utils_ssid_to_utf8(ssid.get_data()) | |||
return ret | |||
def prompt_saved(saved_cons): | |||
"""Prompt for a saved connection.""" | |||
actions = create_saved_actions(saved_cons) | |||
sel = get_selection(actions) | |||
sel() | |||
def ap_security(nm_ap): | |||
"""Parse the security flags to return a string with 'WPA2', etc. """ | |||
flags = nm_ap.get_flags() | |||
wpa_flags = nm_ap.get_wpa_flags() | |||
rsn_flags = nm_ap.get_rsn_flags() | |||
sec_str = "" | |||
if ((flags & getattr(NM, '80211ApFlags').PRIVACY) and | |||
(wpa_flags == 0) and (rsn_flags == 0)): | |||
sec_str += " WEP" | |||
if wpa_flags != 0: | |||
sec_str += " WPA1" | |||
if rsn_flags != 0: | |||
sec_str += " WPA2" | |||
if ((wpa_flags & getattr(NM, '80211ApSecurityFlags').KEY_MGMT_802_1X) or | |||
(rsn_flags & getattr(NM, '80211ApSecurityFlags').KEY_MGMT_802_1X)): | |||
sec_str += " 802.1X" | |||
# If there is no security use "--" | |||
if sec_str == "": | |||
sec_str = "--" | |||
return sec_str.lstrip() | |||
class Action(): # pylint: disable=too-few-public-methods | |||
"""Helper class to execute functions from a string variable""" | |||
def __init__(self, | |||
name, | |||
func, | |||
args=None, | |||
active=False): | |||
self.name = name | |||
self.func = func | |||
self.is_active = active | |||
if args is None: | |||
self.args = None | |||
elif isinstance(args, list): | |||
self.args = args | |||
else: | |||
self.args = [args] | |||
def __str__(self): | |||
return self.name | |||
def __call__(self): | |||
if self.args is None: | |||
self.func() | |||
else: | |||
self.func(*self.args) | |||
def process_ap(nm_ap, is_active, adapter): | |||
"""Activate/Deactivate a connection and get password if required""" | |||
if is_active: | |||
CLIENT.deactivate_connection_async(nm_ap, None, deactivate_cb, nm_ap) | |||
else: | |||
conns_cur = [i for i in CONNS if | |||
i.get_setting_wireless() is not None and | |||
i.get_setting_wireless().get_mac_address() == | |||
adapter.get_permanent_hw_address()] | |||
con = nm_ap.filter_connections(conns_cur) | |||
if len(con) > 1: | |||
raise ValueError("There are multiple connections possible") | |||
if len(con) == 1: | |||
CLIENT.activate_connection_async(con[0], adapter, nm_ap.get_path(), | |||
None, activate_cb, nm_ap) | |||
else: | |||
if ap_security(nm_ap) != "--": | |||
password = get_passphrase() | |||
else: | |||
password = "" | |||
set_new_connection(nm_ap, password, adapter) | |||
LOOP.run() | |||
def activate_cb(dev, res, data): | |||
"""Notification if activate connection completed successfully | |||
""" | |||
try: | |||
conn = dev.activate_connection_finish(res) | |||
except GLib.Error: | |||
conn = None | |||
if conn is not None: | |||
notify("Activated {}".format(conn.get_id())) | |||
else: | |||
notify("Problem activating {}".format(data.get_id()), | |||
urgency="critical") | |||
LOOP.quit() | |||
def deactivate_cb(dev, res, data): | |||
"""Notification if deactivate connection completed successfully | |||
""" | |||
if dev.deactivate_connection_finish(res) is True: | |||
notify("Deactivated {}".format(data.get_id())) | |||
else: | |||
notify("Problem deactivating {}".format(data.get_id()), | |||
urgency="critical") | |||
LOOP.quit() | |||
def process_vpngsm(con, activate): | |||
"""Activate/deactive VPN or GSM connections""" | |||
if activate: | |||
CLIENT.activate_connection_async(con, None, None, | |||
None, activate_cb, con) | |||
else: | |||
CLIENT.deactivate_connection_async(con, None, deactivate_cb, con) | |||
LOOP.run() | |||
def create_ap_actions(aps, active_ap, active_connection, adapter): # pylint: disable=too-many-locals | |||
"""For each AP in a list, create the string and its attached function | |||
(activate/deactivate) | |||
""" | |||
active_ap_bssid = active_ap.get_bssid() if active_ap is not None else "" | |||
names = [ssid_to_utf8(ap) for ap in aps] | |||
max_len_name = max([len(name) for name in names]) if names else 0 | |||
secs = [ap_security(ap) for ap in aps] | |||
max_len_sec = max([len(sec) for sec in secs]) if secs else 0 | |||
ap_actions = [] | |||
for nm_ap, name, sec in zip(aps, names, secs): | |||
bars = NM.utils_wifi_strength_bars(nm_ap.get_strength()) | |||
wifi_chars = CONF.get("dmenu", "wifi_chars", fallback=False) | |||
if wifi_chars: | |||
bars = "".join([wifi_chars[i] for i, j in enumerate(bars) if j == '*']) | |||
is_active = nm_ap.get_bssid() == active_ap_bssid | |||
compact = CONF.getboolean("dmenu", "compact", fallback=False) | |||
if compact: | |||
action_name = u"{} {} {}".format(name, sec, bars) | |||
else: | |||
action_name = u"{:<{}s} {:<{}s} {}".format(name, max_len_name, sec, | |||
max_len_sec, bars) | |||
if is_active: | |||
ap_actions.append(Action(action_name, process_ap, | |||
[active_connection, True, adapter], | |||
active=True)) | |||
else: | |||
ap_actions.append(Action(action_name, process_ap, | |||
[nm_ap, False, adapter])) | |||
return ap_actions | |||
def create_vpn_actions(vpns, active): | |||
"""Create the list of strings to display with associated function | |||
(activate/deactivate) for VPN connections. | |||
""" | |||
active_vpns = [i for i in active if i.get_vpn()] | |||
return _create_vpngsm_actions(vpns, active_vpns, "VPN") | |||
def create_wireguard_actions(wgs, active): | |||
"""Create the list of strings to display with associated function | |||
(activate/deactivate) for Wireguard connections. | |||
""" | |||
active_wgs = [i for i in active if i.get_connection_type() == "wireguard"] | |||
return _create_vpngsm_actions(wgs, active_wgs, "Wireguard") | |||
def create_eth_actions(eths, active): | |||
"""Create the list of strings to display with associated function | |||
(activate/deactivate) for Ethernet connections. | |||
""" | |||
active_eths = [i for i in active if 'ethernet' in i.get_connection_type()] | |||
return _create_vpngsm_actions(eths, active_eths, "Eth") | |||
def create_gsm_actions(gsms, active): | |||
"""Create the list of strings to display with associated function | |||
(activate/deactivate) GSM connections.""" | |||
active_gsms = [i for i in active if | |||
i.get_connection() is not None and | |||
i.get_connection().is_type(NM.SETTING_GSM_SETTING_NAME)] | |||
return _create_vpngsm_actions(gsms, active_gsms, "GSM") | |||
def create_blue_actions(blues, active): | |||
"""Create the list of strings to display with associated function | |||
(activate/deactivate) Bluetooth connections.""" | |||
active_blues = [i for i in active if | |||
i.get_connection() is not None and | |||
i.get_connection().is_type(NM.SETTING_BLUETOOTH_SETTING_NAME)] | |||
return _create_vpngsm_actions(blues, active_blues, "Bluetooth") | |||
def create_saved_actions(saved): | |||
"""Create the list of strings to display with associated function | |||
(activate/deactivate) for VPN connections. | |||
""" | |||
return _create_vpngsm_actions(saved, [], "SAVED") | |||
def _create_vpngsm_actions(cons, active_cons, label): | |||
active_con_ids = [a.get_id() for a in active_cons] | |||
actions = [] | |||
for con in cons: | |||
is_active = con.get_id() in active_con_ids | |||
action_name = u"{}:{}".format(con.get_id(), label) | |||
if is_active: | |||
active_connection = [a for a in active_cons | |||
if a.get_id() == con.get_id()] | |||
if len(active_connection) != 1: | |||
raise ValueError(u"Multiple active connections match" | |||
" the connection: {}".format(con.get_id())) | |||
active_connection = active_connection[0] | |||
actions.append(Action(action_name, process_vpngsm, | |||
[active_connection, False], active=True)) | |||
else: | |||
actions.append(Action(action_name, process_vpngsm, | |||
[con, True])) | |||
return actions | |||
def create_wwan_actions(client): | |||
"""Create WWWAN actions | |||
""" | |||
wwan_enabled = client.wwan_get_enabled() | |||
wwan_action = "Disable" if wwan_enabled else "Enable" | |||
return [Action("{} WWAN".format(wwan_action), toggle_wwan, not wwan_enabled)] | |||
def combine_actions(eths, aps, vpns, wgs, gsms, blues, wwan, others, saved): | |||
"""Combine all given actions into a list of actions. | |||
Args: args - eths: list of Actions | |||
aps: list of Actions | |||
vpns: list of Actions | |||
gsms: list of Actions | |||
blues: list of Actions | |||
wwan: list of Actions | |||
others: list of Actions | |||
""" | |||
compact = CONF.getboolean("dmenu", "compact", fallback=False) | |||
empty_action = [Action('', None)] if not compact else [] | |||
all_actions = [] | |||
all_actions += eths + empty_action if eths else [] | |||
all_actions += aps + empty_action if aps else [] | |||
all_actions += vpns + empty_action if vpns else [] | |||
all_actions += wgs + empty_action if wgs else [] | |||
all_actions += gsms + empty_action if (gsms and wwan) else [] | |||
all_actions += blues + empty_action if blues else [] | |||
all_actions += wwan + empty_action if wwan else [] | |||
all_actions += others + empty_action if others else [] | |||
all_actions += saved + empty_action if saved else [] | |||
return all_actions | |||
def get_selection(all_actions): | |||
"""Spawn dmenu for selection and execute the associated action.""" | |||
rofi_highlight = CONF.getboolean('dmenu', 'rofi_highlight', fallback=False) | |||
inp = [] | |||
if rofi_highlight is True: | |||
inp = [str(action) for action in all_actions] | |||
else: | |||
inp = [('== ' if action.is_active else ' ') + str(action) | |||
for action in all_actions] | |||
active_lines = [index for index, action in enumerate(all_actions) | |||
if action.is_active] | |||
inp_bytes = "\n".join(inp).encode(ENC) | |||
command = dmenu_cmd(len(inp), active_lines=active_lines) | |||
sel = Popen(command, stdin=PIPE, stdout=PIPE, | |||
env=ENV).communicate(input=inp_bytes)[0].decode(ENC) | |||
if not sel.rstrip(): | |||
sys.exit() | |||
if rofi_highlight is False: | |||
action = [i for i in all_actions | |||
if ((str(i).strip() == str(sel.strip()) | |||
and not i.is_active) or | |||
('== ' + str(i) == str(sel.rstrip('\n')) | |||
and i.is_active))] | |||
else: | |||
action = [i for i in all_actions if str(i).strip() == sel.strip()] | |||
assert len(action) == 1, \ | |||
u"Selection was ambiguous: '{}'".format(str(sel.strip())) | |||
return action[0] | |||
def toggle_networking(enable): | |||
"""Enable/disable networking | |||
Args: enable - boolean | |||
""" | |||
toggle = GLib.Variant.new_tuple(GLib.Variant.new_boolean(enable)) | |||
try: | |||
CLIENT.dbus_call(NM.DBUS_PATH, NM.DBUS_INTERFACE, "Enable", toggle, | |||
None, -1, None, None, None) | |||
except AttributeError: | |||
# Workaround for older versions of python-gobject | |||
CLIENT.networking_set_enabled(enable) | |||
notify("Networking {}".format("enabled" if enable is True else "disabled")) | |||
def toggle_wifi(enable): | |||
"""Enable/disable Wifi | |||
Args: enable - boolean | |||
""" | |||
toggle = GLib.Variant.new_boolean(enable) | |||
try: | |||
CLIENT.dbus_set_property(NM.DBUS_PATH, NM.DBUS_INTERFACE, "WirelessEnabled", toggle, | |||
-1, None, None, None) | |||
except AttributeError: | |||
# Workaround for older versions of python-gobject | |||
CLIENT.wireless_set_enabled(enable) | |||
notify("Wifi {}".format("enabled" if enable is True else "disabled")) | |||
def toggle_wwan(enable): | |||
"""Enable/disable WWAN | |||
Args: enable - boolean | |||
""" | |||
toggle = GLib.Variant.new_boolean(enable) | |||
try: | |||
CLIENT.dbus_set_property(NM.DBUS_PATH, NM.DBUS_INTERFACE, "WwanEnabled", toggle, | |||
-1, None, None, None) | |||
except AttributeError: | |||
# Workaround for older versions of python-gobject | |||
CLIENT.wwan_set_enabled(enable) | |||
notify("Wwan {}".format("enabled" if enable is True else "disabled")) | |||
def toggle_bluetooth(enable): | |||
"""Enable/disable Bluetooth | |||
Args: enable - boolean | |||
References: | |||
https://github.com/blueman-project/blueman/blob/master/blueman/plugins/mechanism/RfKill.py | |||
https://www.kernel.org/doc/html/latest/driver-api/rfkill.html | |||
https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/tree/include/uapi/linux/rfkill.h?h=v5.8.9 | |||
""" | |||
type_bluetooth = 2 | |||
op_change_all = 3 | |||
idx = 0 | |||
soft_state = 0 if enable else 1 | |||
hard_state = 0 | |||
data = struct.pack("IBBBB", idx, type_bluetooth, op_change_all, | |||
soft_state, hard_state) | |||
try: | |||
with open('/dev/rfkill', 'r+b', buffering=0) as rff: | |||
rff.write(data) | |||
except PermissionError: | |||
notify("Lacking permission to write to /dev/rfkill.", | |||
"Maybe you need to add your user to the 'rfkill' group?", | |||
urgency="critical") | |||
else: | |||
notify("Bluetooth {}".format("enabled" if enable else "disabled")) | |||
def launch_connection_editor(): | |||
"""Launch nmtui or the gui nm-connection-editor | |||
""" | |||
terminal = CONF.get("editor", "terminal", fallback="xterm") | |||
gui_if_available = CONF.getboolean("editor", "gui_if_available", fallback=True) | |||
if gui_if_available is True: | |||
try: | |||
Popen(["gnome-control-center", "network"]).communicate() | |||
except OSError: | |||
try: | |||
Popen(["nm-connection-editor"]).communicate() | |||
except OSError: | |||
Popen([terminal, "-e", "nmtui"]).communicate() | |||
else: | |||
Popen([terminal, "-e", "nmtui"]).communicate() | |||
def get_passphrase(): | |||
"""Get a password | |||
Returns: string | |||
""" | |||
pinentry = CONF.get("dmenu", "pinentry", fallback=None) | |||
if pinentry: | |||
pin = "" | |||
out = Popen(pinentry, | |||
stdout=PIPE, | |||
stdin=PIPE).communicate(input=b'setdesc Get network password\ngetpin\n')[0] | |||
if out: | |||
res = out.decode(ENC).split("\n")[2] | |||
if res.startswith("D "): | |||
pin = res.split("D ")[1] | |||
return pin | |||
return Popen(dmenu_cmd(0, "Passphrase"), | |||
stdin=PIPE, stdout=PIPE).communicate()[0].decode(ENC) | |||
def delete_connection(): | |||
"""Display list of NM connections and delete the selected one | |||
""" | |||
conn_acts = [Action(i.get_id(), i.delete_async, args=[None, delete_cb, None]) for i in CONNS] | |||
conn_names = "\n".join([str(i) for i in conn_acts]).encode(ENC) | |||
sel = Popen(dmenu_cmd(len(conn_acts), "CHOOSE CONNECTION TO DELETE:"), | |||
stdin=PIPE, | |||
stdout=PIPE, | |||
env=ENV).communicate(input=conn_names)[0].decode(ENC) | |||
if not sel.strip(): | |||
sys.exit() | |||
action = [i for i in conn_acts if str(i) == sel.rstrip("\n")] | |||
assert len(action) == 1, u"Selection was ambiguous: {}".format(str(sel)) | |||
action[0]() | |||
LOOP.run() | |||
def delete_cb(dev, res, data): | |||
"""Notification if delete completed successfully | |||
""" | |||
if dev.delete_finish(res) is True: | |||
notify("Deleted {}".format(dev.get_id())) | |||
else: | |||
notify("Problem deleting {}".format(dev.get_id()), urgency="critical") | |||
LOOP.quit() | |||
def set_new_connection(nm_ap, nm_pw, adapter): | |||
"""Setup a new NetworkManager connection | |||
Args: ap - NM.AccessPoint | |||
pw - string | |||
""" | |||
nm_pw = str(nm_pw).strip() | |||
profile = create_wifi_profile(nm_ap, nm_pw, adapter) | |||
CLIENT.add_and_activate_connection_async(profile, adapter, nm_ap.get_path(), | |||
None, verify_conn, profile) | |||
LOOP.run() | |||
def create_wifi_profile(nm_ap, password, adapter): | |||
# pylint: disable=C0301 | |||
# From https://cgit.freedesktop.org/NetworkManager/NetworkManager/tree/examples/python/gi/add_connection.py | |||
# and https://cgit.freedesktop.org/NetworkManager/NetworkManager/tree/examples/python/dbus/add-wifi-psk-connection.py | |||
# pylint: enable=C0301 | |||
"""Create the NM profile given the AP and passphrase""" | |||
ap_sec = ap_security(nm_ap) | |||
profile = NM.SimpleConnection.new() | |||
s_con = NM.SettingConnection.new() | |||
s_con.set_property(NM.SETTING_CONNECTION_ID, ssid_to_utf8(nm_ap)) | |||
s_con.set_property(NM.SETTING_CONNECTION_UUID, str(uuid.uuid4())) | |||
s_con.set_property(NM.SETTING_CONNECTION_TYPE, "802-11-wireless") | |||
profile.add_setting(s_con) | |||
s_wifi = NM.SettingWireless.new() | |||
s_wifi.set_property(NM.SETTING_WIRELESS_SSID, nm_ap.get_ssid()) | |||
s_wifi.set_property(NM.SETTING_WIRELESS_MODE, 'infrastructure') | |||
s_wifi.set_property(NM.SETTING_WIRELESS_MAC_ADDRESS, adapter.get_permanent_hw_address()) | |||
profile.add_setting(s_wifi) | |||
s_ip4 = NM.SettingIP4Config.new() | |||
s_ip4.set_property(NM.SETTING_IP_CONFIG_METHOD, "auto") | |||
profile.add_setting(s_ip4) | |||
s_ip6 = NM.SettingIP6Config.new() | |||
s_ip6.set_property(NM.SETTING_IP_CONFIG_METHOD, "auto") | |||
profile.add_setting(s_ip6) | |||
if ap_sec != "--": | |||
s_wifi_sec = NM.SettingWirelessSecurity.new() | |||
if "WPA" in ap_sec: | |||
s_wifi_sec.set_property(NM.SETTING_WIRELESS_SECURITY_KEY_MGMT, | |||
"wpa-psk") | |||
s_wifi_sec.set_property(NM.SETTING_WIRELESS_SECURITY_AUTH_ALG, | |||
"open") | |||
s_wifi_sec.set_property(NM.SETTING_WIRELESS_SECURITY_PSK, password) | |||
elif "WEP" in ap_sec: | |||
s_wifi_sec.set_property(NM.SETTING_WIRELESS_SECURITY_KEY_MGMT, | |||
"None") | |||
s_wifi_sec.set_property(NM.SETTING_WIRELESS_SECURITY_WEP_KEY_TYPE, | |||
NM.WepKeyType.PASSPHRASE) | |||
s_wifi_sec.set_wep_key(0, password) | |||
profile.add_setting(s_wifi_sec) | |||
return profile | |||
def verify_conn(client, result, data): | |||
"""Callback function for add_and_activate_connection_async | |||
Check if connection completes successfully. Delete the connection if there | |||
is an error. | |||
""" | |||
try: | |||
act_conn = client.add_and_activate_connection_finish(result) | |||
conn = act_conn.get_connection() | |||
if not all([conn.verify(), | |||
conn.verify_secrets(), | |||
data.verify(), | |||
data.verify_secrets()]): | |||
raise GLib.Error | |||
notify("Added {}".format(conn.get_id())) | |||
except GLib.Error: # pylint: disable=catching-non-exception | |||
try: | |||
notify("Connection to {} failed".format(conn.get_id()), | |||
urgency="critical") | |||
conn.delete_async(None, None, None) | |||
except UnboundLocalError: | |||
pass | |||
finally: | |||
LOOP.quit() | |||
def create_ap_list(adapter, active_connections): | |||
"""Generate list of access points. Remove duplicate APs , keeping strongest | |||
ones and the active AP | |||
Args: adapter | |||
active_connections - list of all active connections | |||
Returns: aps - list of access points | |||
active_ap - active AP | |||
active_ap_con - active Connection | |||
adapter | |||
""" | |||
aps = [] | |||
ap_names = [] | |||
active_ap = adapter.get_active_access_point() | |||
aps_all = sorted(adapter.get_access_points(), | |||
key=lambda a: a.get_strength(), reverse=True) | |||
conns_cur = [i for i in CONNS if | |||
i.get_setting_wireless() is not None and | |||
i.get_setting_wireless().get_mac_address() == | |||
adapter.get_permanent_hw_address()] | |||
try: | |||
ap_conns = active_ap.filter_connections(conns_cur) | |||
active_ap_name = ssid_to_utf8(active_ap) | |||
active_ap_con = [active_conn for active_conn in active_connections | |||
if active_conn.get_connection() in ap_conns] | |||
except AttributeError: | |||
active_ap_name = None | |||
active_ap_con = [] | |||
if len(active_ap_con) > 1: | |||
raise ValueError("Multiple connection profiles match" | |||
" the wireless AP") | |||
active_ap_con = active_ap_con[0] if active_ap_con else None | |||
for nm_ap in aps_all: | |||
ap_name = ssid_to_utf8(nm_ap) | |||
if nm_ap != active_ap and ap_name == active_ap_name: | |||
# Skip adding AP if it's not active but same name as active AP | |||
continue | |||
if ap_name not in ap_names: | |||
ap_names.append(ap_name) | |||
aps.append(nm_ap) | |||
return aps, active_ap, active_ap_con, adapter | |||
def notify(message, details=None, urgency="low"): | |||
"""Use notify-send if available for notifications | |||
""" | |||
args = ["-u", urgency, message] | |||
if details is not None: | |||
args.append(details) | |||
try: | |||
Popen(["notify-send"] + args, | |||
stdout=PIPE, stderr=PIPE).communicate() | |||
except FileNotFoundError: | |||
pass | |||
def run(): | |||
"""Main script entrypoint""" | |||
active = CLIENT.get_active_connections() | |||
adapter = choose_adapter(CLIENT) | |||
if adapter: | |||
ap_actions = create_ap_actions(*create_ap_list(adapter, active)) | |||
else: | |||
ap_actions = [] | |||
vpns = [i for i in CONNS if i.is_type(NM.SETTING_VPN_SETTING_NAME)] | |||
try: | |||
wgs = [i for i in CONNS if i.is_type(NM.SETTING_WIREGUARD_SETTING_NAME)] | |||
except AttributeError: | |||
# Workaround for older versions of python-gobject with no wireguard support | |||
wgs = [] | |||
eths = [i for i in CONNS if i.is_type(NM.SETTING_WIRED_SETTING_NAME)] | |||
blues = [i for i in CONNS if i.is_type(NM.SETTING_BLUETOOTH_SETTING_NAME)] | |||
vpn_actions = create_vpn_actions(vpns, active) | |||
wg_actions = create_wireguard_actions(wgs, active) | |||
eth_actions = create_eth_actions(eths, active) | |||
blue_actions = create_blue_actions(blues, active) | |||
other_actions = create_other_actions(CLIENT) | |||
wwan_installed = is_modemmanager_installed() | |||
if wwan_installed: | |||
gsms = [i for i in CONNS if i.is_type(NM.SETTING_GSM_SETTING_NAME)] | |||
gsm_actions = create_gsm_actions(gsms, active) | |||
wwan_actions = create_wwan_actions(CLIENT) | |||
else: | |||
gsm_actions = [] | |||
wwan_actions = [] | |||
list_saved = CONF.getboolean('dmenu', 'list_saved', fallback=False) | |||
saved_cons = [i for i in CONNS if i not in vpns + wgs + eths + blues] | |||
if list_saved: | |||
saved_actions = create_saved_actions(saved_cons) | |||
else: | |||
saved_actions = [Action("Saved connections", prompt_saved, [saved_cons])] | |||
actions = combine_actions(eth_actions, ap_actions, vpn_actions, wg_actions, | |||
gsm_actions, blue_actions, wwan_actions, | |||
other_actions, saved_actions) | |||
sel = get_selection(actions) | |||
sel() | |||
if __name__ == '__main__': | |||
run() | |||
# vim: set et ts=4 sw=4 : |
@ -1,17 +0,0 @@ | |||
#!/bin/sh | |||
icon= | |||
ibg=^b#705B86^ | |||
ifg=^c#fabd2f^ | |||
tfg=^c#292541^ | |||
tbg=^b#cdabb8^ | |||
reset=^d^ | |||
if ! updates=$(checkupdates 2> /dev/null | wc -l ); then | |||
updates=0t | |||
fi | |||
echo $updates > ~/.cache/pacman_updates | |||
echo "$ibg$ifg $icon $tfg$tbg $(cat ~/.cache/pacman_updates) $reset" | |||
@ -1,49 +0,0 @@ | |||
#! /bin/sh | |||
bat=$(cat /sys/class/power_supply/BAT0/capacity) | |||
status=$(cat /sys/class/power_supply/BAT0/status) | |||
ramp10= | |||
ramp20= | |||
ramp30= | |||
ramp40= | |||
ramp50= | |||
ramp60= | |||
ramp70= | |||
ramp80= | |||
ramp90= | |||
ramp100= | |||
tbg=^b#cdabb8^ | |||
tfg=^c#292541^ | |||
reset=^d^ | |||
if [[ $status == "Discharging" ]]; then | |||
ibg=^b#705b86^ | |||
elif [[ $status == "Full" ]]; then | |||
ibg=^b#50FA7B^ | |||
else | |||
ibg=^b#50FA7B^ | |||
fi | |||
if [[ $bat -lt 10 ]]; then | |||
echo "$ibg$tfg $ramp10 $tbg $bat% $reset" | |||
elif [[ $bat -lt 20 ]]; then | |||
echo "$ibg$tfg $ramp20 $tbg $bat% $reset" | |||
elif [[ $bat -lt "30" ]]; then | |||
echo "$ibg$tfg $ramp30 $tbg $bat% $reset" | |||
elif [[ $bat -lt "40" ]]; then | |||
echo "$ibg$tfg $ramp40 $tbg $bat% $reset" | |||
elif [[ $bat -lt "50" ]]; then | |||
echo "$ibg$tfg $ramp50 $tbg $bat% $reset" | |||
elif [[ $bat -lt "60" ]]; then | |||
echo "$ibg$tfg $ramp60 $tbg $bat% $reset" | |||
elif [[ $bat -lt "70" ]]; then | |||
echo "$ibg$tfg $ramp70 $tbg $bat% $reset" | |||
elif [[ $bat -lt "80" ]]; then | |||
echo "$ibg$tfg $ramp80 $tbg $bat% $reset" | |||
elif [[ $bat -lt "90" ]]; then | |||
echo "$ibg$tfg $ramp90 $tbg $bat% $reset" | |||
elif [[ $bat -le "100" ]]; then | |||
echo "$ibg$tfg $ramp100 $tbg $bat% $reset" | |||
fi |
@ -1,14 +0,0 @@ | |||
#! /bin/sh | |||
icon= | |||
bfg=^b#292541^ | |||
tfg=^c#cc241d^ | |||
reset=^d^ | |||
cmd=$(cat /sys/class/leds/input3::capslock/brightness) | |||
if [[ "$cmd" == "1" ]]; then | |||
echo "$tfg$tbg $icon $reset" | |||
else | |||
echo "" | |||
fi |
@ -1,12 +0,0 @@ | |||
#! /bin/sh | |||
#localización | |||
#initloc=$(curl -s ifconfig.co/city) | |||
#if [ "$initloc" == "La Paz" ]; then | |||
# loc="LAP" | |||
#else | |||
# loc=init-loc | |||
#fi | |||
# obtener clima | |||
#curl -s "wttr.in/$loc?format=%t" | |||
curl -s "wttr.in/?format=%t" |
@ -1,27 +0,0 @@ | |||
#! /bin/sh | |||
estado=$(curl -s "wttr.in/?format=%C" ) | |||
case $estado in | |||
Unknown) icon="";; | |||
Cloudy) icon="";; | |||
Fog) icon="";; | |||
"Heavy rain") icon="";; | |||
"Heavy showers") icon="";; | |||
"Heavy snow") icon="";; | |||
"Heavy snow showers") icon="";; | |||
"Light rain") icon="";; | |||
"Light showers") icon="";; | |||
"Light sleet") icon="";; | |||
"Light sleet showers") icon="";; | |||
"Light snow") icon="";; | |||
"Light snow showers") icon="";; | |||
"Partly cloudy") icon="";; | |||
"Sunny") icon="";; | |||
"Clear") icon="";; | |||
"Thundery heavy rain") icon="";; | |||
"Thundery showers") icon="";; | |||
"Thundery snow showers") icon="";; | |||
"Very cloudy") icon="";; | |||
esac | |||
echo $icon |
@ -1,9 +0,0 @@ | |||
#! /bin/sh | |||
icon= | |||
cmd=$(date +"%b %e, %R") | |||
#echo "$ifg $icon $tfg$cmd" | |||
echo "$icon $cmd" |
@ -1,11 +0,0 @@ | |||
#! /bin/sh | |||
icon= | |||
tfg=^c#292541^ | |||
ibg=^b#705b86^ | |||
tbg=^b#cdabb8^ | |||
reset=^d^ | |||
cmd=$(date +"%b %e, %R") | |||
echo "$tfg$ibg$ifg $icon $tbg $cmd $reset" |
@ -1,10 +0,0 @@ | |||
#! /bin/sh | |||
while true; do | |||
read -t 1 -n 1 key | |||
if [[ $key == o ]]; then | |||
notify-send "presionaste la o" | |||
fi | |||
done | |||
@ -0,0 +1,19 @@ | |||
#!/bin/sh | |||
ETH_IT=enp2s0 | |||
WLAN_IT=wlp3s0 | |||
is_eth_used=$(cat /sys/class/net/${ETH_IT}/carrier) | |||
is_wlan_used=$(cat /sys/class/net/${WLAN_IT}/carrier) | |||
echo -n "^c#88c0d0^" | |||
if [ "$is_eth_used" -eq 1 ]; then # wired network is carrying | |||
icon="^d^ " #uF6FF | |||
elif [ "$is_wlan_used" -eq 1 ]; then # wireless network is carrying | |||
icon="直 ^d^ $(nmcli -t -f active,ssid dev wifi | egrep '^yes' | cut -d\' -f2 | cut -d ':' -f2)" #uF1EB | |||
else | |||
icon="^d^" #uf128 # no network | |||
fi | |||
echo -n $icon |
@ -1,9 +0,0 @@ | |||
#! /bin/sh | |||
if [[ $(cat /sys/class/net/wlp*/operstate) == "up" ]]; then | |||
echo " 直 " | |||
else | |||
echo " 睊 " | |||
fi | |||
@ -1,14 +0,0 @@ | |||
#! /bin/sh | |||
if [[ $(cat /sys/class/net/wlp*/operstate) == "up" ]]; then | |||
echo " WiFi " | |||
else | |||
ping -c 1 google.com | |||
if [[ $? == 0 ]]; then | |||
echo "Eth" | |||
else | |||
" " | |||
fi | |||
fi | |||
@ -1,3 +0,0 @@ | |||
#! /bin/sh | |||
echo "" |
@ -1,14 +0,0 @@ | |||
#! /bin/sh | |||
icon=" " | |||
#status=$(~/.local/bin/status-bar/spotify_status.py -f '{artist} - {song}') | |||
status=$(playerctl metadata --format '{{artist}} - {{title}}' | cut -c 1-50) | |||
runstate=$(playerctl status) | |||
if [[ $runstate == "Playing" ]]; then | |||
echo " $icon $status" | |||
else | |||
echo "" | |||
fi | |||
@ -1,17 +0,0 @@ | |||
#! /bin/sh | |||
icon="" | |||
ibg=^b#875B82^ | |||
ifg=^c#1DB954^ | |||
tfg=^c#292541^ | |||
tbg=^b#cdabb8^ | |||
reset=^d^ | |||
status=$(~/.local/bin/status-bar/spotify_status.py) | |||
runstate=$(pgrep -c spotify) | |||
if [[ $runstate -le 1 ]]; then | |||
echo "" | |||
else | |||
echo "$ibg$ifg $icon $tbg$tfg $status $reset" | |||
fi | |||
@ -1,122 +0,0 @@ | |||
#!/bin/python | |||
import sys | |||
import dbus | |||
import argparse | |||
parser = argparse.ArgumentParser() | |||
parser.add_argument( | |||
'-t', | |||
'--trunclen', | |||
type=int, | |||
metavar='trunclen' | |||
) | |||
parser.add_argument( | |||
'-f', | |||
'--format', | |||
type=str, | |||
metavar='custom format', | |||
dest='custom_format' | |||
) | |||
parser.add_argument( | |||
'-p', | |||
'--playpause', | |||
type=str, | |||
metavar='play-pause indicator', | |||
dest='play_pause' | |||
) | |||
parser.add_argument( | |||
'--font', | |||
type=str, | |||
metavar='the index of the font to use for the main label', | |||
dest='font' | |||
) | |||
parser.add_argument( | |||
'--playpause-font', | |||
type=str, | |||
metavar='the index of the font to use to display the playpause indicator', | |||
dest='play_pause_font' | |||
) | |||
args = parser.parse_args() | |||
def fix_string(string): | |||
# corrects encoding for the python version used | |||
if sys.version_info.major == 3: | |||
return string | |||
else: | |||
return string.encode('utf-8') | |||
# Default parameters | |||
output = fix_string(u'{play_pause} {artist}: {song}') | |||
trunclen = 35 | |||
play_pause = fix_string(u'\u25B6,\u23F8') # first character is play, second is paused | |||
label_with_font = '%{{T{font}}}{label}%{{T-}}' | |||
font = args.font | |||
play_pause_font = args.play_pause_font | |||
# parameters can be overwritten by args | |||
if args.trunclen is not None: | |||
trunclen = args.trunclen | |||
if args.custom_format is not None: | |||
output = args.custom_format | |||
if args.play_pause is not None: | |||
play_pause = args.play_pause | |||
try: | |||
session_bus = dbus.SessionBus() | |||
spotify_bus = session_bus.get_object( | |||
'org.mpris.MediaPlayer2.spotify', | |||
'/org/mpris/MediaPlayer2' | |||
) | |||
spotify_properties = dbus.Interface( | |||
spotify_bus, | |||
'org.freedesktop.DBus.Properties' | |||
) | |||
metadata = spotify_properties.Get('org.mpris.MediaPlayer2.Player', 'Metadata') | |||
status = spotify_properties.Get('org.mpris.MediaPlayer2.Player', 'PlaybackStatus') | |||
# Handle play/pause label | |||
play_pause = play_pause.split(',') | |||
if status == 'Playing': | |||
play_pause = play_pause[0] | |||
elif status == 'Paused': | |||
play_pause = play_pause[1] | |||
else: | |||
play_pause = str() | |||
if play_pause_font: | |||
play_pause = label_with_font.format(font=play_pause_font, label=play_pause) | |||
# Handle main label | |||
artist = fix_string(metadata['xesam:artist'][0]) if metadata['xesam:artist'] else '' | |||
song = fix_string(metadata['xesam:title']) if metadata['xesam:title'] else '' | |||
if not artist and not song: | |||
print('') | |||
else: | |||
if len(song) > trunclen: | |||
song = song[0:trunclen] | |||
song += '...' | |||
if ('(' in song) and (')' not in song): | |||
song += ')' | |||
if font: | |||
artist = label_with_font.format(font=font, label=artist) | |||
song = label_with_font.format(font=font, label=song) | |||
print(output.format(artist=artist, song=song, play_pause=play_pause)) | |||
except Exception as e: | |||
if isinstance(e, dbus.exceptions.DBusException): | |||
print('') | |||
else: | |||
print(e) |
@ -0,0 +1,9 @@ | |||
#! /bin/sh | |||
icon= | |||
cmd=$(date +"%R") | |||
#echo "$ifg $icon $tfg$cmd" | |||
echo -n "^c#0f111a^^b#bf616a^ $icon $cmd ^d^" |
@ -1,24 +0,0 @@ | |||
#! /bin/sh | |||
#mute=$(amixer get Master | awk -F'[][]' 'END{ print $6 }' ) | |||
#vol=$(amixer get Master | awk -F'[][]' 'END{ print $2 }' ) | |||
mute=$(pamixer --get-mute) | |||
vol=$(pamixer --get-volume) | |||
iconmute=婢 | |||
ramp1=奄 | |||
ramp2=奔 | |||
ramp3=墳 | |||
#if [[ $mute == "true" ]]; then | |||
# echo " $iconmute $vol% " | |||
if [[ $mute == "true" ]]; then | |||
echo " $iconmute mute" | |||
elif [[ "$vol" -ge "100" ]]; then | |||
echo "$ramp3 $vol% " | |||
elif [[ "$vol" -le "33" ]]; then | |||
echo "$ramp1 $vol% " | |||
elif [[ "$vol" -le "66" ]]; then | |||
echo "$ramp2 $vol% " | |||
elif [[ "$vol" -ge "66" ]]; then | |||
echo "$ramp3 $vol% " | |||
fi |
@ -1,29 +0,0 @@ | |||
#! /bin/sh | |||
#mute=$(amixer get Master | awk -F'[][]' 'END{ print $6 }' ) | |||
#vol=$(amixer get Master | awk -F'[][]' 'END{ print $2 }' ) | |||
mute=$(pamixer --get-mute) | |||
vol=$(pamixer --get-volume) | |||
iconmute= | |||
ramp1= | |||
ramp2= | |||
ramp3= | |||
mbg=^b#cc241d^ | |||
tfg=^c#292541^ | |||
ibg=^b#875b82^ | |||
tbg=^b#cdabb8^ | |||
reset=^d^ | |||
if [[ $mute == "true" ]]; then | |||
echo "$mbg$tfg $iconmute $tbg$tfg $vol% $reset" | |||
elif [[ "$vol" == "100" ]]; then | |||
echo "$ibg$tfg $ramp3 $tbg$tfg $vol% $reset" | |||
elif [[ "$vol" < "33" ]]; then | |||
echo "$ibg$tfg $ramp1 $tbg$tfg $vol% $reset" | |||
elif [[ "$vol" < "66" ]]; then | |||
echo "$ibg$tfg $ramp2 $tbg$tfg $vol% $reset" | |||
elif [[ "$vol" = "66" ]]; then | |||
echo "$ibg$tfg $ramp2 $tbg$tfg $vol% $reset" | |||
elif [[ "$vol" > "66" ]]; then | |||
echo "$ibg$tfg $ramp3 $tbg$tfg $vol% $reset" | |||
fi |
@ -0,0 +1,31 @@ | |||
#!/bin/sh | |||
export LOCATION=ankara | |||
weatherreport="${XDG_DATA_HOME:-$HOME/.local/share}/weatherreport" | |||
getforecast() { curl -sf "wttr.in/$LOCATION" > "$weatherreport" || exit 1 ;} | |||
# Some very particular and terse stream manipulation. We get the maximum | |||
# precipitation chance and the daily high and low from the downloaded file and | |||
# display them with coresponding emojis. | |||
showweather() { printf "%s" "$(sed '16q;d' "$weatherreport" | | |||
grep -wo "[0-9]*%" | sort -rn | sed "s/^/\^c#81a1c1\^ \^d\^/g;1q" | tr -d '\n')" | |||
sed '13q;d' "$weatherreport" | grep -o "m\\([-+]\\)*[0-9]\\+" | sort -n -t 'm' -k 2n | sed -e 1b -e '$!d' | tr '\n|m' ' ' | awk '{print " ﰕ " $1 "°","^c#ebcb8b^滛 ^d^" $2 "°"}' ;} | |||
case $BLOCK_BUTTON in | |||
1) setsid -f "$TERMINAL" -e less -Srf "$weatherreport" ;; | |||
2) getforecast && showweather ;; | |||
3) notify-send " Weather module" "\- Left click for full forecast. | |||
- Middle click to update forecast. | |||
: Chance of rain/snow | |||
ﰕ: Daily low | |||
滛: Daily high" ;; | |||
6) "$TERMINAL" -e "$EDITOR" "$0" ;; | |||
esac | |||
# The test if our forcecast is updated to the day. If it isn't download a new | |||
# weather report from wttr.in with the above function. | |||
[ "$(stat -c %y "$weatherreport" 2>/dev/null | cut -d' ' -f1)" = "$(date '+%Y-%m-%d')" ] || | |||
getforecast | |||
showweather |
@ -0,0 +1,76 @@ | |||
#!/usr/bin/env sh | |||
# surf_linkselect.sh: | |||
# Usage: curl somesite.com | surf_linkselect [SURFWINDOWID] [PROMPT] | |||
# Deps: xmllint, dmenu | |||
# Info: | |||
# Designed to be used w/ surf externalpipe patch. Enables keyboard-only | |||
# link selection via dmenu. Given HTML stdin, extracts links one per line | |||
# Selected link is normalized based on current URI and printed to STDOUT. | |||
# Pipe the result to a new surf or xprop _SURF_URI accordingly. | |||
SURF_WINDOW="${1:-$(xprop -root | sed -n '/^_NET_ACTIVE_WINDOW/ s/.* //p')}" | |||
DMENU_PROMPT="${2:-Link}" | |||
function dump_links_with_titles() { | |||
awk '{ | |||
input = $0; | |||
$0 = input; | |||
gsub("<[^>]*>", ""); | |||
gsub(/[ ]+/, " "); | |||
gsub("&", "\\&"); | |||
gsub("<", "<"); | |||
gsub(">", ">"); | |||
$1 = $1; | |||
title = ($0 == "" ? "None" : $0); | |||
$0 = input; | |||
match($0, /\<[ ]*[aA][^>]* [hH][rR][eE][fF]=["]([^"]+)["]/, linkextract); | |||
$0 = linkextract[1]; | |||
gsub(/^[ \t]+/,""); | |||
gsub(/[ \t]+$/,""); | |||
gsub("[ ]", "%20"); | |||
link = $0; | |||
if (link != "") { | |||
print title ": " link; | |||
} | |||
}' | |||
} | |||
function link_normalize() { | |||
URI=$1 | |||
awk -v uri=$URI '{ | |||
gsub("&", "\\&"); | |||
if ($0 ~ /^https?:\/\// || $0 ~ /^\/\/.+$/) { | |||
print $0; | |||
} else if ($0 ~/^#/) { | |||
gsub(/[#?][^#?]+/, "", uri); | |||
print uri $0; | |||
} else if ($0 ~/^\//) { | |||
split(uri, uri_parts, "/"); | |||
print uri_parts[3] $0; | |||
} else { | |||
gsub(/[#][^#]+/, "", uri); | |||
uri_parts_size = split(uri, uri_parts, "/"); | |||
delete uri_parts[uri_parts_size]; | |||
for (v in uri_parts) { | |||
uri_pagestripped = uri_pagestripped uri_parts[v] "/" | |||
} | |||
print uri_pagestripped $0; | |||
} | |||
}' | |||
} | |||
function link_select() { | |||
tr '\n\r' ' ' | | |||
xmllint --html --xpath "//a" - | | |||
dump_links_with_titles | | |||
awk '!x[$0]++' | | |||
# sort | uniq | |||
dmenu -p "$DMENU_PROMPT" -l 10 -i -w $SURF_WINDOW | | |||
awk -F' ' '{print $NF}' | | |||
link_normalize $(xprop -id $SURF_WINDOW _SURF_URI | cut -d '"' -f 2) | |||
} | |||
link_select |
@ -0,0 +1,10 @@ | |||
language: bash | |||
dist: xenial | |||
script: | |||
- shellcheck -s bash clipmenu clipmenud clipdel clipfsck clipctl | |||
- tests/test-clipmenu | |||
matrix: | |||
fast_finish: true |
@ -0,0 +1,5 @@ | |||
This is free and unencumbered software released into the public domain. | |||
Anyone is free to copy, modify, publish, use, compile, sell, or distribute this | |||
software, either in source code form or as a compiled binary, for any purpose, | |||
commercial or non-commercial, and by any means. |
@ -0,0 +1,12 @@ | |||
# `dmenu` is not a hard dependency, but you need it unless | |||
# you plan to set CM_LAUNCHER to another value like `rofi` | |||
REQUIRED_BINS := xsel clipnotify | |||
PREFIX ?= /usr | |||
$(foreach bin,$(REQUIRED_BINS),\ | |||
$(if $(shell command -v $(bin) 2> /dev/null),$(info Found `$(bin)`),$(error Missing Dep. Please install `$(bin)`))) | |||
.PHONY: install | |||
install: | |||
install --target "${PREFIX}/bin" -D -m755 clipmenu clipmenud clipdel clipctl | |||
install -D -m644 init/clipmenud.service "${PREFIX}/lib/systemd/user/clipmenud.service" |
@ -0,0 +1,100 @@ | |||
[![Tests](https://img.shields.io/travis/cdown/clipmenu/develop.svg)](https://travis-ci.org/cdown/clipmenu) | |||
clipmenu is a simple clipboard manager using [dmenu][] (or [rofi][] with | |||
`CM_LAUNCHER=rofi`) and [xsel][]. | |||
# Demo | |||
![Demo](https://cloud.githubusercontent.com/assets/660663/24079784/6f76da94-0c88-11e7-8251-40b1f02ebf3c.gif) | |||
# Usage | |||
## clipmenud | |||
Start `clipmenud`, then run `clipmenu` to select something to put on the | |||
clipboard. For systemd users, a user service called `clipmenud` is packaged as | |||
part of the project. | |||
For those using a systemd unit and not using a desktop environment which does | |||
it automatically, you must import `$DISPLAY` so that `clipmenud` knows which X | |||
server to use. For example, in your `~/.xinitrc` do this prior to launching | |||
clipmenud: | |||
systemctl --user import-environment DISPLAY | |||
## clipmenu | |||
You may wish to bind a shortcut in your window manager to launch `clipmenu`. | |||
All args passed to clipmenu are transparently dispatched to dmenu. That is, if | |||
you usually call dmenu with args to set colours and other properties, you can | |||
invoke clipmenu in exactly the same way to get the same effect, like so: | |||
clipmenu -i -fn Terminus:size=8 -nb '#002b36' -nf '#839496' -sb '#073642' -sf '#93a1a1' | |||
For a full list of environment variables that clipmenud can take, please see | |||
`clipmenud --help`. | |||
# Features | |||
The behavior of `clipmenud` can be customized through environment variables. | |||
Despite being only <300 lines, clipmenu has many useful features, including: | |||
* Customising the maximum number of clips stored (default 1000) | |||
* Disabling clip collection temporarily with `clipctl disable`, reenabling with | |||
`clipctl enable` | |||
* Not storing clipboard changes from certain applications, like password | |||
managers | |||
* Taking direct ownership of the clipboard | |||
* ...and much more. | |||
Check `clipmenud --help` to view all possible environment variables and what | |||
they do. If you manage `clipmenud` with `systemd`, you can override the | |||
defaults by using `systemctl --user edit clipmenud` to generate an override | |||
file. | |||
# Supported launchers | |||
Any dmenu-compliant application will work, but here are `CM_LAUNCHER` | |||
configurations that are known to work: | |||
- `dmenu` (the default) | |||
- `fzf` | |||
- `rofi` | |||
- `rofi-script`, for [rofi's script | |||
mode](https://github.com/davatorium/rofi-scripts/tree/master/mode-scripts) | |||
# Installation | |||
Several distributions, including Arch and Nix, provide clipmenu as an official | |||
package called `clipmenu`. | |||
## Manual installation | |||
If your distribution doesn't provide a package, you can manually install using | |||
`make install` (or better yet, create a package for your distribution!). You | |||
will need `xsel` and `clipnotify` installed, and also `dmenu` unless you plan | |||
to use a different launcher. | |||
# How does it work? | |||
clipmenud is less than 300 lines, and clipmenu is less than 100, so hopefully | |||
it should be fairly self-explanatory. However, at the most basic level: | |||
## clipmenud | |||
1. `clipmenud` uses [clipnotify](https://github.com/cdown/clipnotify) to wait | |||
for new clipboard events. | |||
2. If `clipmenud` detects changes to the clipboard contents, it writes them out | |||
to the cache directory and an index using a hash as the filename. | |||
## clipmenu | |||
1. `clipmenu` reads the index to find all available clips. | |||
2. `dmenu` is executed to allow the user to select a clip. | |||
3. After selection, the clip is put onto the PRIMARY and CLIPBOARD X | |||
selections. | |||
[dmenu]: http://tools.suckless.org/dmenu/ | |||
[rofi]: https://github.com/DaveDavenport/Rofi | |||
[xsel]: http://www.vergenet.net/~conrad/software/xsel/ |
@ -0,0 +1,52 @@ | |||
#!/usr/bin/env bash | |||
: "${CM_DIR:="${XDG_RUNTIME_DIR-"${TMPDIR-/tmp}"}"}" | |||
if [[ -z $1 ]] || [[ $1 == --help ]] || [[ $1 == -h ]]; then | |||
cat << 'EOF' | |||
clipctl provides controls for the clipmenud daemon. | |||
Commands: | |||
enable: enable clip collection | |||
disable: disable clip collection | |||
status: returns "enabled" or "disabled" | |||
toggle: toggles clip collection | |||
version: returns major version | |||
cache-dir: returns the directory used for caching | |||
EOF | |||
exit 0 | |||
fi | |||
clipmenud_pid=$(pgrep -u "$(id -u)" -nf 'clipmenud$') | |||
case $1 in | |||
enable|disable|toggle|status) | |||
if [[ -z "$clipmenud_pid" ]]; then | |||
echo "clipmenud is not running" >&2 | |||
exit 2 | |||
fi | |||
;; | |||
esac | |||
major_version=6 | |||
cache_dir=$CM_DIR/clipmenu.$major_version.$USER | |||
status_file=$cache_dir/status | |||
case $1 in | |||
enable) kill -USR2 "$clipmenud_pid" ;; | |||
disable) kill -USR1 "$clipmenud_pid" ;; | |||
status) cat "$status_file" ;; | |||
toggle) | |||
if [[ $(clipctl status) == "enabled" ]]; then | |||
clipctl disable | |||
else | |||
clipctl enable | |||
fi | |||
;; | |||
version) echo "$major_version" ;; | |||
cache-dir) echo "$cache_dir" ;; | |||
*) | |||
printf 'Unknown command: %s\n' "$1" >&2 | |||
exit 1 | |||
;; | |||
esac |
@ -0,0 +1,93 @@ | |||
#!/usr/bin/env bash | |||
CM_REAL_DELETE=0 | |||
if [[ $1 == -d ]]; then | |||
CM_REAL_DELETE=1 | |||
shift | |||
fi | |||
shopt -s nullglob | |||
cache_dir=$(clipctl cache-dir) | |||
cache_file=$cache_dir/line_cache | |||
lock_file=$cache_dir/lock | |||
lock_timeout=2 | |||
if [[ $1 == --help ]] || [[ $1 == -h ]]; then | |||
cat << 'EOF' | |||
clipdel deletes clipmenu entries matching a regex. By default, just lists what | |||
it would delete, pass -d to do it for real. If no pattern is passed as an argument, | |||
it will try to read one from standard input. | |||
".*" is special, it will just nuke the entire data directory, including the | |||
line caches and all other state. | |||
Arguments: | |||
-d Delete for real. | |||
Environment variables: | |||
- $CM_DIR: specify the base directory to store the cache dir in (default: $XDG_RUNTIME_DIR, $TMPDIR, or /tmp) | |||
EOF | |||
exit 0 | |||
fi | |||
if ! [[ -f $cache_file ]]; then | |||
printf '%s\n' "No line cache file found, no clips exist" >&2 | |||
exit 0 # Well, this is a kind of success... | |||
fi | |||
if [[ -n $1 ]]; then | |||
raw_pattern=$1 | |||
elif ! [[ -t 0 ]]; then | |||
IFS= read -r raw_pattern | |||
fi | |||
esc_pattern=${raw_pattern//\#/'\#'} | |||
# We use 2 separate sed commands so "esc_pattern" matches only the 'clip' text | |||
# without the timestamp (e.g. $> clipdel '^delete_exact_match$') | |||
sed_common_command="s#^[0-9]\+ ##;\\#${esc_pattern}#" | |||
if ! [[ $raw_pattern ]]; then | |||
printf '%s\n' 'No pattern provided, see --help' >&2 | |||
exit 2 | |||
fi | |||
exec {lock_fd}> "$lock_file" | |||
if (( CM_REAL_DELETE )) && [[ "$raw_pattern" == ".*" ]]; then | |||
flock -x -w "$lock_timeout" "$lock_fd" || exit | |||
rm -rf -- "$cache_dir" | |||
mkdir -p -- "$cache_dir" | |||
exit 0 | |||
else | |||
mapfile -t matches < <( | |||
sed -n "${sed_common_command}p" "$cache_file" | | |||
sort -u | |||
) | |||
if (( CM_REAL_DELETE )); then | |||
flock -x -w "$lock_timeout" "$lock_fd" || exit | |||
for match in "${matches[@]}"; do | |||
ck=$(cksum <<< "$match") | |||
rm -f -- "$cache_dir/$ck" | |||
done | |||
temp=$(mktemp) | |||
# sed 'h' and 'g' here means save and restore the line, so | |||
# timestamps are not removed from non-deleted lines. 'd' deletes the | |||
# line and restarts, skipping 'g'/restore. | |||
# https://www.gnu.org/software/sed/manual/html_node/Other-Commands.html#Other-Commands | |||
sed "h;${sed_common_command}d;g" "$cache_file" > "$temp" | |||
mv -- "$temp" "$cache_file" | |||
flock -u "$lock_fd" | |||
else | |||
if (( ${#matches[@]} )); then | |||
printf '%s\n' "${matches[@]}" | |||
fi | |||
fi | |||
fi |
@ -0,0 +1,28 @@ | |||
#!/usr/bin/env bash | |||
shopt -s nullglob | |||
cache_dir=$(clipctl cache-dir) | |||
cache_file=$cache_dir/line_cache | |||
declare -A cksums | |||
while IFS= read -r line; do | |||
cksum=$(cksum <<< "$line") | |||
cksums["$cksum"]="$line" | |||
# Are all cache entries represented by a file? | |||
full_file=$cache_dir/$cksum | |||
if ! [[ -f $full_file ]]; then | |||
printf 'cache entry without file: %s -> %s\n' "$line" "$full_file" >&2 | |||
fi | |||
done < <(cut -d' ' -f2- < "$cache_file") | |||
# Are all files represented by a cache entry? | |||
for file in "$cache_dir"/[012346789]*; do | |||
cksum=${file##*/} | |||
line=${cksums["$cksum"]-_missing_} | |||
if [[ $line == _missing_ ]]; then | |||
printf 'file without cache entry: %s\n' "$file" | |||
fi | |||
done |
@ -0,0 +1,71 @@ | |||
#!/usr/bin/env bash | |||
: "${CM_LAUNCHER=dmenu}" | |||
: "${CM_HISTLENGTH=8}" | |||
shopt -s nullglob | |||
cache_dir=$(clipctl cache-dir) | |||
cache_file=$cache_dir/line_cache | |||
# Not -h, see #142 | |||
if [[ $1 == --help ]]; then | |||
cat << 'EOF' | |||
clipmenu is a simple clipboard manager using dmenu and xsel. Launch this | |||
when you want to select a clip. | |||
All arguments are passed through to dmenu itself. | |||
Environment variables: | |||
- $CM_DIR: specify the base directory to store the cache dir in (default: $XDG_RUNTIME_DIR, $TMPDIR, or /tmp) | |||
- $CM_HISTLENGTH: specify the number of lines to show in dmenu/rofi (default: 8) | |||
- $CM_LAUNCHER: specify a dmenu-compatible launcher (default: dmenu) | |||
- $CM_OUTPUT_CLIP: if set, output clip selection to stdout | |||
EOF | |||
exit 0 | |||
fi | |||
if ! [[ -f "$cache_file" ]]; then | |||
printf '%s\n' 'No cache file yet, did you run clipmenud?' | |||
exit 2 | |||
fi | |||
# Blacklist of non-dmenu launchers | |||
launcher_args=(-l "${CM_HISTLENGTH}") | |||
if [[ "$CM_LAUNCHER" == fzf ]]; then | |||
launcher_args=() | |||
fi | |||
# rofi supports dmenu-like arguments through the -dmenu flag | |||
[[ "$CM_LAUNCHER" == rofi ]] && set -- -dmenu "$@" | |||
list_clips() { | |||
LC_ALL=C sort -rnk 1 < "$cache_file" | cut -d' ' -f2- | awk '!seen[$0]++' | |||
} | |||
if [[ "$CM_LAUNCHER" == rofi-script ]]; then | |||
if (( $# )); then | |||
chosen_line="${!#}" | |||
else | |||
list_clips | |||
exit | |||
fi | |||
else | |||
chosen_line=$(list_clips | "$CM_LAUNCHER" "${launcher_args[@]}" "$@") | |||
launcher_exit=$? | |||
fi | |||
[[ $chosen_line ]] || exit 1 | |||
file=$cache_dir/$(cksum <<< "$chosen_line") | |||
[[ -f "$file" ]] || exit 2 | |||
for selection in clipboard primary; do | |||
xsel --logfile /dev/null -i --"$selection" < "$file" | |||
done | |||
if (( CM_OUTPUT_CLIP )); then | |||
cat "$file" | |||
fi | |||
exit "${launcher_exit:-"$?"}" |
@ -0,0 +1,253 @@ | |||
#!/usr/bin/env bash | |||
: "${CM_ONESHOT=0}" | |||
: "${CM_OWN_CLIPBOARD=0}" | |||
: "${CM_SYNC_PRIMARY_TO_CLIPBOARD=0}" | |||
: "${CM_DEBUG=0}" | |||
: "${CM_MAX_CLIPS:=1000}" | |||
# Buffer to batch to avoid calling too much. Only used if CM_MAX_CLIPS >0. | |||
CM_MAX_CLIPS_THRESH=$(( CM_MAX_CLIPS + 10 )) | |||
: "${CM_SELECTIONS:=clipboard primary}" | |||
read -r -a selections <<< "$CM_SELECTIONS" | |||
cache_dir=$(clipctl cache-dir) | |||
cache_file=$cache_dir/line_cache | |||
status_file=$cache_dir/status | |||
# lock_file: lock for *one* iteration of clipboard capture/propagation | |||
# session_lock_file: lock to prevent multiple clipmenud daemons | |||
lock_file=$cache_dir/lock | |||
session_lock_file=$cache_dir/session_lock | |||
lock_timeout=2 | |||
has_xdotool=0 | |||
_xsel() { timeout 1 xsel --logfile /dev/null "$@"; } | |||
error() { printf 'ERROR: %s\n' "${1?}" >&2; } | |||
info() { printf 'INFO: %s\n' "${1?}"; } | |||
die() { | |||
error "${2?}" | |||
exit "${1?}" | |||
} | |||
make_line_cksums() { while read -r line; do cksum <<< "${line#* }"; done; } | |||
get_first_line() { | |||
data=${1?} | |||
# We look for the first line matching regex /./ here because we want the | |||
# first line that can provide reasonable context to the user. | |||
awk -v limit=300 ' | |||
BEGIN { printed = 0; } | |||
printed == 0 && NF { | |||
$0 = substr($0, 0, limit); | |||
printf("%s", $0); | |||
printed = 1; | |||
} | |||
END { | |||
if (NR > 1) | |||
printf(" (%d lines)", NR); | |||
printf("\n"); | |||
}' <<< "$data" | |||
} | |||
debug() { (( CM_DEBUG )) && printf '%s\n' "$@" >&2; } | |||
sig_disable() { | |||
info "Received disable signal, suspending clipboard capture" | |||
_CM_DISABLED=1 | |||
_CM_FIRST_DISABLE=1 | |||
echo "disabled" > "$status_file" | |||
[[ -v _CM_CLIPNOTIFY_PID ]] && kill "$_CM_CLIPNOTIFY_PID" | |||
} | |||
sig_enable() { | |||
if ! (( _CM_DISABLED )); then | |||
info "Received enable signal but we're not disabled, so doing nothing" | |||
return | |||
fi | |||
# Still store the last data so we don't end up eventually putting it in the | |||
# clipboard if it wasn't changed | |||
for selection in "${selections[@]}"; do | |||
data=$(_xsel -o --"$selection"; printf x) | |||
last_data_sel[$selection]=${data%x} | |||
done | |||
info "Received enable signal, resuming clipboard capture" | |||
_CM_DISABLED=0 | |||
echo "enabled" > "$status_file" | |||
} | |||
kill_background_jobs() { | |||
# While we usually _are_, there are no guarantees that we're the process | |||
# group leader. As such, all we can do is look at the pending jobs. Bash | |||
# avoids a subshell here, so the job list is in the right shell. | |||
local bg | |||
bg=$(jobs -p) | |||
# Don't log `kill' failures, since with KillMode=control-group, we're | |||
# racing with init. | |||
[[ $bg ]] && kill -- "$bg" 2>/dev/null | |||
} | |||
if [[ $1 == --help ]] || [[ $1 == -h ]]; then | |||
cat << 'EOF' | |||
clipmenud collects and caches what's on the clipboard. You can manage its | |||
operation with clipctl. | |||
Environment variables: | |||
- $CM_DEBUG: turn on debugging output (default: 0) | |||
- $CM_DIR: specify the base directory to store the cache dir in (default: $XDG_RUNTIME_DIR, $TMPDIR, or /tmp) | |||
- $CM_MAX_CLIPS: soft maximum number of clips to store, 0 for inf. At $CM_MAX_CLIPS + 10, the number of clips is reduced to $CM_MAX_CLIPS (default: 1000) | |||
- $CM_ONESHOT: run once immediately, do not loop (default: 0) | |||
- $CM_OWN_CLIPBOARD: take ownership of the clipboard. Note: this may cause missed copies if some other application also handles the clipboard directly (default: 0) | |||
- $CM_SELECTIONS: space separated list of the selections to manage (default: "clipboard primary") | |||
- $CM_SYNC_PRIMARY_TO_CLIPBOARD: sync selections from primary to clipboard immediately (default: 0) | |||
- $CM_IGNORE_WINDOW: disable recording the clipboard in windows where the windowname matches the given regex (e.g. a password manager), do not ignore any windows if unset or empty (default: unset) | |||
EOF | |||
exit 0 | |||
fi | |||
[[ $DISPLAY ]] || die 2 'The X display is unset, is your X server running?' | |||
# It's ok that this only applies to the final directory. | |||
# shellcheck disable=SC2174 | |||
mkdir -p -m0700 "$cache_dir" | |||
echo "enabled" > "$status_file" | |||
exec {session_lock_fd}> "$session_lock_file" | |||
flock -x -n "$session_lock_fd" || | |||
die 2 "Can't lock session file -- is another clipmenud running?" | |||
declare -A last_data_sel | |||
declare -A updated_sel | |||
command -v clipnotify >/dev/null 2>&1 || die 2 "clipnotify not in PATH" | |||
command -v xdotool >/dev/null 2>&1 && has_xdotool=1 | |||
if [[ $CM_IGNORE_WINDOW ]] && ! (( has_xdotool )); then | |||
echo "WARN: CM_IGNORE_WINDOW does not work without xdotool, which is not installed" >&2 | |||
fi | |||
exec {lock_fd}> "$lock_file" | |||
trap sig_disable USR1 | |||
trap sig_enable USR2 | |||
trap 'trap - INT TERM EXIT; kill_background_jobs; exit 0' INT TERM EXIT | |||
while true; do | |||
if ! (( CM_ONESHOT )); then | |||
# Make sure we're interruptible for the sig_{en,dis}able traps | |||
clipnotify & | |||
_CM_CLIPNOTIFY_PID="$!" | |||
wait "$_CM_CLIPNOTIFY_PID" | |||
fi | |||
if (( _CM_DISABLED )); then | |||
# The first one will just be from interrupting `wait`, so don't print | |||
if (( _CM_FIRST_DISABLE )); then | |||
unset _CM_FIRST_DISABLE | |||
else | |||
info "Got a clipboard notification, but we are disabled, skipping" | |||
fi | |||
continue | |||
fi | |||
if [[ $CM_IGNORE_WINDOW ]] && (( has_xdotool )); then | |||
windowname="$(xdotool getactivewindow getwindowname)" | |||
if [[ "$windowname" =~ $CM_IGNORE_WINDOW ]]; then | |||
debug "ignoring clipboard because windowname \"$windowname\" matches \"${CM_IGNORE_WINDOW}\"" | |||
continue | |||
fi | |||
fi | |||
if ! flock -x -w "$lock_timeout" "$lock_fd"; then | |||
if (( CM_ONESHOT )); then | |||
die 1 "Timed out waiting for lock" | |||
else | |||
error "Timed out waiting for lock, skipping this iteration" | |||
continue | |||
fi | |||
fi | |||
for selection in "${selections[@]}"; do | |||
updated_sel[$selection]=0 | |||
data=$(_xsel -o --"$selection"; printf x) | |||
data=${data%x} # avoid trailing newlines being stripped | |||
[[ $data == *[^[:space:]]* ]] || continue | |||
[[ $last_data == "$data" ]] && continue | |||
[[ ${last_data_sel[$selection]} == "$data" ]] && continue | |||
if [[ $last_data && $data == "$last_data"* ]] || | |||
[[ $last_data && $data == *"$last_data" ]]; then | |||
# Don't actually remove the file yet, because it might be | |||
# referenced by an older entry. These will be dealt with at vacuum. | |||
debug "$selection: $last_data is a possible partial of $data" | |||
previous_size=$(wc -c <<< "$last_cache_file_output") | |||
truncate -s -"$previous_size" "$cache_file" | |||
fi | |||
first_line=$(get_first_line "$data") | |||
debug "New clipboard entry on $selection selection: \"$first_line\"" | |||
cache_file_output="$(date +%s%N) $first_line" | |||
filename="$cache_dir/$(cksum <<< "$first_line")" | |||
last_cache_file_output=$cache_file_output | |||
last_data=$data | |||
last_data_sel[$selection]=$data | |||
updated_sel[$selection]=1 | |||
debug "Writing $data to $filename" | |||
printf '%s' "$data" > "$filename" | |||
debug "Writing $cache_file_output to $cache_file" | |||
printf '%s\n' "$cache_file_output" >> "$cache_file" | |||
if (( CM_OWN_CLIPBOARD )) && [[ $selection == clipboard ]]; then | |||
# Only clipboard, since apps like urxvt will unhilight for PRIMARY | |||
_xsel -o --clipboard | _xsel -i --clipboard | |||
fi | |||
done | |||
if (( CM_SYNC_PRIMARY_TO_CLIPBOARD )) && (( updated_sel[primary] )); then | |||
_xsel -o --primary | _xsel -i --clipboard | |||
fi | |||
# The cache file may not exist if this is the first run and data is skipped | |||
if (( CM_MAX_CLIPS )) && [[ -f "$cache_file" ]] && (( "$(wc -l < "$cache_file")" > CM_MAX_CLIPS_THRESH )); then | |||
info "Trimming clip cache to CM_MAX_CLIPS ($CM_MAX_CLIPS)" | |||
trunc_tmp=$(mktemp) | |||
tail -n "$CM_MAX_CLIPS" "$cache_file" | uniq > "$trunc_tmp" | |||
mv -- "$trunc_tmp" "$cache_file" | |||
# Vacuum up unreferenced clips. They may either have been | |||
# unreferenced by the above CM_MAX_CLIPS code, or they may be old | |||
# possible partials. | |||
declare -A cksums | |||
while IFS= read -r line; do | |||
cksum=$(cksum <<< "$line") | |||
cksums["$cksum"]="$line" | |||
done < <(cut -d' ' -f2- < "$cache_file") | |||
num_vacuumed=0 | |||
for file in "$cache_dir"/[012346789]*; do | |||
cksum=${file##*/} | |||
if [[ ${cksums["$cksum"]-_missing_} == _missing_ ]]; then | |||
debug "Vacuuming due to lack of reference: $file" | |||
(( ++num_vacuumed )) | |||
rm -- "$file" | |||
fi | |||
done | |||
unset cksums | |||
info "Vacuumed $num_vacuumed clip files." | |||
fi | |||
flock -u "$lock_fd" | |||
(( CM_ONESHOT )) && break | |||
done |
@ -0,0 +1,21 @@ | |||
[Unit] | |||
Description=Clipmenu daemon | |||
[Service] | |||
ExecStart=/usr/bin/clipmenud | |||
Restart=always | |||
RestartSec=500ms | |||
MemoryDenyWriteExecute=yes | |||
NoNewPrivileges=yes | |||
ProtectControlGroups=yes | |||
ProtectKernelTunables=yes | |||
RestrictAddressFamilies= | |||
RestrictRealtime=yes | |||
# We don't need to do any clean up, so if something hangs (borked xclip, etc), | |||
# it's going to stay that way. Just forcefully kill and get it over with. | |||
TimeoutStopSec=2 | |||
[Install] | |||
WantedBy=default.target |
@ -0,0 +1,93 @@ | |||
#!/usr/bin/env bash | |||
set -x | |||
set -e | |||
set -o pipefail | |||
dir=$(./clipctl cache-dir) | |||
cache_file=$dir/line_cache | |||
if [[ $0 == /* ]]; then | |||
location=${0%/*} | |||
else | |||
location=$PWD/${0#./} | |||
location=${location%/*} | |||
fi | |||
cat - "$location/../clipmenu" > /tmp/clipmenu << 'EOF' | |||
#!/usr/bin/env bash | |||
shopt -s expand_aliases | |||
shim() { | |||
printf '%s args:' "$1" >&2 | |||
printf ' %q' "${@:2}" >&2 | |||
printf '\n' >&2 | |||
i=0 | |||
while IFS= read -r line; do | |||
let i++ | |||
printf '%s line %d stdin: %s\n' "$1" "$i" "$line" >&2 | |||
done | |||
if [[ -v SHIM_STDOUT ]]; then | |||
printf '%s\n' "$SHIM_STDOUT" | |||
fi | |||
} | |||
# Cannot be an alias due to expansion order with $CM_LAUNCHER | |||
dmenu() { | |||
SHIM_STDOUT="Selected text. (2 lines)" shim dmenu "$@" | |||
} | |||
rofi() { | |||
SHIM_STDOUT="Selected text. (2 lines)" shim rofi "$@" | |||
} | |||
alias xsel='shim xsel' | |||
alias xclip='shim xclip' | |||
alias clipctl='./clipctl' | |||
EOF | |||
chmod a+x /tmp/clipmenu | |||
rm -rf "$dir" | |||
mkdir -p "$dir" | |||
cat > "$cache_file" << 'EOF' | |||
1234 Selected text. (2 lines) | |||
1235 Selected text 2. (2 lines) | |||
EOF | |||
cat > "$dir/$(cksum <<< 'Selected text. (2 lines)')" << 'EOF' | |||
Selected text. | |||
Yes, it's selected text. | |||
EOF | |||
### TESTS ### | |||
temp=$(mktemp) | |||
trap 'cat "$temp"' EXIT | |||
/tmp/clipmenu --foo bar > "$temp" 2>&1 | |||
# Arguments are transparently passed to dmenu | |||
grep -Fxq 'dmenu args: -l 8 --foo bar' "$temp" | |||
# Output from cache file should get to dmenu, reversed | |||
grep -Fxq 'dmenu line 1 stdin: Selected text 2. (2 lines)' "$temp" | |||
grep -Fxq 'dmenu line 2 stdin: Selected text. (2 lines)' "$temp" | |||
# xsel should copy both to clipboard *and* primary | |||
grep -Fxq 'xsel args: --logfile /dev/null -i --clipboard' "$temp" | |||
grep -Fxq 'xsel args: --logfile /dev/null -i --primary' "$temp" | |||
grep -Fxq 'xsel line 1 stdin: Selected text.' "$temp" | |||
grep -Fxq "xsel line 2 stdin: Yes, it's selected text." "$temp" | |||
CM_LAUNCHER=rofi /tmp/clipmenu --foo bar > "$temp" 2>&1 | |||
# We have a special case to add -dmenu for rofi | |||
grep -Fxq 'rofi args: -l 8 -dmenu --foo bar' "$temp" |
@ -0,0 +1,87 @@ | |||
#!/usr/bin/env bash | |||
msg() { | |||
printf '>>> %s\n' "$@" >&2 | |||
} | |||
dir=$(clipctl cache-dir) | |||
cache_file=$dir/line_cache | |||
log=$(mktemp) | |||
tim=$(mktemp) | |||
clipmenu_shim=$(mktemp) | |||
num_files=1500 | |||
trap 'rm -f -- "$log" "$tim" "$clipmenu_shim"' EXIT | |||
if [[ $0 == /* ]]; then | |||
location=${0%/*} | |||
else | |||
location=$PWD/${0#./} | |||
location=${location%/*} | |||
fi | |||
msg 'Setting up edited clipmenu' | |||
cat - "$location/../clipmenu" > /tmp/clipmenu << EOF | |||
#!/usr/bin/env bash | |||
exec 3>&2 2> >(tee "$log" | | |||
sed -u 's/^.*$/now/' | | |||
date -f - +%s.%N > "$tim") | |||
set -x | |||
dmenu() { :; } | |||
xsel() { :; } | |||
EOF | |||
chmod a+x /tmp/clipmenu | |||
if ! (( NO_RECREATE )); then | |||
rm -rf "$dir" | |||
mkdir -p "$dir" | |||
msg "Writing $num_files clipboard files" | |||
for (( i = 0; i <= num_files; i++ )); do | |||
(( i % 100 )) || printf '%s... ' "$i" | |||
line_len=$(( (RANDOM % 10000) + 1 )) | |||
num_lines=$(( (RANDOM % 10) + 1 )) | |||
data=$( | |||
tr -dc 'a-zA-Z0-9' < /dev/urandom | | |||
fold -w "$line_len" | | |||
head -"$num_lines" | |||
) | |||
read -r first_line_raw <<< "$data" | |||
printf -v first_line '%s (%s lines)\n' "$first_line_raw" "$num_lines" | |||
printf '%d %s' "$i" "$first_line" >> "$cache_file" | |||
fn=$dir/$(cksum <<< "$first_line") | |||
printf '%s' "$data" > "$fn" | |||
done | |||
printf 'done\n' | |||
else | |||
msg 'Not nuking/creating new clipmenu files' | |||
fi | |||
msg 'Running modified clipmenu' | |||
time /tmp/clipmenu | |||
(( TIME_ONLY )) && exit 0 | |||
msg 'Displaying perf data' | |||
# modified from http://stackoverflow.com/a/20855353/945780 | |||
paste <( | |||
while read -r tim ;do | |||
[ -z "$last" ] && last=${tim//.} && first=${tim//.} | |||
crt=000000000$((${tim//.}-10#0$last)) | |||
ctot=000000000$((${tim//.}-10#0$first)) | |||
printf "%12.9f %12.9f\n" ${crt:0:${#crt}-9}.${crt:${#crt}-9} \ | |||
${ctot:0:${#ctot}-9}.${ctot:${#ctot}-9} | |||
last=${tim//.} | |||
done < "$tim" | |||
) "$log" | less |
@ -1,245 +0,0 @@ | |||
/* See LICENSE file for copyright and license details. */ | |||
#include <X11/XF86keysym.h> | |||
/* appearance */ | |||
static const unsigned int borderpx = 3; /* border pixel of windows */ | |||
static const unsigned int gappx = 6; | |||
static const unsigned int snap = 32; /* snap pixel */ | |||
static const int rmaster = 0; /* 1 = master at right*/ | |||
static const int showbar = 1; /* 0 means no bar */ | |||
static const int topbar = 1; /* 0 means bottom bar */ | |||
static const int user_bh = 27; /* 0 means that dwm will calculate bar height, >= 1 means dwm will user_bh as bar height */ | |||
static const int tag_padding = 0; | |||
static const int vertpad = 0; /* vertical padding of bar */ | |||
static const int sidepad = 0; /* horizontal padding of bar */ | |||
static const char *fonts[] = {"Hack Nerd Font:Regular:size=10", "Material Design Icons:Regular:pixelsize=16:antialias=true"}; | |||
static const char dmenufont[] = "Hack Nerd Font:size=10"; | |||
static const char fore[] = "#f8f8f2"; | |||
static const char back[] = "#282a36"; | |||
static const char border[] = "#f8f8f2"; | |||
static const char col0[] = "#000000"; | |||
static const char col1[] = "#FF5555"; | |||
static const char col2[] = "#50FA7B"; | |||
static const char col3[] = "#F1FA8C"; | |||
static const char col4[] = "#BD93F9"; | |||
static const char col5[] = "#FF79C6"; | |||
static const char col6[] = "#8BE9FD"; | |||
static const char col7[] = "#BFBFBF"; | |||
static const char col8[] = "#4D4D4D"; | |||
static const char col9[] = "#FF6E67"; | |||
static const char col10[] = "#5AF78E"; | |||
static const char col11[] = "#F4F99D"; | |||
static const char col12[] = "#CAA9FA"; | |||
static const char col13[] = "#FF92D0"; | |||
static const char col14[] = "#9AEDFE"; | |||
static const char col15[] = "#E6E6E6"; | |||
static const char spotify[]= "#1FC167"; | |||
static const char *colors[][3] = { | |||
/* fg bg border */ | |||
[SchemeNorm] = { fore, back, back }, // \x0b | |||
[SchemeSel] = { fore, back, border }, // \x0c | |||
[SchemeStatus] = { fore, back, border }, // \x0d Statusbar right | |||
[SchemeTagsSel] = { col4, back, border }, // \x0e Tagbar left selected | |||
[SchemeTagsNorm] = { fore, back, border }, // \x0f Tagbar left unselected | |||
[SchemeInfoSel] = { fore, back, border }, // \x10 infobar middle selected | |||
[SchemeInfoNorm] = { fore, back, border }, // \x11 infobar middle unselected | |||
[SchemeCol1] = { col1, back, col0 }, // \x12 | |||
[SchemeCol2] = { col2, back, col0 }, // \x13 | |||
[SchemeCol3] = { col3, back, col0 }, // \x14 | |||
[SchemeCol4] = { col4, back, col0 }, // \x15 | |||
[SchemeCol5] = { col5, back, col0 }, // \x16 | |||
[SchemeCol6] = { col6, back, col0 }, // \x17 | |||
[SchemeCol7] = { col7, back, col0 }, // \x18 | |||
[SchemeCol8] = { col8, back, col0 }, // \x19 | |||
[SchemeCol9] = { col9, back, col0 }, // \x1a | |||
[SchemeCol10] = { col10, back, col0 }, // \x1b | |||
[SchemeCol11] = { col11, back, col0 }, // \x1c | |||
[SchemeCol12] = { spotify, back, col0 }, // \x1d Spotify | |||
}; | |||
/* tagging */ | |||
static const char *tags[] = { "1", "2", "3", "4", "5", "6", "7", "8", "9", "0"}; | |||
//static const char *alttags[] = { "", "", "", ""}; | |||
static const Rule rules[] = { | |||
/* xprop(1): | |||
* WM_CLASS(STRING) = instance, class | |||
* WM_NAME(STRING) = title | |||
*/ | |||
/* class instance title tags mask isfloating monitor */ | |||
{ "discord", NULL, NULL, 1 << 8, 0, -1 }, | |||
{ "Termite", NULL, NULL, 1 << 0, 0, -1 }, | |||
{ "firefoxdeveloperedition", NULL, NULL, 1 << 1, 0, -1 }, | |||
{ "Tor Browser", NULL, NULL, 1 << 1, 0, -1 }, | |||
{ "Chromium", NULL, NULL, 1 << 1, 0, -1 }, | |||
{ "zoom", NULL, NULL, 1 << 4, 0, -1 }, | |||
{ "TelegramDesktop", NULL, NULL, 1 << 8, 0, -1 }, | |||
{ "whatsapp-nativefier-d52542", NULL, NULL, 1 << 8, 0, -1 }, | |||
{ "neomutt", NULL, NULL, 1 << 7, 0, -1 }, | |||
{ "Sublime_Text", NULL, NULL, 1 << 2, 0, -1 }, | |||
{ "code-oss", NULL, NULL, 1 << 2, 0, -1 }, | |||
{ "jetbrains-idea", NULL, NULL, 1 << 2, 0, -1 }, | |||
{ "Nemo", NULL, NULL, 1 << 3, 1, -1 }, | |||
{ "Spotify", NULL, NULL, 1 << 9, 0, -1 }, | |||
}; | |||
/* layout(s) */ | |||
static const float mfact = 0.5; /* factor of master area size [0.05..0.95] */ | |||
static const int nmaster = 1; /* number of clients in master area */ | |||
static const int resizehints = 0; /* 1 means respect size hints in tiled resizals */ | |||
#include "fibonacci.c" | |||
#include "layouts.c" | |||
static const Layout layouts[] = { | |||
/* symbol arrange function */ | |||
{ "鉶", tile }, /* first entry is default */ | |||
{ "", dwindle }, | |||
{ "ﱖ", grid }, | |||
{ "", centeredmaster }, | |||
{ "", centeredfloatingmaster }, | |||
{ "[M]", monocle }, | |||
{ "[D]", deck }, | |||
{ NULL, NULL }, | |||
}; | |||
/* key definitions */ | |||
#define MODKEY Mod4Mask | |||
#define TAGKEYS(KEY,TAG) \ | |||
{ MODKEY, KEY, view, {.ui = 1 << TAG} }, \ | |||
{ MODKEY|Mod1Mask, KEY, toggleview, {.ui = 1 << TAG} }, \ | |||
{ MODKEY|ShiftMask, KEY, tag, {.ui = 1 << TAG} }, \ | |||
{ MODKEY|ControlMask, KEY, toggletag, {.ui = 1 << TAG} }, | |||
/* COSAS QUE TENGO QUE AVERIGUAR COMO QUITAR */ | |||
/* helper for spawning shell commands in the pre dwm-5.0 fashion */ | |||
#define SHCMD(cmd) { .v = (const char*[]){ "/bin/sh", "-c", cmd, NULL } } | |||
static char dmenumon[2] = "0"; /* component of dmenucmd, manipulated in spawn() */ | |||
static const char *dmenucmd[] = { "/usr/bin/rofi","-show","drun", NULL }; | |||
static const char *termcmd[] = { "/usr/bin/termite", "--title", "Terminal", NULL }; | |||
static const char *upvol[] = { "/usr/bin/pactl", "set-sink-volume", "0", "+5%", NULL }; | |||
static const char *downvol[] = { "/usr/bin/pactl", "set-sink-volume", "0", "-5%", NULL }; | |||
static const char *mutevol[] = { "/usr/bin/pactl", "set-sink-mute", "0", "toggle", NULL }; | |||
static const char *upbright[] = {"/usr/bin/xbacklight","-inc","10",NULL}; | |||
static const char *downbright[] = {"/usr/bin/xbacklight","-dec","10",NULL}; | |||
static const char *lock[] = {"/usr/bin/betterlockscreen","-l","-t","Stay the fuck out!",NULL}; | |||
static const char *clipmenu[] = {"/usr/bin/clipmenu","-i",NULL}; | |||
static const char *play[] = {"/usr/bin/playerctl","play-pause",NULL}; | |||
static const char *prev[] = {"/usr/bin/playerctl","previous",NULL}; | |||
static const char *next[] = {"/usr/bin/playerctl","next",NULL}; | |||
static const char *outmenu[] = {"/home/yigit/.scripts/dmenu-logout"}; | |||
static const char *bwmenu[] = {"/usr/bin/bwmenu", "--auto-lock", "-1"}; | |||
static const char *trackpad[] = {"/home/yigit/.scripts/toggle_touchpad.sh"}; | |||
static const char *screenshot[] = { "scrot","-d","3", "%Y-%m-%d-%s_$wx$h.jpg", "-e","xclip -selection clipboard -t image/jpg < $f; mv $f ~/Pictures/Screenshots/;dunstify -a 'SNAP' 'Email taken'", NULL }; | |||
static const char *windowshot[] = { "scrot", "-u", "-d","3", "%Y-%m-%d-%s_$wx$h.jpg", "-e","xclip -selection clipboard -t image/jpg < $f; mv $f ~/Pictures/Screenshots/;dunstify -a 'SNAP' 'Email taken'", NULL }; | |||
/* commands */ | |||
#include "movestack.c" | |||
#include "shiftview.c" | |||
static Key keys[] = { | |||
/* modifier key function argument */ | |||
{ MODKEY, XK_d, spawn, {.v = dmenucmd } }, | |||
{ MODKEY, XK_p, spawn, {.v = bwmenu } }, | |||
{ MODKEY, XK_Return, spawn, {.v = termcmd } }, | |||
{ MODKEY, XK_b, togglebar, {0} }, | |||
{ MODKEY, XK_j, focusstack, {.i = +1 } }, | |||
{ MODKEY, XK_k, focusstack, {.i = -1 } }, | |||
{ MODKEY, XK_i, incnmaster, {.i = +1 } }, | |||
{ MODKEY, XK_s, incnmaster, {.i = -1 } }, | |||
{ MODKEY, XK_h, setmfact, {.f = -0.05} }, | |||
{ MODKEY, XK_l, setmfact, {.f = +0.05} }, | |||
{ MODKEY|ShiftMask, XK_Return, zoom, {0} }, | |||
{ MODKEY, XK_Tab, view, {0} }, | |||
{ MODKEY, XK_q, killclient, {0} }, | |||
{ MODKEY|ShiftMask, XK_j, movestack, {.i = +1 } }, /*Mover ventana hacia abajo*/ | |||
{ MODKEY|ShiftMask, XK_k, movestack, {.i = -1 } }, /*Mover ventana hacia arriba*/ | |||
{ MODKEY, XK_t, setlayout, {.v = &layouts[0]} }, /*tiled*/ | |||
{ MODKEY|Mod1Mask, XK_f, setlayout, {.v = &layouts[1]} }, /*Spiral*/ | |||
{ MODKEY|Mod1Mask, XK_g, setlayout, {.v = &layouts[2]} }, /*Grid*/ | |||
{ MODKEY|Mod1Mask, XK_c, setlayout, {.v = &layouts[3]} }, /*center*/ | |||
{ MODKEY, XK_m, setlayout, {.v = &layouts[5]} }, /*monocle*/ | |||
{ MODKEY|ShiftMask, XK_m, setlayout, {.v = &layouts[6]} }, /*Deck*/ | |||
{ MODKEY, XK_s, togglefloating, {0} }, /*float*/ | |||
{ MODKEY, XK_f, togglefullscr, {0} }, /*Fullscreen*/ | |||
{ MODKEY|Mod1Mask, XK_comma, cyclelayout, {.i = -1 } }, /*Ciclar layouts*/ | |||
{ MODKEY|Mod1Mask, XK_period, cyclelayout, {.i = +1 } }, /*Ciclar layouts*/ | |||
{ MODKEY, XK_a, view, {.ui = ~0 } }, | |||
{ MODKEY|ShiftMask, XK_a, tag, {.ui = ~0 } }, | |||
{ MODKEY, XK_comma, focusmon, {.i = -1 } }, | |||
{ MODKEY, XK_period, focusmon, {.i = +1 } }, | |||
{ MODKEY|ShiftMask, XK_comma, tagmon, {.i = -1 } }, | |||
{ MODKEY|ShiftMask, XK_period, tagmon, {.i = +1 } }, | |||
/*Monitores*/ | |||
{ MODKEY|ShiftMask, XK_comma, tagmon, {.i = -1 } }, /*Mandar ventana a monitor anterior*/ | |||
{ MODKEY|ShiftMask, XK_period, tagmon, {.i = +1 } }, /*Mandar ventana a monitor siguiente*/ | |||
TAGKEYS( XK_1, 0) | |||
TAGKEYS( XK_2, 1) | |||
TAGKEYS( XK_3, 2) | |||
TAGKEYS( XK_4, 3) | |||
TAGKEYS( XK_5, 4) | |||
TAGKEYS( XK_6, 5) | |||
TAGKEYS( XK_7, 6) | |||
TAGKEYS( XK_8, 7) | |||
TAGKEYS( XK_9, 8) | |||
TAGKEYS( XK_0, 9) | |||
{ MODKEY|ShiftMask, XK_q, spawn, {.v = outmenu} }, | |||
{ MODKEY|ShiftMask, XK_t, spawn, {.v = trackpad} }, | |||
{ MODKEY, XK_x, spawn, {.v = lock } }, | |||
{ MODKEY, XK_c, spawn, {.v = clipmenu } }, | |||
{ 0, XF86XK_AudioLowerVolume, spawn, {.v = downvol } }, | |||
{ 0, XF86XK_MonBrightnessUp, spawn, {.v = upbright } }, | |||
{ 0, XF86XK_MonBrightnessDown, spawn, {.v = downbright } }, | |||
{ 0, XF86XK_AudioMute, spawn, {.v = mutevol } }, | |||
{ 0, XF86XK_AudioRaiseVolume, spawn, {.v = upvol } }, | |||
{ 0, XF86XK_AudioPrev, spawn, {.v = prev } }, | |||
{ 0, XF86XK_AudioPlay, spawn, {.v = play } }, | |||
{ 0, XF86XK_AudioNext, spawn, {.v = next } }, | |||
{ 0, XK_Print, spawn, {.v = screenshot } }, | |||
{ MODKEY, XK_Print, spawn, {.v = windowshot } }, | |||
}; | |||
/* button definitions */ | |||
/* click can be ClkTagBar, ClkLtSymbol, ClkStatusText, ClkWinTitle, ClkClientWin, or ClkRootWin */ | |||
static Button buttons[] = { | |||
/* click event mask button function argument */ | |||
{ ClkLtSymbol, 0, Button1, setlayout, {0} }, | |||
{ ClkLtSymbol, 0, Button3, setlayout, {.v = &layouts[2]} }, | |||
{ ClkWinTitle, 0, Button2, zoom, {0} }, | |||
{ ClkStatusText, 0, Button2, spawn, {.v = termcmd } }, | |||
{ ClkClientWin, MODKEY, Button1, movemouse, {0} }, | |||
{ ClkClientWin, MODKEY, Button2, togglefloating, {0} }, | |||
{ ClkClientWin, MODKEY, Button3, resizemouse, {0} }, | |||
{ ClkTagBar, 0, Button1, view, {0} }, | |||
{ ClkTagBar, 0, Button3, toggleview, {0} }, | |||
{ ClkTagBar, MODKEY, Button1, tag, {0} }, | |||
{ ClkTagBar, MODKEY, Button3, toggletag, {0} }, | |||
}; | |||
/* | |||
static Button buttons[] = { | |||
// click event mask button function argument | |||
{ ClkLtSymbol, 0, Button1, cyclelayout, {.i = +1 } }, | |||
{ ClkLtSymbol, 0, Button3, setlayout, {.v = &layouts[5]} }, | |||
{ ClkWinTitle, 0, Button2, zoom, {0} }, | |||
{ ClkStatusText, 0, Button1, sigdwmblocks, {.i = 1} }, | |||
{ ClkStatusText, 0, Button2, sigdwmblocks, {.i = 2} }, | |||
{ ClkStatusText, 0, Button3, sigdwmblocks, {.i = 3} }, | |||
{ ClkClientWin, MODKEY, Button1, movemouse, {0} }, | |||
{ ClkClientWin, MODKEY, Button2, togglefloating, {0} }, | |||
{ ClkClientWin, MODKEY, Button3, resizemouse, {0} }, | |||
{ ClkTagBar, 0, Button1, view, {0} }, | |||
{ ClkTagBar, 0, Button3, toggleview, {0} }, | |||
{ ClkTagBar, MODKEY, Button1, tag, {0} }, | |||
{ ClkTagBar, MODKEY, Button3, toggletag, {0} }, | |||
{ ClkClientWin, 0, Button1, winview, {0} }, | |||
}; | |||
*/ |
@ -0,0 +1,5 @@ | |||
*.o | |||
dwm | |||
dwm-msg | |||
config.h | |||
patches.h |
@ -1,50 +0,0 @@ | |||
# dwm - dynamic window manager | |||
# See LICENSE file for copyright and license details. | |||
include config.mk | |||
SRC = drw.c dwm.c util.c | |||
OBJ = ${SRC:.c=.o} | |||
all: options dwm | |||
options: | |||
@echo dwm build options: | |||
@echo "CFLAGS = ${CFLAGS}" | |||
@echo "LDFLAGS = ${LDFLAGS}" | |||
@echo "CC = ${CC}" | |||
.c.o: | |||
${CC} -c ${CFLAGS} $< | |||
${OBJ}: config.h config.mk | |||
config.h: | |||
cp config.def.h $@ | |||
dwm: ${OBJ} | |||
${CC} -o $@ ${OBJ} ${LDFLAGS} | |||
clean: | |||
rm -f dwm ${OBJ} dwm-${VERSION}.tar.gz | |||
dist: clean | |||
mkdir -p dwm-${VERSION} | |||
cp -R LICENSE Makefile README config.def.h config.mk\ | |||
dwm.1 drw.h util.h ${SRC} dwm.png transient.c dwm-${VERSION} | |||
tar -cf dwm-${VERSION}.tar dwm-${VERSION} | |||
gzip dwm-${VERSION}.tar | |||
rm -rf dwm-${VERSION} | |||
install: all | |||
mkdir -p ~/.local | |||
cp -f dwm ~/.local | |||
chmod 755 ~/.local/dwm | |||
# pkill dwm | |||
# ~/.local/dwmblocks & | |||
uninstall: | |||
rm -f ${DESTDIR}${PREFIX}/bin/dwm\ | |||
${DESTDIR}${MANPREFIX}/man1/dwm.1 | |||
.PHONY: all options clean dist install uninstall |
@ -0,0 +1,48 @@ | |||
dwm - dynamic window manager | |||
============================ | |||
dwm is an extremely fast, small, and dynamic window manager for X. | |||
Requirements | |||
------------ | |||
In order to build dwm you need the Xlib header files. | |||
Installation | |||
------------ | |||
Edit config.mk to match your local setup (dwm is installed into | |||
the /usr/local namespace by default). | |||
Afterwards enter the following command to build and install dwm (if | |||
necessary as root): | |||
make clean install | |||
Running dwm | |||
----------- | |||
Add the following line to your .xinitrc to start dwm using startx: | |||
exec dwm | |||
In order to connect dwm to a specific display, make sure that | |||
the DISPLAY environment variable is set correctly, e.g.: | |||
DISPLAY=foo.bar:1 exec dwm | |||
(This will start dwm on display :1 of the host foo.bar.) | |||
In order to display status info in the bar, you can do something | |||
like this in your .xinitrc: | |||
while xsetroot -name "`date` `uptime | sed 's/.*,//'`" | |||
do | |||
sleep 1 | |||
done & | |||
exec dwm | |||
Configuration | |||
------------- | |||
The configuration of dwm is done by creating a custom config.h | |||
and (re)compiling the source code. |
@ -1,32 +0,0 @@ | |||
* Dynamic Window Manager | |||
Este es mi propio build de DWM, con algunos parches y configuraciones. | |||
*** Parches | |||
**** Funcionales | |||
Aquellos parches que añaden nuevas funciones a DWM, algunas de ellas son funciones sencillas que están presentes en otros TWM. | |||
- [[https://dwm.suckless.org/patches/actualfullscreen/dwm-actualfullscreen-20191112-cb3f58a.diff][actualfullscreen:]] Permite activar verdadera pantalla completa en lugar de solo activar el modo monocle y esconder el panel. | |||
- [[https://dwm.suckless.org/patches/alwayscenter/][alwayscenter]]: Las ventanas flotantes siempre se colocan al centro de la pantalla. | |||
- [[https://dwm.suckless.org/patches/cyclelayouts/dwm-cyclelayouts-20180524-6.2.diff][cyclelayouts:]] Pasa por la lista de layouts con una combinación de botones. | |||
- [[https://dwm.suckless.org/patches/inplacerotate/][inplacerotate]]: Permite rotar las ventanas en el stack sin cambiar master. | |||
- [[https://dwm.suckless.org/patches/movestack/dwm-movestack-6.1.diff][movestack:]] Permite mover las ventanas en la parte stack, cuando se usa el esquema por defecto. | |||
- [[https://dwm.suckless.org/patches/pertag/dwm-pertag-20170513-ceac8c9.diff][pertag:]] Por defecto, dwm usa el mismo esquema para todos los tags. Con este parche cada tag tiene un esquema diferente (el esquema inicial es tile). | |||
- [[https://dwm.suckless.org/patches/switchcol/][switchcol:]] Permite cambiar el foco entre master y stack con un solo /keybinding/. Útil con el layout /deck/. | |||
- [[https://dwm.suckless.org/patches/winview/][winview]]: Si están viendo varios tags a la vez, al presionar un /keybinding/ se cambia al tag de la ventana enfocada. | |||
**** Layouts | |||
Son aquellas /layouts/ nuevas que he agregado a DWM. | |||
- [[https://dwm.suckless.org/patches/centeredmaster/][centeredmaster:]] La ventana /master/ se coloca en el centro mientras las otras aparecen en rejilla alrededor. | |||
- [[https://dwm.suckless.org/patches/deck/][deck:]] La parte /stack/ solo muestra una ventana a la vez. Si se usa con /inplacerotate/ es posible cambiar la ventana en el /stack/ sin mover /master/. | |||
- [[https://dwm.suckless.org/patches/fibonacci/dwm-fibonacci-5.8.2.diff][fibonacci:]] Dos esquemas nuevos: spiral y dwindle (à la bspwm). | |||
- [[https://dwm.suckless.org/patches/gridmode/dwm-gridmode-20170909-ceac8c9.diff][gridmode:]] Esquema de rejilla para. | |||
- [[https://dwm.suckless.org/patches/rmaster/dwm-rmaster-6.1.diff][rmaster:]] Permite invertir el orden del esquema tile: master a la izquierda y stack a la derecha. | |||
**** Estéticos | |||
Parches que unicamente sirven para mejorar el aspecto estético del WM y aportan poco o nada al aspecto funcional. | |||
- [[https://dwm.suckless.org/patches/alttagsdecoration/][alttagsdecoration]]: Permite declarar íconos diferentes para cada tag cuando están ocupados. | |||
- [[https://dwm.suckless.org/patches/colorbar/][colorbar]]: Añade esquemas de colores nuevos para cada sección del panel. | |||
- [[https://github.com/ashish-yadav11/dwmblocks][dwmblocks]]: Agrega soporte para texto coloreado y poder presionar en los módulos. | |||
- [[https://dwm.suckless.org/patches/alpha/dwm-fixborders-6.2.diff][fixborders]]: Arregla los bordes transparentes cuando se usa compton/picom. | |||
- [[https://dwm.suckless.org/patches/uselessgap/dwm-uselessgap-6.2.diff][uselessgap:]] Añade separaciones inútiles entre ventanas. Apesar de no ser el único parche, si es el más sencillo de aplicar. |
@ -0,0 +1,690 @@ | |||
This dwm 6.2 (bb2e72, 2020-07-08) side project has a different take on dwm patching. It uses preprocessor directives to decide whether or not to include a patch during build time. Essentially this means that this build, for better or worse, contains both the patched _and_ the original code. The aim being that you can select which patches to include and the build will contain that code and nothing more. Due to the complexity of some of the patches dwm-flexipatch has diverged from mainstream dwm by making some core patches non-optional for maintenance reasons. For the classic dwm-flexipatch build refer to branch [dwm-flexipatch-1.0](https://github.com/bakkeby/dwm-flexipatch/tree/dwm-flexipatch-1.0). | |||
For example to include the `alpha` patch then you would only need to flip this setting from 0 to 1 in [patches.h](https://github.com/bakkeby/dwm-flexipatch/blob/master/patches.def.h): | |||
```c | |||
#define BAR_ALPHA_PATCH 1 | |||
``` | |||
So if you have ever been curious about trying out dwm, but have been discouraged by manual patching, then this may be a good starting point to see what a "fully fledged" dwm can look like. Want to try out the `pertag` patch? Just flip a config and recompile. Once you have found out what works for you and what doesn't then you should be in a better position to choose patches should you want to start patching from scratch. | |||
Alternatively if you have found the patches you want, but don't want the rest of the flexipatch entanglement on your plate then you may want to have a look at [flexipatch-finalizer](https://github.com/bakkeby/flexipatch-finalizer); a custom pre-processor tool that removes all the unused flexipatch code leaving you with a build that contains the patches you selected. | |||
Refer to [https://dwm.suckless.org/](https://dwm.suckless.org/) for details on the dwm window manager, how to install it and how it works. | |||
--- | |||
### Changelog: | |||
2021-02-11 - Added the riodraw and focusdir patches | |||
2021-01-22 - Added the placemouse patch | |||
2021-01-02 - Added the Layoutmenu patch | |||
2020-10-26 - Added the \_NET\_CLIENT\_LIST\_STACKING patch | |||
2020-09-29 - Added the on\_empty\_keys patch (ported from InstantOS) | |||
2020-09-28 - Added the \_IS\_FLOATING patch (embedded in the EWMHTAGS patch) | |||
2020-09-18 - Added the nomodbuttons patch allowing for toggleable mouse button bindings that have no modifiers | |||
2020-09-10 - Added the anybar patch (with experimental support for dwm bar(s) + anybar) | |||
2020-09-09 - Added the bar border patch | |||
2020-09-08 - Added ipc v1.5.5 patch | |||
2020-09-07 - Scratchpads improvement (multi-monitor support) | |||
2020-09-05 - Assortment of fullscreen improvements | |||
2020-08-27 - Added aspectresize patch | |||
2020-08-25 - Unified tag icon handling while adding support for different icons per monitor. Added alttagsdecoration patch. | |||
2020-08-22 - Added logic to auto-hide bars if nothing is drawn on them (e.g. for standalone bars that only show certain clients). Added clientindicators patch and unified indicator code. Simplified Pango integration by settling on common function signatures. | |||
2020-08-21 - Simplification of color configuration; settling on a set of color schemes that is shared between multiple patches (urgentborder, floatborder and titlecolor patches made non-optional) | |||
2020-08-20 - Added experimental flexwintitle patch based on bartabgroups | |||
2020-08-13 - Added bartabgroups patch | |||
2020-08-11 - Added decoration hints and focusmaster patches | |||
2020-08-10 - Added cool autostart, insets and steam patches | |||
2020-08-02 - Added reorganizetags patch | |||
2020-07-19 - Added barmodules patch - making extrabar, leftlayout, staticstatus and statusallmons patches redundant, added powerline patch | |||
2020-07-18 - **Note**: Up until now building dwm-flexipath without any patches selected would have given you something more or less identical with mainstream dwm. In order to reduce complexity when it comes to maintainance future versions of dwm-flexipatch may diverge from this by making some patches non-optional. For the classic dwm-flexipatch and its many patch integration hints refer to branch [dwm-flexipatch-1.0](https://github.com/bakkeby/dwm-flexipatch/tree/dwm-flexipatch-1.0) which will be subject to bug fixes and mainstream dwm updates as far as feasible. | |||
2020-07-05 - Extrabar compatibility improvements (staticstatus, status2d, dwmblocks) and fix for systray randomly causing dwm to crash when first systray application starts | |||
2020-06-24 - Added resizepoint, statusbutton and sendmon_keepfocus patches | |||
2020-06-21 - Added floatpos and bar_height patches | |||
2020-06-19 - Added tagothermonitor patch | |||
2020-06-15 - Added sizehints patch | |||
2020-06-14 - Added RULE macro to replace rules setup making the default config less of an abomination and making it simpler to include new rules based patches | |||
2020-06-11 - Added the pango patch | |||
2020-06-10 - Added the staticstatus patch | |||
2020-05-31 - Added the keymodes patch | |||
2020-05-29 - Added the color emoji patch | |||
2020-05-26 - Added the status2d patch (with alpha, systray, statuspadding and dwmblocks compatibility, no statuscolors or extrabar compatibility) | |||
2020-05-21 - Added the moveplace and moveresize patches | |||
2020-05-03 - Added the shiftviewclients patch and the no transparent borders patch which removes opacity from window borders when the alpha patch is not used | |||
2020-05-02 - Added dwmblocks patch | |||
2020-04-27 - Upgraded the tagmonfixfs patch to better support moving fullscreen windows to adjacent monitors | |||
2020-04-26 - Expanded monitor rules patch to include nmaster, showbar and topbar options | |||
2020-04-23 - Improved swallow and switchtag compatibility | |||
2020-04-16 - Upgraded the scratchpad patch to the multiple scratchpads patch \[[ref](https://lists.suckless.org/hackers/2004/17205.html)\]. Updated the statuscolors patch with the width computation fix \[[ref](https://lists.suckless.org/hackers/2004/17207.html)\]. | |||
2020-04-13 - Added statuscmd patch | |||
2020-03-31 - Added the rounded corners patch | |||
2020-03-27 - Revamped the dragmfact patch to support both horizontal and vertical layout splits as well as centered master variants | |||
2020-03-25 - Added dragcfact patch | |||
2020-03-23 - Added stacker patch | |||
2020-03-21 - Reworked a series of layouts to re-allocate remaining pixels following an even (or cfacts) split with the aim of presenting a pixel perfect layout. This affects the following layouts: tile, bstack, bstackhoriz, centered master, centered floating master, columns, deck, and corresponding flextile-deluxe layouts | |||
2020-02-11 - Added swaptags and vtcolor patches | |||
2020-02-09 - Added alternative scratchpad patch | |||
2020-02-02 - Added fsignal and transferall patches | |||
2020-01-29 - Added swapfocus and shiftview patches | |||
2020-01-26 - Added transfer patch | |||
2020-01-24 - Added barpadding patch (incl. statusallmons, statuspadding, statuscolors, systray, alpha, holdbar and extrabar patch compatibility). Moved patches.h to patches.def.h to mimic the config pattern of having default and personal settings. | |||
2020-01-17 - Added inplacerotate patch | |||
2019-12-15 - Updated dragmfact patch to include fix patch to make it work with multiple monitors | |||
2019-11-26 - Added dmenumatchtop patch, added improvements to the switchtag patch based on ideas from the switchtotag patch | |||
2019-11-21 - Added fakefullscreenclient patch | |||
2019-10-24 - Added dragmfact, extrabar, exresize and nodmenu patches | |||
2019-10-22 - Added ispermanent and swallow patches | |||
2019-10-16 - Introduced [flexipatch-finalizer](https://github.com/bakkeby/flexipatch-finalizer) | |||
2019-10-11 - Added the patch to ignore Xft errors when drawing text in the status bar | |||
2019-10-10 - Added mpdcontrol, scratchpad and spawn_cwd cpatches | |||
2019-10-08 - Added columns layout and fakefullscreen patch | |||
2019-10-07 - Added sortscreens and dwmc patches, fixed minor cross-compatibility issues for combo, holdbar, leftlayout, hidevacanttags, taggrid and activetagindicatorbar | |||
2019-10-06 - Added statuscolors and statusallmons patches, fixed minor cross-compatibility issues for killunsel, fullscreen, noborder, tagintostack patches | |||
2019-10-05 - Added killunsel, taggrid, hidevacanttags and cmdcustomize patches | |||
2019-10-04 - Added maximize, movestack, monoclesymbol, noborder, tagall and tagintostack patches | |||
2019-10-03 - Added onlyquitonempty and switchcol patches | |||
2019-10-02 - Added restartsig, emptyview, focusurgent and focusadjacenttag patches | |||
2019-10-01 - Added leftlayout, fullscreen, holdbar and unfloatvisible patches | |||
2019-09-30 - Replaced flextile with flextile-deluxe, refactored monitor rules to support predetermined layouts per tag | |||
2019-09-15 - Added focusonclick, xrdb, viewontag, urgentborder and winview patches | |||
2019-09-14 - Added setborderpx, selfrestart and push (no master variant), sticky and warp patches | |||
2019-09-13 - Added titlecolor and push patches | |||
2019-09-12 - Added activetagindicatorbar, alwaysfullscreen and autoresize patches | |||
2019-09-11 - Added monitor rules, combo and ewmhtags patches | |||
2019-09-10 - Minor tweaks to awesomebar patch (incl. alpha and systray compatibility). Added floatbordercolor patch. | |||
2019-09-09 - Added deck, fibonacci (dwindle and spiral), gridmode, gapplessgrid, horizgrid, nrowgrid, centeredmaster and flextile layouts. Added alternativetags and awesomebar patches. | |||
2019-09-08 - Added cfacts and vanitygaps patches, added bstack and bstackhoriz layouts | |||
2019-09-07 - Added cyclelayouts, resizecorners, rotatestack, savefloats, statuspadding, switchtag, center and windowrolerule patches | |||
2019-09-06 - Added attachabove, attachaside, attachbelow, attachbottom, autostart, fancybar, focusonnetactive and losefullscreen patches | |||
2019-09-05 - Alpha, systray, togglefullscreen, tagallmon, tagmonfixfs, tagswapmon, pertag and zoomswap patches added | |||
### Patches included: | |||
- [activetagindicatorbar](https://dwm.suckless.org/patches/activetagindicatorbar/) | |||
- this patch changes the rectangle indicating if a tag is used by a client into a bar above the tag name | |||
- [alpha](https://dwm.suckless.org/patches/alpha/) | |||
- adds transparency for the status bar | |||
- [alternativetags](https://dwm.suckless.org/patches/alternativetags/) | |||
- adds alternative tags which can be toggled on the fly for the sole purpose of providing visual aid | |||
- [alttagsdecoration](https://dwm.suckless.org/patches/alttagsdecoration/) | |||
- provides the ability to use alternative text for tags which contain at least one window | |||
- [alwaysfullscreen](https://dwm.suckless.org/patches/alwaysfullscreen/) | |||
- prevents the focus to drift from the active fullscreen client when using focusstack\(\) | |||
- [anybar](https://dwm.suckless.org/patches/anybar/) | |||
- enables dwm to manage external status bars such as lemonbar and polybar | |||
- dwm treats the external bar as it would its own, so all regular dwm commands such as togglebar affect the external bar in the same way | |||
- [aspectresize](https://dwm.suckless.org/patches/aspectresize/) | |||
- allows windows to be resized with its aspect ratio remaining constant | |||
- [attachabove](https://dwm.suckless.org/patches/attachabove/) | |||
- new windows are placed above selected client | |||
- [attachaside](https://dwm.suckless.org/patches/attachaside/) | |||
- new windows are placed on top of the stack | |||
- [attachbelow](https://dwm.suckless.org/patches/attachbelow/) | |||
- new windows are placed below selected client | |||
- [attachbottom](https://dwm.suckless.org/patches/attachbottom/) | |||
- new windows are placed at the bottom of the stack | |||
- [autoresize](https://dwm.suckless.org/patches/autoresize/) | |||
- by default, windows that are not visible when requesting a resize/move will not get resized/moved, with this patch, however, they will | |||
- [autostart](https://dwm.suckless.org/patches/autostart/) | |||
- makes dwm run `~/.dwm/autostart_blocking.sh` and `~/.dwm/autostart.sh &` on startup | |||
- [awesomebar](https://dwm.suckless.org/patches/awesomebar/) | |||
- enhanced taskbar that allows focus / hiding / unhiding of windows by clicking on the status bar | |||
- [bar_border](https://codemadness.org/paste/dwm-border-bar.patch) | |||
- adds a border around the bar similarly to how client windows have borders | |||
- [bar_height](https://dwm.suckless.org/patches/bar_height/) | |||
- allows the bar height to be explicitly set rather than being derived from font | |||
- [barmodules](https://github.com/bakkeby/patches/wiki/barmodules/) | |||
- splits the dwm bar into modules allowing for re-arrangement of the bar and easier integration for new features | |||
- [barpadding](https://dwm.suckless.org/patches/barpadding/) | |||
- adds vertical and horizontal space between the statusbar and the edge of the screen | |||
- [bartabgroups](https://dwm.suckless.org/patches/bartabgroups/) | |||
- turns the titlebar area into a mfact-respecting tab-bar showing each client's title | |||
- [center](https://dwm.suckless.org/patches/center/) | |||
- adds an iscentered rule to automatically center clients on the current monitor | |||
- [cfacts](https://dwm.suckless.org/patches/cfacts/) | |||
- the cfacts patch provides the ability to assign different weights to clients in their respective stack in tiled layout | |||
- [clientindicators](https://dwm.suckless.org/patches/clientindicators/) | |||
- draws a dot indicator overlayed on each tag icon for each client | |||
- the selected client is drawn as a larger horizontal line | |||
- [cmdcustomize](https://dwm.suckless.org/patches/cmdcustomize/) | |||
- allows color attributes to be set through the command line | |||
- [colorbar](https://dwm.suckless.org/patches/colorbar/) | |||
- lets you change the foreground and background color of every statusbar element | |||
- color_emoji | |||
- enables color emoji in dmenu by removing a workaround for a BadLength error in the Xft library when color glyphs are used | |||
- enabling this will crash dwm on encountering such glyphs unless you also have an updated Xft library that can handle them | |||
- [combo](https://dwm.suckless.org/patches/combo/) | |||
- allows you to select multiple tags by pressing all the right keys as a combo, e.g. hold MOD and press and hold 1 and 3 together to view those two tags | |||
- [cool_autostart](https://dwm.suckless.org/patches/cool_autostart/) | |||
- allows dwm to execute commands from an array in the config.h file | |||
- when dwm exits all processes from the autostart array will be killed automatically | |||
- [cyclelayouts](https://dwm.suckless.org/patches/cyclelayouts/) | |||
- lets you cycle through all your layouts | |||
- [decoration_hints](https://dwm.suckless.org/patches/decoration_hints/) | |||
- make dwm respect \_MOTIF\_WM\_HINTS property, and not draw borders around windows requesting for it | |||
- some applications use this property to notify window managers to not draw window decorations | |||
- not respecting this property leads to issues with applications that draw their own borders, like chromium (with "Use system title bar and borders" turned off) or vlc in fullscreen mode | |||
- [dmenumatchtop](https://dwm.suckless.org/patches/dmenumatchtop) | |||
- updates the position of dmenu to match that of the bar | |||
- i.e. if topbar is 0 then dmenu will appear at the bottom and if 1 then dmenu will appear at the top | |||
- [dragcfact](https://github.com/bakkeby/patches/wiki/dragcfact/) | |||
- lets you resize clients' size (i.e. modify cfact) by holding modkey + shift + right-click and dragging the mouse | |||
- [dragmfact](https://github.com/bakkeby/patches/wiki/dragmfact/) | |||
- lets you resize the split in layouts (i.e. modify mfact) by holding the modkey + shift + left-click and dragging the mouse | |||
- this is a bespoke patch that supports vertical and horizontal layout splits as well as centered master variants | |||
- [dwmblocks](https://gist.github.com/danbyl/54f7c1d57fc6507242a95b71c3d8fdea) | |||
- signal integration to use dwm with a patched [dwmblocks](https://github.com/torrinfail/dwmblocks) | |||
- combined with the statuscmd patch this gives a clickable statusbar | |||
- [dwmc](http://dwm.suckless.org/patches/dwmc/) | |||
- a simple dwmc client using a fork of fsignal to communicate with dwm | |||
- [emptyview](https://dwm.suckless.org/patches/emptyview/) | |||
- allows no tag at all to be selected | |||
- dwm will start with no tag selected and when a client with no tag rule is started and no tag is selected then it will be opened on the first tag | |||
- [ewmhtags](https://dwm.suckless.org/patches/ewmhtags/) | |||
- adds EWMH support for \_NET_NUMBER_OF_DESKTOPS, \_NET_CURRENT_DESKTOP, \_NET_DESKTOP_NAMES and \_NET_DESKTOP_VIEWPORT, which allows for compatibility with other bars and programs that request workspace information, e.g. polybar's xworkspaces module | |||
- [exresize](https://dwm.suckless.org/patches/exresize/) | |||
- this patch allows the user to change size and placement of floating windows using only the keyboard | |||
- it also allows for temporary vertical and horizontal extension of windows similar to other WMs fill command | |||
- [~extrabar~](https://dwm.suckless.org/patches/extrabar/) | |||
- ~enables an extra status bar in dwm in a similar manner to the dualstatus patch~ | |||
- ~if the primary status is at the top via topbar then the extra status bar will be placed at the bottom and vice versa~ | |||
- extrastatus | |||
- formerly extrabar - now only splits the status into to statuses by using a status separator | |||
- [fakefullscreen](https://dwm.suckless.org/patches/fakefullscreen/) | |||
- only allow clients to "fullscreen" into the space currently given to them | |||
- as an example, this will allow you to view a fullscreen video in your browser on one half of the screen, while having the other half available for other tasks | |||
- [fakefullscreenclient](https://github.com/bakkeby/patches/wiki/fakefullscreenclient/) | |||
- similarly to the fakefullscreen patch this patch only allows clients to "fullscreen" into the space currently given to them | |||
- as an example, this will allow you to view a fullscreen video in your browser on one half of the screen, while having the other half available for other tasks | |||
- the "twist" with this patch is that fake fullscreen can be toggled on a per client basis rather than applying to all clients globally | |||
- [fancybar](https://dwm.suckless.org/patches/fancybar/) | |||
- shows the titles of all visible windows in the status bar | |||
- flexwintitle | |||
- based on the bartabgroups patch, this is a layout aware barmodules module for handling window titles intended to be used with flextile-deluxe | |||
- [~floatbordercolor~](https://dwm.suckless.org/patches/float_border_color/) | |||
- ~this patch allows a different border color to be chosen for floating windows~ | |||
- [floatpos](https://github.com/bakkeby/patches/wiki/floatpos/) | |||
- adds a float rule allowing the size and position of floating windows to be specified | |||
- control the size and position of floating windows similar to exresize, moveresize, moveplace patches | |||
- specify size and position using absolute, relative or fixed co-ordinates or | |||
- position floating windows in a grid-like manner | |||
- [focusadjacenttag](https://dwm.suckless.org/patches/focusadjacenttag/) | |||
- provides the ability to focus the tag on the immediate left or right of the currently focused tag | |||
- it also allows to send the focused window either on the left or the right tag | |||
- [focusdir](https://github.com/bakkeby/patches/wiki/focusdir) | |||
- allows focusing on clients based on direction (up, down, left, right) instead of client order | |||
- [focusmaster](https://dwm.suckless.org/patches/focusmaster/) | |||
- a simple patch that just puts focus back to the master client | |||
- [focusonclick](https://dwm.suckless.org/patches/focusonclick/) | |||
- this patch makes you switch focus only by mouse click and not sloppy (focus follows mouse pointer) | |||
- [focusonnetactive](https://dwm.suckless.org/patches/focusonnetactive/) | |||
- by default, dwm responds to \_NET_ACTIVE_WINDOW client messages by setting the urgency bit on the named window | |||
- this patch activates the window instead | |||
- [focusurgent](https://dwm.suckless.org/patches/focusurgent/) | |||
- adds a keyboard shortcut to select the next window having the urgent flag regardless of the tag it is on | |||
- [fsignal](https://dwm.suckless.org/patches/fsignal/) | |||
- send "fake signals" to dwm for handling, using xsetroot | |||
- this will not conflict with the status bar, which also is managed using xsetroot | |||
- [fullscreen](https://dwm.suckless.org/patches/fullscreen/) | |||
- applies the monocle layout with the focused client on top and hides the bar | |||
- when pressed again it shows the bar and restores the layout that was active before going fullscreen | |||
- [hidevacanttags](https://dwm.suckless.org/patches/hide_vacant_tags/) | |||
- prevents dwm from drawing tags with no clients (i.e. vacant) on the bar | |||
- [holdbar](http://dwm.suckless.org/patches/holdbar/) | |||
- with this patch dwm's built-in status bar is only shown when HOLDKEY is pressed | |||
- additionally the bar will now overlay the display | |||
- [ignore-xft-errors-when-drawing-text](https://groups.google.com/forum/m/#!topic/wmii/7bncCahYIww) | |||
- sometimes dwm crashes when it cannot render some glyphs in window titles (usually emoji) | |||
- this patch is essentially a hack to ignore any errors when drawing text on the status bar and may be removed if a more appropriate solution comes up | |||
- [inplacerotate](https://dwm.suckless.org/patches/inplacerotate/) | |||
- allows rotation of all clients in the master or stack area without affecting the other area | |||
- [insets](https://dwm.suckless.org/patches/insets/) | |||
- lets custom insets from each edge of the screen to be defined | |||
- an example use case would be to make space for an external bar | |||
- [ipc](https://github.com/mihirlad55/dwm-ipc) | |||
- implements inter-process communication through a UNIX socket for dwm | |||
- allows for the window manager to be queried for information, e.g. listen for events such as tag or layout changes, as well as send commands to control the window manager via other programs | |||
- [\_IS\_FLOATING](https://github.com/bakkeby/dwm-flexipatch/issues/50) | |||
- adds the \_IS\_FLOATING xproperty for floating windows | |||
- this can allow for a compositor to handle floating windows differently to tiled windows, e.g. only show shadows on floating windows | |||
- this patch is enabled via the ewmhtags patch | |||
- [ispermanent](https://dwm.suckless.org/patches/ispermanent/) | |||
- adds rule option for clients to avoid accidental termination by killclient for sticky windows | |||
- [keymodes](https://dwm.suckless.org/patches/keymodes/) | |||
- this patch adds key modes (like in vim or emacs) where chains of keyboard shortcuts can be performed | |||
- [~leftlayout~](http://dwm.suckless.org/patches/leftlayout/) | |||
- ~moves the layout symbol in the status bar to the left hand side~ | |||
- [losefullscreen](https://github.com/bakkeby/patches/wiki/losefullscreen/) | |||
- by default in dwm it is possible to make an application fullscreen, then use the focusstack keybindings to focus on other windows beneath the current window | |||
- it is also possible to spawn new windows (e.g. a terminal) that end up getting focus while the previous window remains in fullscreen | |||
- this patch ensures that in such scenarios the previous window loses fullscreen | |||
- [maximize](https://dwm.suckless.org/patches/maximize/) | |||
- adds helper functions for maximizing, horizontally and vertically, floating windows using keybindings | |||
- [mpdcontrol](https://dwm.suckless.org/patches/mpdcontrol/) | |||
- adds keyboard bindings to control MDP (Music Player Daemon) | |||
- [monitorrules](https://github.com/bakkeby/patches/wiki/monitorrules/) | |||
- adds rules per monitor, e.g. have default layouts per monitor | |||
- the use case for this is if the second monitor is vertical (i.e. rotated) then you may want to use a different default layout for this monitor than what is used for the main monitor (for example normal vertical split for main monitor and horizontal split for the second) | |||
- [monoclesymbol](https://dwm.suckless.org/patches/monoclesymbol/) | |||
- always display the the monocle-symbol as defined in config.h if the monocle-layout is activated | |||
- do not display the number of open clients in the current tag | |||
- [moveresize](https://dwm.suckless.org/patches/moveresize/) | |||
- allows you to move and resize dwm's clients using keyboard bindings | |||
- [movestack](https://dwm.suckless.org/patches/movestack/) | |||
- allows you to move clients around in the stack and swap them with the master | |||
- [netclientliststacking](https://github.com/bakkeby/patches/wiki/netclientliststacking) | |||
- adds support for the \_NET\_CLIENT\_LIST\_STACKING atom, needed by certain applications like the Zoom video conferencing application | |||
- [noborder](https://dwm.suckless.org/patches/noborder/) | |||
- removes the border when there is only one window visible | |||
- [nodmenu](https://dwm.suckless.org/patches/nodmenu/) | |||
- enable modifying dmenu in config.def.h which resulted previously in a compilation error because two lines of code hardcode dmenu into dwm | |||
- allows complete removal of dmenu, should you want to do that | |||
- nomodbuttons | |||
- allows for toggleable client button bindings that have no modifiers | |||
- this can, for example, allow you to move or resize using the mouse alone without holding down a modifier key, which can be practical if you have extra buttons on your mouse | |||
- [no_transparent_borders](https://github.com/szatanjl/dwm/commit/1529909466206016f2101457bbf37c67195714c8) | |||
- when terminals have transparency then their borders also become transparent | |||
- this patch ensures that borders have no transparency | |||
- note that this patch is only relevant if you are not using the alpha patch | |||
- [on\_empty\_keys](https://github.com/bakkeby/dwm-flexipatch/issues/51) | |||
- port of InstantVM's on_empty_keys functionality allowing keybindings that apply only when a tag/view is empty | |||
- an example use case is being able to launch applications with first hand keys like "f" to launch firefox | |||
- [onlyquitonempty](https://dwm.suckless.org/patches/onlyquitonempty/) | |||
- makes it so dwm will only exit via quit() if no windows are open (in order to prevent accidental loss of work) | |||
- [pango](https://dwm.suckless.org/patches/pango/) | |||
- adds simple markup for status messages using pango markup | |||
- [pertag](https://dwm.suckless.org/patches/pertag/) | |||
- adds nmaster, mfact, layouts and more per tag rather than per monitor | |||
- [placemouse](https://github.com/bakkeby/patches/wiki/placemouse) | |||
- lets the user change the position of a client in the stack using the mouse. | |||
- [powerline](https://gitlab.com/udiboy1209-suckless/dwm/-/commit/071f5063e8ac4280666828179f92788d893eea40#4b1a539194be7467cefbda22f675a3b7c19ceca7) | |||
- adds drawing of powerline arrows (and diagonal lines) for both the status bar and the tags | |||
- [push](https://dwm.suckless.org/patches/push/) | |||
- this patch provides a way to move clients up and down inside the client list | |||
- [reorganizetags](https://dwm.suckless.org/patches/reorganizetags/) | |||
- shifts all clients per tag to leftmost unoccupied tags | |||
- e.g. if clients A, B, C are tagged on tags 1, 5, 9 respectively, when reorganized they will now be on tag 1, 2, and 3 | |||
- [resizecorners](https://dwm.suckless.org/patches/resizecorners/) | |||
- by default, windows only resize from the bottom right corner | |||
- with this patch the mouse is warped to the nearest corner and you resize from there | |||
- [resizepoint](https://github.com/bakkeby/patches/wiki/resizepoint/) | |||
- practically the same as resizecorners, but the cursor does not warp to any of the window corners | |||
- [restartsig](https://dwm.suckless.org/patches/restartsig/) | |||
- adds a keyboard shortcut to restart dwm or alternatively by using kill -HUP dwmpid | |||
- additionally dwm can quit cleanly by using kill -TERM dwmpid | |||
- [riodraw](https://github.com/bakkeby/patches/wiki/riodraw/) | |||
- adds rio-like drawing to spawn new windows or to resize the selected client (backported from instantWM) | |||
- depends on an external tool slop being installed | |||
- [rotatestack](https://dwm.suckless.org/patches/rotatestack/) | |||
- let's you rotate through the stack using keyboard shortcuts | |||
- [roundedcorners](https://github.com/mitchweaver/suckless/blob/master/dwm/patches/mitch-06-rounded_corners-f04cac6d6e39cd9e3fc4fae526e3d1e8df5e34b2.patch) | |||
- adds rounded corners to client windows | |||
- [savefloats](https://dwm.suckless.org/patches/save_floats/) | |||
- saves size and position of every floating window before it is forced into tiled mode | |||
- if the window is made floating again then the old dimensions will be restored | |||
- [scratchpad](https://dwm.suckless.org/patches/scratchpad/) | |||
- the scratchpad patch allows you to spawn or restore a floating terminal window | |||
- [scratchpad_alt_1](https://github.com/GasparVardanyan/dwm-scratchpad) | |||
- this alternative patch enables a scratchpad feature in dwm similar to the scratchpad feature in i3wm | |||
- [selfrestart](https://dwm.suckless.org/patches/selfrestart/) | |||
- restart dwm without the unnecessary dependency of an external script | |||
- [sendmon_keepfocus](https://github.com/bakkeby/patches/wiki/sendmon_keepfocus/) | |||
- minor patch that allow clients to keep focus when being sent to another monitor | |||
- [setborderpx](https://dwm.suckless.org/patches/setborderpx/) | |||
- this patch allows border pixels to be changed during runtime | |||
- [shiftview](https://github.com/chau-bao-long/dotfiles/blob/master/suckless/dwm/shiftview.diff) | |||
- adds keybindings for left and right circular shift through tags | |||
- also see focusadjacenttag | |||
- [shiftviewclients](https://github.com/bakkeby/patches/wiki/shiftviewclients/) | |||
- variant of the shiftview patch which skips tags that have no clients | |||
- [sizehints](https://dwm.suckless.org/patches/sizehints/) | |||
- makes dwm obey even "soft" sizehints for new clients | |||
- [sortscreens](https://www.mail-archive.com/hackers@suckless.org/msg09400.html) | |||
- this patch aims to address some inconsistencies when it comes to focusmon, tagmon and similar functionality by explicitly sorting screens left to right (or top to bottom in a vertical layout) | |||
- [spawn_cwd](https://dwm.suckless.org/patches/spawn_cwd/) | |||
- spawns programs from currently focused client's working directory | |||
- [stacker](https://dwm.suckless.org/patches/stacker/) | |||
- provides comprehensive utilities for managing the client stack | |||
- [~staticstatus~](https://dwm.suckless.org/patches/staticstatus/) | |||
- ~allows the status text to be fixed to the bar on a specific monitor rather than being drawn on the focused monitor~ | |||
- [status2d](https://dwm.suckless.org/patches/status2d/) | |||
- allows colors and rectangle drawing in the dwm status bar | |||
- [~statusallmons~](https://dwm.suckless.org/patches/statuspadding/) | |||
- ~this patch draws and updates the statusbar on all monitors~ | |||
- [statusbutton](https://dwm.suckless.org/patches/statusbutton/) | |||
- adds a clickable button to the left hand side of the statusbar | |||
- [statuscmd](https://dwm.suckless.org/patches/statuscmd/) | |||
- adds the ability to execute shell commands based on the mouse button and position when clicking the status bar | |||
- [statuscolors](https://dwm.suckless.org/patches/statuscolors/) | |||
- enables colored text in the status bar allowing multiple color combinations for use in the status script | |||
- [statuspadding](https://dwm.suckless.org/patches/statuspadding/) | |||
- adds configuration options for horizontal and vertical padding in the status bar | |||
- [steam](https://github.com/bakkeby/patches/wiki/steam) | |||
- a minor patch that works around the issue of floating Steam windows jumping around the screen when they receive focus | |||
- [sticky](https://dwm.suckless.org/patches/sticky/) | |||
- adds toggleable keyboard shortcut to make a client 'sticky', i.e. visible on all tags | |||
- [swallow](https://dwm.suckless.org/patches/swallow/) | |||
- this patch adds "window swallowing" to dwm as known from Plan 9's windowing system rio | |||
- clients marked with isterminal in config.h swallow a window opened by any child process, e.g. running xclock in a terminal | |||
- closing the xclock window restores the terminal window in the current position | |||
- [swapfocus](https://dwm.suckless.org/patches/swapfocus/) | |||
- this patch depends on the pertag patch and makes it possible to switch focus with a single shortcut (mod-s) instead of having to think if you should use mod-j or mod-k for reaching the previously used window | |||
- [swaptags](https://dwm.suckless.org/patches/swaptags/) | |||
- allows swapping the contents of the currently selected tag with another tag by using keyboard shortcuts | |||
- [switchcol](https://dwm.suckless.org/patches/switchcol/) | |||
- allows you to switch focus between the master and stack columns using a single keybinding | |||
- [switchtag](https://github.com/bakkeby/patches/wiki/switchtag/) | |||
- when an application opens on a specific tab this patch adds the option to also switch to that tag when the application starts | |||
- optionally, the previous view can also be restored when the client is closed | |||
- [systray](https://dwm.suckless.org/patches/systray/) | |||
- adds system tray in the status bar | |||
- [tagall](https://dwm.suckless.org/patches/tagall/) | |||
- adds keyboard shortcuts to move all (or only floating) windows from one tag to another | |||
- [tagallmon](https://github.com/bakkeby/patches/wiki/tagallmon/) | |||
- move all visible windows to an adjacent monitor | |||
- [tagintostack](https://dwm.suckless.org/patches/tagintostack/) | |||
- makes new clients attach into the stack area when you toggle a new tag into view | |||
- this means your master area will remain unchanged when toggling views | |||
- [taggrid](https://dwm.suckless.org/patches/taggrid/) | |||
- adds an option to place tags in rows like in many other window managers | |||
- [tagmonfixfs](https://github.com/bakkeby/patches/wiki/tagmonfixfs/) | |||
- allows moving a fullscreen window to another monitor while remaining in fullscreen | |||
- [tagothermonitor](https://dwm.suckless.org/patches/tagothermonitor/) | |||
- adds functions and keybindings to tag a window to a desired tag on an adjacent monitor | |||
- [tagswapmon](https://github.com/bakkeby/patches/wiki/tagswapmon/) | |||
- swap all visible windows on one monitor with those of an adjacent monitor | |||
- [~titlecolor~](https://dwm.suckless.org/patches/titlecolor/) | |||
- ~adds a new color scheme used by the (selected) window title in the bar~ | |||
- [togglefullscreen](https://github.com/bakkeby/patches/wiki/togglefullscreen/) | |||
- allows you to toggle fullscreen on and off using a single shortcut key | |||
- [transfer](https://dwm.suckless.org/patches/transfer/) | |||
- lets you transfer the currently focused client between the master and stack area while increasing or decreasing the master area (nmaster) accordingly | |||
- [transferall](https://dwm.suckless.org/patches/transfer/) | |||
- lets you transfer all clients between the master and stack area while increasing or decreasing the master area (nmaster) accordingly | |||
- [unfloatvisible](https://dwm.suckless.org/patches/unfloatvisible/) | |||
- resets isfloating on any visible windows that have it set and optionally also applies a layout | |||
- [killunsel](https://dwm.suckless.org/patches/killunsel/) | |||
- kills all visible clients that are not selected (only the selected client will remain) | |||
- [~urgentborder~](https://dwm.suckless.org/patches/urgentborder/) | |||
- ~this patch makes "urgent" windows have different colors~ | |||
- [vanitygaps](https://github.com/bakkeby/patches/blob/master/dwm/dwm-vanitygaps-6.2.diff) | |||
- adds configurable gaps between windows differentiating between outer, inner, horizontal and vertical gaps | |||
- [viewontag](https://dwm.suckless.org/patches/viewontag/) | |||
- follow a window to the tag it is being moved to | |||
- [vtcolor](https://dwm.suckless.org/patches/vtcolors/) | |||
- this patch adds the ability for dwm to read colors from the linux virtual console essentially allowing you to use the same color scheme as your regular tty | |||
- [warp](https://dwm.suckless.org/patches/warp/) | |||
- warps the mouse cursor to the center of the currently focused window or screen when the mouse cursor is (a) on a different screen or (b) on top of a different window | |||
- [windowrolerule](https://github.com/bakkeby/patches/wiki/windowrolerule/) | |||
- sometimes a single application opens different windows depending on the task at hand and this is often reflected in the WM_WINDOW_ROLE(STRING) x property | |||
- this patch adds the role field to the rule configuration so that one can differentiate between, say, Firefox "browser" vs "Preferences" vs "Manager" or Google-chrome "browser" vs "pop-up". | |||
- [winview](http://dwm.suckless.org/patches/winview/) | |||
- allows switching the view to that of a given client from the all-window view (Mod-0) using a keyboard shortcut | |||
- [xrdb](http://dwm.suckless.org/patches/xrdb/) | |||
- allows dwm to read colors from xrdb (.Xresources) during runtime | |||
- [zoomfloating](https://www.reddit.com/r/suckless/comments/ie5fe3/zoomfloating_my_own_simple_original_patch/) | |||
- a simple patch that allows floating windows to be zoomed into the master stack position | |||
- [zoomswap](https://dwm.suckless.org/patches/zoomswap/) | |||
- allows a master and a stack window to swap places rather than every window on the screen changing position | |||
### Layouts included: | |||
- [bstack](https://dwm.suckless.org/patches/bottomstack/) | |||
- bottomstack layout | |||
- [bstackhoriz](https://dwm.suckless.org/patches/bottomstack/) | |||
- bottomstack horizontal layout | |||
- [centeredmaster](https://dwm.suckless.org/patches/centeredmaster/) | |||
- centeredmaster layout | |||
- [centeredfloatingmaster](https://dwm.suckless.org/patches/centeredmaster/) | |||
- centeredfloatingmaster layout | |||
- [columns](https://dwm.suckless.org/patches/columns/) | |||
- same as the default tile layout except clients in the master area are arranged in columns (i.e. left to right) | |||
- [deck](https://dwm.suckless.org/patches/deck/) | |||
- deck layout - clients in the stack area are arranged on top of each other (like monocle) | |||
- [fibonacci](https://dwm.suckless.org/patches/fibonacci/) | |||
- fibonacci (dwindle and spiral) layouts | |||
- [flextile-deluxe](https://github.com/bakkeby/patches/wiki/flextile-deluxe/) | |||
- a re-envisioned, flexible and over-the-top version of the original [flextile](https://dwm.suckless.org/patches/flextile/) patch supporting | |||
- multiple split layouts (horizontal, vertical, centered, floating, fixed) | |||
- tile arrangement on a per split basis (stack horizontally, stack vertically, grids, fibonacci) | |||
- pertag, cfacts, rmaster, vanitygaps compatibility | |||
- tile, deck, monocle, centeredmaster, bstack, bstackhoriz, gapplessgrid and more | |||
- this gives you a lot of versatility in terms of layout | |||
- [gapplessgrid](https://dwm.suckless.org/patches/gaplessgrid/) | |||
- gappless grid layout | |||
- [gridmode](https://dwm.suckless.org/patches/gridmode/) | |||
- gridmode (grid) layout | |||
- [horizgrid](https://dwm.suckless.org/patches/horizgrid/) | |||
- horizontal grid layout | |||
- [nrowgrid](https://dwm.suckless.org/patches/nrowgrid/) | |||
- nrowgrid layout, number of rows in grid controlled by nmaster |
@ -1,35 +0,0 @@ | |||
#+TITLE:DWM: Dynamic Window Manager | |||
This is my own build of DWM with some patches and configurations. | |||
*** Patches | |||
**** Feature related | |||
Those patches that add new features to DWM, some of them are simple features that are present in other TWMs. | |||
- [[https://dwm.suckless.org/patches/actualfullscreen/dwm-actualfullscreen-20191112-cb3f58a.diff][actualfullscreen:]] Allows to activate true fullscreen instead of just change to monocle layout and hide the bar. | |||
- [[https://dwm.suckless.org/patches/alwayscenter/][alwayscenter]]: Floating windows are always placed at the center of the screen. | |||
- [[https://dwm.suckless.org/patches/cyclelayouts/dwm-cyclelayouts-20180524-6.2.diff][cyclelayouts:]] Go through the layout list with a button combination. | |||
- [[https://dwm.suckless.org/patches/inplacerotate/][inplacerotate]]: Allows to rotate the windows in the stack without changing master. | |||
- [[https://dwm.suckless.org/patches/movestack/dwm-movestack-6.1.diff][movestack:]] Allows to move the windows in the stack part, when using the default layout. | |||
- [[https://dwm.suckless.org/patches/pertag/dwm-pertag-20170513-ceac8c9.diff][pertag:]] By default, dwm uses the same layout for all tags. With this patch each tag has a different layout (the initial scheme is tile). | |||
- [[https://dwm.suckless.org/patches/switchcol/][switchcol:]] It allows to change the focus between master and stack with a single keybinding. Useful with the /deck/ layout. | |||
- [[https://dwm.suckless.org/patches/winview/][winview]]: If you are seeing several tags at once, pressing a keybinding switches to the tag in the focused window. | |||
**** Layouts | |||
New layout I've added to DWM. | |||
- [[https://dwm.suckless.org/patches/centeredmaster/][centeredmaster:]] The /master/ window is placed in the center while the others appear in a grid around it. | |||
- [[https://dwm.suckless.org/patches/deck/][deck:]] The /stack/ part only shows one window at a time. If used with /inplacerotate/ it is possible to change the window in /stack/ without moving /master/. | |||
- [[https://dwm.suckless.org/patches/fibonacci/dwm-fibonacci-5.8.2.diff][fibonacci:]] Two new layouts: spiral y dwindle (à la bspwm). | |||
- [[https://dwm.suckless.org/patches/gridmode/dwm-gridmode-20170909-ceac8c9.diff][gridmode:]] Grid layout. | |||
- [[https://dwm.suckless.org/patches/rmaster/dwm-rmaster-6.1.diff][rmaster:]] Allows to reverse the order of the tile scheme: master on the left and stack on the right. | |||
**** Aesthetic related | |||
Patches that only serve to improve the aesthetic aspect of the WM and contribute little or nothing to the functional aspect. | |||
- [[https://dwm.suckless.org/patches/alttagsdecoration/][alttagsdecoration]]: Allows you to declare different icons for each tag when they are busy. | |||
- [[https://dwm.suckless.org/patches/barpadding/][barpadding:]] Add vertical andhorizontal padding to the bar from the screen. | |||
- [[https://dwm.suckless.org/patches/colorbar/][colorbar]]: Add new color schemes for each section of the panel. | |||
- [[https://github.com/ashish-yadav11/dwmblocks][dwmblocks]]: Add support for colored text and click on the dwmblocks modules. | |||
- [[https://dwm.suckless.org/patches/alpha/dwm-fixborders-6.2.diff][fixborders]]: Fix the transparent borders when using compton/picom. | |||
- [[https://dwm.suckless.org/patches/uselessgap/dwm-uselessgap-6.2.diff][uselessgap:]] Add useless gaps between windows. Although it is not the only patch, it is the easiest to apply. | |||
@ -0,0 +1,33 @@ | |||
/* See LICENSE file for copyright and license details. */ | |||
#include <X11/XF86keysym.h> | |||
/* appearance */ | |||
static const int rmaster = 0; /* 1 = master at right*/ | |||
static const unsigned int systraypinning = 0; /* 0: sloppy systray follows selected monitor, >0: pin systray to monitor X */ | |||
static const unsigned int systrayspacing = 2; /* systray spacing */ | |||
static const int systraypinningfailfirst = 1; /* 1: if pinning fails, display systray on the first monitor, False: display systray on the last monitor*/ | |||
static const int showsystray = 1; /* 0 means no systray */ | |||
static const int tag_padding = 0; | |||
static const int vertpad = 0; /* vertical padding of bar */ | |||
static const int sidepad = 0; /* horizontal padding of bar */ | |||
/* tagging */ | |||
static const char *tags[] = { "1", "2", "3", "4", "5", "6", "7", "8", "9", "0"}; | |||
//static const char *alttags[] = { "", "", "", ""}; | |||
/* layout(s) */ | |||
static const float mfact = 0.5; /* factor of master area size [0.05..0.95] */ | |||
static const int nmaster = 1; /* number of clients in master area */ | |||
static const int resizehints = 0; /* 1 means respect size hints in tiled resizals */ | |||
static const Layout layouts[] = { | |||
/* symbol arrange function */ | |||
{ "鉶", tile }, /* first entry is default */ | |||
/* { "", dwindle }, */ | |||
{ "ﱖ", grid }, | |||
{ "", centeredmaster }, | |||
{ "", centeredfloatingmaster }, | |||
{ "[M]", monocle }, | |||
{ "[D]", deck }, | |||
{ NULL, NULL }, | |||
}; |
@ -1,727 +0,0 @@ | |||
diff --git a/config.def.h b/config.def.h | |||
index 1c0b587..2d824d1 100644 | |||
--- a/config.def.h | |||
+++ b/config.def.h | |||
@@ -3,6 +3,10 @@ | |||
/* appearance */ | |||
static const unsigned int borderpx = 1; /* border pixel of windows */ | |||
static const unsigned int snap = 32; /* snap pixel */ | |||
+static const unsigned int systraypinning = 0; /* 0: sloppy systray follows selected monitor, >0: pin systray to monitor X */ | |||
+static const unsigned int systrayspacing = 2; /* systray spacing */ | |||
+static const int systraypinningfailfirst = 1; /* 1: if pinning fails, display systray on the first monitor, False: display systray on the last monitor*/ | |||
+static const int showsystray = 1; /* 0 means no systray */ | |||
static const int showbar = 1; /* 0 means no bar */ | |||
static const int topbar = 1; /* 0 means bottom bar */ | |||
static const char *fonts[] = { "monospace:size=10" }; | |||
diff --git a/dwm.c b/dwm.c | |||
index 664c527..abce13d 100644 | |||
--- a/dwm.c | |||
+++ b/dwm.c | |||
@@ -57,12 +57,30 @@ | |||
#define TAGMASK ((1 << LENGTH(tags)) - 1) | |||
#define TEXTW(X) (drw_fontset_getwidth(drw, (X)) + lrpad) | |||
+#define SYSTEM_TRAY_REQUEST_DOCK 0 | |||
+ | |||
+/* XEMBED messages */ | |||
+#define XEMBED_EMBEDDED_NOTIFY 0 | |||
+#define XEMBED_WINDOW_ACTIVATE 1 | |||
+#define XEMBED_FOCUS_IN 4 | |||
+#define XEMBED_MODALITY_ON 10 | |||
+ | |||
+#define XEMBED_MAPPED (1 << 0) | |||
+#define XEMBED_WINDOW_ACTIVATE 1 | |||
+#define XEMBED_WINDOW_DEACTIVATE 2 | |||
+ | |||
+#define VERSION_MAJOR 0 | |||
+#define VERSION_MINOR 0 | |||
+#define XEMBED_EMBEDDED_VERSION (VERSION_MAJOR << 16) | VERSION_MINOR | |||
+ | |||
/* enums */ | |||
enum { CurNormal, CurResize, CurMove, CurLast }; /* cursor */ | |||
enum { SchemeNorm, SchemeSel }; /* color schemes */ | |||
enum { NetSupported, NetWMName, NetWMState, NetWMCheck, | |||
+ NetSystemTray, NetSystemTrayOP, NetSystemTrayOrientation, NetSystemTrayOrientationHorz, | |||
NetWMFullscreen, NetActiveWindow, NetWMWindowType, | |||
NetWMWindowTypeDialog, NetClientList, NetLast }; /* EWMH atoms */ | |||
+enum { Manager, Xembed, XembedInfo, XLast }; /* Xembed atoms */ | |||
enum { WMProtocols, WMDelete, WMState, WMTakeFocus, WMLast }; /* default atoms */ | |||
enum { ClkTagBar, ClkLtSymbol, ClkStatusText, ClkWinTitle, | |||
ClkClientWin, ClkRootWin, ClkLast }; /* clicks */ | |||
@@ -141,6 +159,12 @@ typedef struct { | |||
int monitor; | |||
} Rule; | |||
+typedef struct Systray Systray; | |||
+struct Systray { | |||
+ Window win; | |||
+ Client *icons; | |||
+}; | |||
+ | |||
/* function declarations */ | |||
static void applyrules(Client *c); | |||
static int applysizehints(Client *c, int *x, int *y, int *w, int *h, int interact); | |||
@@ -172,6 +196,7 @@ static void focusstack(const Arg *arg); | |||
static Atom getatomprop(Client *c, Atom prop); | |||
static int getrootptr(int *x, int *y); | |||
static long getstate(Window w); | |||
+static unsigned int getsystraywidth(); | |||
static int gettextprop(Window w, Atom atom, char *text, unsigned int size); | |||
static void grabbuttons(Client *c, int focused); | |||
static void grabkeys(void); | |||
@@ -189,13 +214,16 @@ static void pop(Client *); | |||
static void propertynotify(XEvent *e); | |||
static void quit(const Arg *arg); | |||
static Monitor *recttomon(int x, int y, int w, int h); | |||
+static void removesystrayicon(Client *i); | |||
static void resize(Client *c, int x, int y, int w, int h, int interact); | |||
+static void resizebarwin(Monitor *m); | |||
static void resizeclient(Client *c, int x, int y, int w, int h); | |||
static void resizemouse(const Arg *arg); | |||
+static void resizerequest(XEvent *e); | |||
static void restack(Monitor *m); | |||
static void run(void); | |||
static void scan(void); | |||
-static int sendevent(Client *c, Atom proto); | |||
+static int sendevent(Window w, Atom proto, int m, long d0, long d1, long d2, long d3, long d4); | |||
static void sendmon(Client *c, Monitor *m); | |||
static void setclientstate(Client *c, long state); | |||
static void setfocus(Client *c); | |||
@@ -207,6 +235,7 @@ static void seturgent(Client *c, int urg); | |||
static void showhide(Client *c); | |||
static void sigchld(int unused); | |||
static void spawn(const Arg *arg); | |||
+static Monitor *systraytomon(Monitor *m); | |||
static void tag(const Arg *arg); | |||
static void tagmon(const Arg *arg); | |||
static void tile(Monitor *); | |||
@@ -224,18 +253,23 @@ static int updategeom(void); | |||
static void updatenumlockmask(void); | |||
static void updatesizehints(Client *c); | |||
static void updatestatus(void); | |||
+static void updatesystray(void); | |||
+static void updatesystrayicongeom(Client *i, int w, int h); | |||
+static void updatesystrayiconstate(Client *i, XPropertyEvent *ev); | |||
static void updatetitle(Client *c); | |||
static void updatewindowtype(Client *c); | |||
static void updatewmhints(Client *c); | |||
static void view(const Arg *arg); | |||
static Client *wintoclient(Window w); | |||
static Monitor *wintomon(Window w); | |||
+static Client *wintosystrayicon(Window w); | |||
static int xerror(Display *dpy, XErrorEvent *ee); | |||
static int xerrordummy(Display *dpy, XErrorEvent *ee); | |||
static int xerrorstart(Display *dpy, XErrorEvent *ee); | |||
static void zoom(const Arg *arg); | |||
/* variables */ | |||
+static Systray *systray = NULL; | |||
static const char broken[] = "broken"; | |||
static char stext[256]; | |||
static int screen; | |||
@@ -258,9 +292,10 @@ static void (*handler[LASTEvent]) (XEvent *) = { | |||
[MapRequest] = maprequest, | |||
[MotionNotify] = motionnotify, | |||
[PropertyNotify] = propertynotify, | |||
+ [ResizeRequest] = resizerequest, | |||
[UnmapNotify] = unmapnotify | |||
}; | |||
-static Atom wmatom[WMLast], netatom[NetLast]; | |||
+static Atom wmatom[WMLast], netatom[NetLast], xatom[XLast]; | |||
static int running = 1; | |||
static Cur *cursor[CurLast]; | |||
static Clr **scheme; | |||
@@ -440,7 +475,7 @@ buttonpress(XEvent *e) | |||
arg.ui = 1 << i; | |||
} else if (ev->x < x + blw) | |||
click = ClkLtSymbol; | |||
- else if (ev->x > selmon->ww - (int)TEXTW(stext)) | |||
+ else if (ev->x > selmon->ww - (int)TEXTW(stext) - getsystraywidth()) | |||
click = ClkStatusText; | |||
else | |||
click = ClkWinTitle; | |||
@@ -483,6 +518,11 @@ cleanup(void) | |||
XUngrabKey(dpy, AnyKey, AnyModifier, root); | |||
while (mons) | |||
cleanupmon(mons); | |||
+ if (showsystray) { | |||
+ XUnmapWindow(dpy, systray->win); | |||
+ XDestroyWindow(dpy, systray->win); | |||
+ free(systray); | |||
+ } | |||
for (i = 0; i < CurLast; i++) | |||
drw_cur_free(drw, cursor[i]); | |||
for (i = 0; i < LENGTH(colors); i++) | |||
@@ -513,9 +553,57 @@ cleanupmon(Monitor *mon) | |||
void | |||
clientmessage(XEvent *e) | |||
{ | |||
+ XWindowAttributes wa; | |||
+ XSetWindowAttributes swa; | |||
XClientMessageEvent *cme = &e->xclient; | |||
Client *c = wintoclient(cme->window); | |||
+ if (showsystray && cme->window == systray->win && cme->message_type == netatom[NetSystemTrayOP]) { | |||
+ /* add systray icons */ | |||
+ if (cme->data.l[1] == SYSTEM_TRAY_REQUEST_DOCK) { | |||
+ if (!(c = (Client *)calloc(1, sizeof(Client)))) | |||
+ die("fatal: could not malloc() %u bytes\n", sizeof(Client)); | |||
+ if (!(c->win = cme->data.l[2])) { | |||
+ free(c); | |||
+ return; | |||
+ } | |||
+ c->mon = selmon; | |||
+ c->next = systray->icons; | |||
+ systray->icons = c; | |||
+ if (!XGetWindowAttributes(dpy, c->win, &wa)) { | |||
+ /* use sane defaults */ | |||
+ wa.width = bh; | |||
+ wa.height = bh; | |||
+ wa.border_width = 0; | |||
+ } | |||
+ c->x = c->oldx = c->y = c->oldy = 0; | |||
+ c->w = c->oldw = wa.width; | |||
+ c->h = c->oldh = wa.height; | |||
+ c->oldbw = wa.border_width; | |||
+ c->bw = 0; | |||
+ c->isfloating = True; | |||
+ /* reuse tags field as mapped status */ | |||
+ c->tags = 1; | |||
+ updatesizehints(c); | |||
+ updatesystrayicongeom(c, wa.width, wa.height); | |||
+ XAddToSaveSet(dpy, c->win); | |||
+ XSelectInput(dpy, c->win, StructureNotifyMask | PropertyChangeMask | ResizeRedirectMask); | |||
+ XReparentWindow(dpy, c->win, systray->win, 0, 0); | |||
+ /* use parents background color */ | |||
+ swa.background_pixel = scheme[SchemeNorm][ColBg].pixel; | |||
+ XChangeWindowAttributes(dpy, c->win, CWBackPixel, &swa); | |||
+ sendevent(c->win, netatom[Xembed], StructureNotifyMask, CurrentTime, XEMBED_EMBEDDED_NOTIFY, 0 , systray->win, XEMBED_EMBEDDED_VERSION); | |||
+ /* FIXME not sure if I have to send these events, too */ | |||
+ sendevent(c->win, netatom[Xembed], StructureNotifyMask, CurrentTime, XEMBED_FOCUS_IN, 0 , systray->win, XEMBED_EMBEDDED_VERSION); | |||
+ sendevent(c->win, netatom[Xembed], StructureNotifyMask, CurrentTime, XEMBED_WINDOW_ACTIVATE, 0 , systray->win, XEMBED_EMBEDDED_VERSION); | |||
+ sendevent(c->win, netatom[Xembed], StructureNotifyMask, CurrentTime, XEMBED_MODALITY_ON, 0 , systray->win, XEMBED_EMBEDDED_VERSION); | |||
+ XSync(dpy, False); | |||
+ resizebarwin(selmon); | |||
+ updatesystray(); | |||
+ setclientstate(c, NormalState); | |||
+ } | |||
+ return; | |||
+ } | |||
if (!c) | |||
return; | |||
if (cme->message_type == netatom[NetWMState]) { | |||
@@ -568,7 +656,7 @@ configurenotify(XEvent *e) | |||
for (c = m->clients; c; c = c->next) | |||
if (c->isfullscreen) | |||
resizeclient(c, m->mx, m->my, m->mw, m->mh); | |||
- XMoveResizeWindow(dpy, m->barwin, m->wx, m->by, m->ww, bh); | |||
+ resizebarwin(m); | |||
} | |||
focus(NULL); | |||
arrange(NULL); | |||
@@ -653,6 +741,11 @@ destroynotify(XEvent *e) | |||
if ((c = wintoclient(ev->window))) | |||
unmanage(c, 1); | |||
+ else if ((c = wintosystrayicon(ev->window))) { | |||
+ removesystrayicon(c); | |||
+ resizebarwin(selmon); | |||
+ updatesystray(); | |||
+ } | |||
} | |||
void | |||
@@ -696,19 +789,23 @@ dirtomon(int dir) | |||
void | |||
drawbar(Monitor *m) | |||
{ | |||
- int x, w, tw = 0; | |||
+ int x, w, tw = 0, stw = 0; | |||
int boxs = drw->fonts->h / 9; | |||
int boxw = drw->fonts->h / 6 + 2; | |||
unsigned int i, occ = 0, urg = 0; | |||
Client *c; | |||
+ if(showsystray && m == systraytomon(m)) | |||
+ stw = getsystraywidth(); | |||
+ | |||
/* draw status first so it can be overdrawn by tags later */ | |||
if (m == selmon) { /* status is only drawn on selected monitor */ | |||
drw_setscheme(drw, scheme[SchemeNorm]); | |||
- tw = TEXTW(stext) - lrpad + 2; /* 2px right padding */ | |||
- drw_text(drw, m->ww - tw, 0, tw, bh, 0, stext, 0); | |||
+ tw = TEXTW(stext) - lrpad / 2 + 2; /* 2px right padding */ | |||
+ drw_text(drw, m->ww - tw - stw, 0, tw, bh, lrpad / 2 - 2, stext, 0); | |||
} | |||
+ resizebarwin(m); | |||
for (c = m->clients; c; c = c->next) { | |||
occ |= c->tags; | |||
if (c->isurgent) | |||
@@ -729,7 +826,7 @@ drawbar(Monitor *m) | |||
drw_setscheme(drw, scheme[SchemeNorm]); | |||
x = drw_text(drw, x, 0, w, bh, lrpad / 2, m->ltsymbol, 0); | |||
- if ((w = m->ww - tw - x) > bh) { | |||
+ if ((w = m->ww - tw - stw - x) > bh) { | |||
if (m->sel) { | |||
drw_setscheme(drw, scheme[m == selmon ? SchemeSel : SchemeNorm]); | |||
drw_text(drw, x, 0, w, bh, lrpad / 2, m->sel->name, 0); | |||
@@ -740,7 +837,7 @@ drawbar(Monitor *m) | |||
drw_rect(drw, x, 0, w, bh, 1, 1); | |||
} | |||
} | |||
- drw_map(drw, m->barwin, 0, 0, m->ww, bh); | |||
+ drw_map(drw, m->barwin, 0, 0, m->ww - stw, bh); | |||
} | |||
void | |||
@@ -777,8 +874,11 @@ expose(XEvent *e) | |||
Monitor *m; | |||
XExposeEvent *ev = &e->xexpose; | |||
- if (ev->count == 0 && (m = wintomon(ev->window))) | |||
+ if (ev->count == 0 && (m = wintomon(ev->window))) { | |||
drawbar(m); | |||
+ if (m == selmon) | |||
+ updatesystray(); | |||
+ } | |||
} | |||
void | |||
@@ -863,10 +963,17 @@ getatomprop(Client *c, Atom prop) | |||
unsigned long dl; | |||
unsigned char *p = NULL; | |||
Atom da, atom = None; | |||
+ /* FIXME getatomprop should return the number of items and a pointer to | |||
+ * the stored data instead of this workaround */ | |||
+ Atom req = XA_ATOM; | |||
+ if (prop == xatom[XembedInfo]) | |||
+ req = xatom[XembedInfo]; | |||
- if (XGetWindowProperty(dpy, c->win, prop, 0L, sizeof atom, False, XA_ATOM, | |||
+ if (XGetWindowProperty(dpy, c->win, prop, 0L, sizeof atom, False, req, | |||
&da, &di, &dl, &dl, &p) == Success && p) { | |||
atom = *(Atom *)p; | |||
+ if (da == xatom[XembedInfo] && dl == 2) | |||
+ atom = ((Atom *)p)[1]; | |||
XFree(p); | |||
} | |||
return atom; | |||
@@ -900,6 +1007,16 @@ getstate(Window w) | |||
return result; | |||
} | |||
+unsigned int | |||
+getsystraywidth() | |||
+{ | |||
+ unsigned int w = 0; | |||
+ Client *i; | |||
+ if(showsystray) | |||
+ for(i = systray->icons; i; w += i->w + systrayspacing, i = i->next) ; | |||
+ return w ? w + systrayspacing : 1; | |||
+} | |||
+ | |||
int | |||
gettextprop(Window w, Atom atom, char *text, unsigned int size) | |||
{ | |||
@@ -1004,7 +1121,7 @@ killclient(const Arg *arg) | |||
{ | |||
if (!selmon->sel) | |||
return; | |||
- if (!sendevent(selmon->sel, wmatom[WMDelete])) { | |||
+ if (!sendevent(selmon->sel->win, wmatom[WMDelete], NoEventMask, wmatom[WMDelete], CurrentTime, 0 , 0, 0)) { | |||
XGrabServer(dpy); | |||
XSetErrorHandler(xerrordummy); | |||
XSetCloseDownMode(dpy, DestroyAll); | |||
@@ -1092,6 +1209,12 @@ maprequest(XEvent *e) | |||
{ | |||
static XWindowAttributes wa; | |||
XMapRequestEvent *ev = &e->xmaprequest; | |||
+ Client *i; | |||
+ if ((i = wintosystrayicon(ev->window))) { | |||
+ sendevent(i->win, netatom[Xembed], StructureNotifyMask, CurrentTime, XEMBED_WINDOW_ACTIVATE, 0, systray->win, XEMBED_EMBEDDED_VERSION); | |||
+ resizebarwin(selmon); | |||
+ updatesystray(); | |||
+ } | |||
if (!XGetWindowAttributes(dpy, ev->window, &wa)) | |||
return; | |||
@@ -1216,6 +1339,16 @@ propertynotify(XEvent *e) | |||
Window trans; | |||
XPropertyEvent *ev = &e->xproperty; | |||
+ if ((c = wintosystrayicon(ev->window))) { | |||
+ if (ev->atom == XA_WM_NORMAL_HINTS) { | |||
+ updatesizehints(c); | |||
+ updatesystrayicongeom(c, c->w, c->h); | |||
+ } | |||
+ else | |||
+ updatesystrayiconstate(c, ev); | |||
+ resizebarwin(selmon); | |||
+ updatesystray(); | |||
+ } | |||
if ((ev->window == root) && (ev->atom == XA_WM_NAME)) | |||
updatestatus(); | |||
else if (ev->state == PropertyDelete) | |||
@@ -1266,6 +1399,20 @@ recttomon(int x, int y, int w, int h) | |||
return r; | |||
} | |||
+void | |||
+removesystrayicon(Client *i) | |||
+{ | |||
+ Client **ii; | |||
+ | |||
+ if (!showsystray || !i) | |||
+ return; | |||
+ for (ii = &systray->icons; *ii && *ii != i; ii = &(*ii)->next); | |||
+ if (ii) | |||
+ *ii = i->next; | |||
+ free(i); | |||
+} | |||
+ | |||
+ | |||
void | |||
resize(Client *c, int x, int y, int w, int h, int interact) | |||
{ | |||
@@ -1273,6 +1420,14 @@ resize(Client *c, int x, int y, int w, int h, int interact) | |||
resizeclient(c, x, y, w, h); | |||
} | |||
+void | |||
+resizebarwin(Monitor *m) { | |||
+ unsigned int w = m->ww; | |||
+ if (showsystray && m == systraytomon(m)) | |||
+ w -= getsystraywidth(); | |||
+ XMoveResizeWindow(dpy, m->barwin, m->wx, m->by, w, bh); | |||
+} | |||
+ | |||
void | |||
resizeclient(Client *c, int x, int y, int w, int h) | |||
{ | |||
@@ -1345,6 +1500,19 @@ resizemouse(const Arg *arg) | |||
} | |||
} | |||
+void | |||
+resizerequest(XEvent *e) | |||
+{ | |||
+ XResizeRequestEvent *ev = &e->xresizerequest; | |||
+ Client *i; | |||
+ | |||
+ if ((i = wintosystrayicon(ev->window))) { | |||
+ updatesystrayicongeom(i, ev->width, ev->height); | |||
+ resizebarwin(selmon); | |||
+ updatesystray(); | |||
+ } | |||
+} | |||
+ | |||
void | |||
restack(Monitor *m) | |||
{ | |||
@@ -1434,26 +1602,36 @@ setclientstate(Client *c, long state) | |||
} | |||
int | |||
-sendevent(Client *c, Atom proto) | |||
+sendevent(Window w, Atom proto, int mask, long d0, long d1, long d2, long d3, long d4) | |||
{ | |||
int n; | |||
- Atom *protocols; | |||
+ Atom *protocols, mt; | |||
int exists = 0; | |||
XEvent ev; | |||
- if (XGetWMProtocols(dpy, c->win, &protocols, &n)) { | |||
- while (!exists && n--) | |||
- exists = protocols[n] == proto; | |||
- XFree(protocols); | |||
+ if (proto == wmatom[WMTakeFocus] || proto == wmatom[WMDelete]) { | |||
+ mt = wmatom[WMProtocols]; | |||
+ if (XGetWMProtocols(dpy, w, &protocols, &n)) { | |||
+ while (!exists && n--) | |||
+ exists = protocols[n] == proto; | |||
+ XFree(protocols); | |||
+ } | |||
+ } | |||
+ else { | |||
+ exists = True; | |||
+ mt = proto; | |||
} | |||
if (exists) { | |||
ev.type = ClientMessage; | |||
- ev.xclient.window = c->win; | |||
- ev.xclient.message_type = wmatom[WMProtocols]; | |||
+ ev.xclient.window = w; | |||
+ ev.xclient.message_type = mt; | |||
ev.xclient.format = 32; | |||
- ev.xclient.data.l[0] = proto; | |||
- ev.xclient.data.l[1] = CurrentTime; | |||
- XSendEvent(dpy, c->win, False, NoEventMask, &ev); | |||
+ ev.xclient.data.l[0] = d0; | |||
+ ev.xclient.data.l[1] = d1; | |||
+ ev.xclient.data.l[2] = d2; | |||
+ ev.xclient.data.l[3] = d3; | |||
+ ev.xclient.data.l[4] = d4; | |||
+ XSendEvent(dpy, w, False, mask, &ev); | |||
} | |||
return exists; | |||
} | |||
@@ -1467,7 +1645,7 @@ setfocus(Client *c) | |||
XA_WINDOW, 32, PropModeReplace, | |||
(unsigned char *) &(c->win), 1); | |||
} | |||
- sendevent(c, wmatom[WMTakeFocus]); | |||
+ sendevent(c->win, wmatom[WMTakeFocus], NoEventMask, wmatom[WMTakeFocus], CurrentTime, 0, 0, 0); | |||
} | |||
void | |||
@@ -1556,6 +1734,10 @@ setup(void) | |||
wmatom[WMTakeFocus] = XInternAtom(dpy, "WM_TAKE_FOCUS", False); | |||
netatom[NetActiveWindow] = XInternAtom(dpy, "_NET_ACTIVE_WINDOW", False); | |||
netatom[NetSupported] = XInternAtom(dpy, "_NET_SUPPORTED", False); | |||
+ netatom[NetSystemTray] = XInternAtom(dpy, "_NET_SYSTEM_TRAY_S0", False); | |||
+ netatom[NetSystemTrayOP] = XInternAtom(dpy, "_NET_SYSTEM_TRAY_OPCODE", False); | |||
+ netatom[NetSystemTrayOrientation] = XInternAtom(dpy, "_NET_SYSTEM_TRAY_ORIENTATION", False); | |||
+ netatom[NetSystemTrayOrientationHorz] = XInternAtom(dpy, "_NET_SYSTEM_TRAY_ORIENTATION_HORZ", False); | |||
netatom[NetWMName] = XInternAtom(dpy, "_NET_WM_NAME", False); | |||
netatom[NetWMState] = XInternAtom(dpy, "_NET_WM_STATE", False); | |||
netatom[NetWMCheck] = XInternAtom(dpy, "_NET_SUPPORTING_WM_CHECK", False); | |||
@@ -1563,6 +1745,9 @@ setup(void) | |||
netatom[NetWMWindowType] = XInternAtom(dpy, "_NET_WM_WINDOW_TYPE", False); | |||
netatom[NetWMWindowTypeDialog] = XInternAtom(dpy, "_NET_WM_WINDOW_TYPE_DIALOG", False); | |||
netatom[NetClientList] = XInternAtom(dpy, "_NET_CLIENT_LIST", False); | |||
+ xatom[Manager] = XInternAtom(dpy, "MANAGER", False); | |||
+ xatom[Xembed] = XInternAtom(dpy, "_XEMBED", False); | |||
+ xatom[XembedInfo] = XInternAtom(dpy, "_XEMBED_INFO", False); | |||
/* init cursors */ | |||
cursor[CurNormal] = drw_cur_create(drw, XC_left_ptr); | |||
cursor[CurResize] = drw_cur_create(drw, XC_sizing); | |||
@@ -1571,6 +1756,8 @@ setup(void) | |||
scheme = ecalloc(LENGTH(colors), sizeof(Clr *)); | |||
for (i = 0; i < LENGTH(colors); i++) | |||
scheme[i] = drw_scm_create(drw, colors[i], 3); | |||
+ /* init system tray */ | |||
+ updatesystray(); | |||
/* init bars */ | |||
updatebars(); | |||
updatestatus(); | |||
@@ -1704,7 +1891,18 @@ togglebar(const Arg *arg) | |||
{ | |||
selmon->showbar = !selmon->showbar; | |||
updatebarpos(selmon); | |||
- XMoveResizeWindow(dpy, selmon->barwin, selmon->wx, selmon->by, selmon->ww, bh); | |||
+ resizebarwin(selmon); | |||
+ if (showsystray) { | |||
+ XWindowChanges wc; | |||
+ if (!selmon->showbar) | |||
+ wc.y = -bh; | |||
+ else if (selmon->showbar) { | |||
+ wc.y = 0; | |||
+ if (!selmon->topbar) | |||
+ wc.y = selmon->mh - bh; | |||
+ } | |||
+ XConfigureWindow(dpy, systray->win, CWY, &wc); | |||
+ } | |||
arrange(selmon); | |||
} | |||
@@ -1799,11 +1997,18 @@ unmapnotify(XEvent *e) | |||
else | |||
unmanage(c, 0); | |||
} | |||
+ else if ((c = wintosystrayicon(ev->window))) { | |||
+ /* KLUDGE! sometimes icons occasionally unmap their windows, but do | |||
+ * _not_ destroy them. We map those windows back */ | |||
+ XMapRaised(dpy, c->win); | |||
+ updatesystray(); | |||
+ } | |||
} | |||
void | |||
updatebars(void) | |||
{ | |||
+ unsigned int w; | |||
Monitor *m; | |||
XSetWindowAttributes wa = { | |||
.override_redirect = True, | |||
@@ -1814,10 +2019,15 @@ updatebars(void) | |||
for (m = mons; m; m = m->next) { | |||
if (m->barwin) | |||
continue; | |||
- m->barwin = XCreateWindow(dpy, root, m->wx, m->by, m->ww, bh, 0, DefaultDepth(dpy, screen), | |||
+ w = m->ww; | |||
+ if (showsystray && m == systraytomon(m)) | |||
+ w -= getsystraywidth(); | |||
+ m->barwin = XCreateWindow(dpy, root, m->wx, m->by, w, bh, 0, DefaultDepth(dpy, screen), | |||
CopyFromParent, DefaultVisual(dpy, screen), | |||
CWOverrideRedirect|CWBackPixmap|CWEventMask, &wa); | |||
XDefineCursor(dpy, m->barwin, cursor[CurNormal]->cursor); | |||
+ if (showsystray && m == systraytomon(m)) | |||
+ XMapRaised(dpy, systray->win); | |||
XMapRaised(dpy, m->barwin); | |||
XSetClassHint(dpy, m->barwin, &ch); | |||
} | |||
@@ -1993,6 +2203,121 @@ updatestatus(void) | |||
if (!gettextprop(root, XA_WM_NAME, stext, sizeof(stext))) | |||
strcpy(stext, "dwm-"VERSION); | |||
drawbar(selmon); | |||
+ updatesystray(); | |||
+} | |||
+ | |||
+void | |||
+updatesystrayicongeom(Client *i, int w, int h) | |||
+{ | |||
+ if (i) { | |||
+ i->h = bh; | |||
+ if (w == h) | |||
+ i->w = bh; | |||
+ else if (h == bh) | |||
+ i->w = w; | |||
+ else | |||
+ i->w = (int) ((float)bh * ((float)w / (float)h)); | |||
+ applysizehints(i, &(i->x), &(i->y), &(i->w), &(i->h), False); | |||
+ /* force icons into the systray dimensions if they don't want to */ | |||
+ if (i->h > bh) { | |||
+ if (i->w == i->h) | |||
+ i->w = bh; | |||
+ else | |||
+ i->w = (int) ((float)bh * ((float)i->w / (float)i->h)); | |||
+ i->h = bh; | |||
+ } | |||
+ } | |||
+} | |||
+ | |||
+void | |||
+updatesystrayiconstate(Client *i, XPropertyEvent *ev) | |||
+{ | |||
+ long flags; | |||
+ int code = 0; | |||
+ | |||
+ if (!showsystray || !i || ev->atom != xatom[XembedInfo] || | |||
+ !(flags = getatomprop(i, xatom[XembedInfo]))) | |||
+ return; | |||
+ | |||
+ if (flags & XEMBED_MAPPED && !i->tags) { | |||
+ i->tags = 1; | |||
+ code = XEMBED_WINDOW_ACTIVATE; | |||
+ XMapRaised(dpy, i->win); | |||
+ setclientstate(i, NormalState); | |||
+ } | |||
+ else if (!(flags & XEMBED_MAPPED) && i->tags) { | |||
+ i->tags = 0; | |||
+ code = XEMBED_WINDOW_DEACTIVATE; | |||
+ XUnmapWindow(dpy, i->win); | |||
+ setclientstate(i, WithdrawnState); | |||
+ } | |||
+ else | |||
+ return; | |||
+ sendevent(i->win, xatom[Xembed], StructureNotifyMask, CurrentTime, code, 0, | |||
+ systray->win, XEMBED_EMBEDDED_VERSION); | |||
+} | |||
+ | |||
+void | |||
+updatesystray(void) | |||
+{ | |||
+ XSetWindowAttributes wa; | |||
+ XWindowChanges wc; | |||
+ Client *i; | |||
+ Monitor *m = systraytomon(NULL); | |||
+ unsigned int x = m->mx + m->mw; | |||
+ unsigned int w = 1; | |||
+ | |||
+ if (!showsystray) | |||
+ return; | |||
+ if (!systray) { | |||
+ /* init systray */ | |||
+ if (!(systray = (Systray *)calloc(1, sizeof(Systray)))) | |||
+ die("fatal: could not malloc() %u bytes\n", sizeof(Systray)); | |||
+ systray->win = XCreateSimpleWindow(dpy, root, x, m->by, w, bh, 0, 0, scheme[SchemeSel][ColBg].pixel); | |||
+ wa.event_mask = ButtonPressMask | ExposureMask; | |||
+ wa.override_redirect = True; | |||
+ wa.background_pixel = scheme[SchemeNorm][ColBg].pixel; | |||
+ XSelectInput(dpy, systray->win, SubstructureNotifyMask); | |||
+ XChangeProperty(dpy, systray->win, netatom[NetSystemTrayOrientation], XA_CARDINAL, 32, | |||
+ PropModeReplace, (unsigned char *)&netatom[NetSystemTrayOrientationHorz], 1); | |||
+ XChangeWindowAttributes(dpy, systray->win, CWEventMask|CWOverrideRedirect|CWBackPixel, &wa); | |||
+ XMapRaised(dpy, systray->win); | |||
+ XSetSelectionOwner(dpy, netatom[NetSystemTray], systray->win, CurrentTime); | |||
+ if (XGetSelectionOwner(dpy, netatom[NetSystemTray]) == systray->win) { | |||
+ sendevent(root, xatom[Manager], StructureNotifyMask, CurrentTime, netatom[NetSystemTray], systray->win, 0, 0); | |||
+ XSync(dpy, False); | |||
+ } | |||
+ else { | |||
+ fprintf(stderr, "dwm: unable to obtain system tray.\n"); | |||
+ free(systray); | |||
+ systray = NULL; | |||
+ return; | |||
+ } | |||
+ } | |||
+ for (w = 0, i = systray->icons; i; i = i->next) { | |||
+ /* make sure the background color stays the same */ | |||
+ wa.background_pixel = scheme[SchemeNorm][ColBg].pixel; | |||
+ XChangeWindowAttributes(dpy, i->win, CWBackPixel, &wa); | |||
+ XMapRaised(dpy, i->win); | |||
+ w += systrayspacing; | |||
+ i->x = w; | |||
+ XMoveResizeWindow(dpy, i->win, i->x, 0, i->w, i->h); | |||
+ w += i->w; | |||
+ if (i->mon != m) | |||
+ i->mon = m; | |||
+ } | |||
+ w = w ? w + systrayspacing : 1; | |||
+ x -= w; | |||
+ XMoveResizeWindow(dpy, systray->win, x, m->by, w, bh); | |||
+ wc.x = x; wc.y = m->by; wc.width = w; wc.height = bh; | |||
+ wc.stack_mode = Above; wc.sibling = m->barwin; | |||
+ XConfigureWindow(dpy, systray->win, CWX|CWY|CWWidth|CWHeight|CWSibling|CWStackMode, &wc); | |||
+ XMapWindow(dpy, systray->win); | |||
+ XMapSubwindows(dpy, systray->win); | |||
+ /* redraw background */ | |||
+ XSetForeground(dpy, drw->gc, scheme[SchemeNorm][ColBg].pixel); | |||
+ XFillRectangle(dpy, systray->win, drw->gc, 0, 0, w, bh); | |||
+ XSync(dpy, False); | |||
} | |||
void | |||
@@ -2060,6 +2385,16 @@ wintoclient(Window w) | |||
return NULL; | |||
} | |||
+Client * | |||
+wintosystrayicon(Window w) { | |||
+ Client *i = NULL; | |||
+ | |||
+ if (!showsystray || !w) | |||
+ return i; | |||
+ for (i = systray->icons; i && i->win != w; i = i->next) ; | |||
+ return i; | |||
+} | |||
+ | |||
Monitor * | |||
wintomon(Window w) | |||
{ | |||
@@ -2113,6 +2448,22 @@ xerrorstart(Display *dpy, XErrorEvent *ee) | |||
return -1; | |||
} | |||
+Monitor * | |||
+systraytomon(Monitor *m) { | |||
+ Monitor *t; | |||
+ int i, n; | |||
+ if(!systraypinning) { | |||
+ if(!m) | |||
+ return selmon; | |||
+ return m == selmon ? m : NULL; | |||
+ } | |||
+ for(n = 1, t = mons; t && t->next; n++, t = t->next) ; | |||
+ for(i = 1, t = mons; t && t->next && i < systraypinning; i++, t = t->next) ; | |||
+ if(systraypinningfailfirst && n < systraypinning) | |||
+ return mons; | |||
+ return t; | |||
+} | |||
+ | |||
void | |||
zoom(const Arg *arg) | |||
{ |
@ -1,28 +0,0 @@ | |||
/** | |||
* dwmconfig.h | |||
* Hardware multimedia keys | |||
*/ | |||
/* Somewhere at the beginning of config.h include: | |||
#include <X11/XF86keysym.h> | |||
/* Add somewhere in your constants definition section */ | |||
static const char *upvol[] = { "/usr/bin/pactl", "set-sink-volume", "0", "+5%", NULL }; | |||
static const char *downvol[] = { "/usr/bin/pactl", "set-sink-volume", "0", "-5%", NULL }; | |||
static const char *mutevol[] = { "/usr/bin/pactl", "set-sink-mute", "0", "toggle", NULL }; | |||
/* Add to keys[] array. With 0 as modifier, you are able to use the keys directly. */ | |||
static Key keys[] = { | |||
{ 0, XF86XK_AudioLowerVolume, spawn, {.v = downvol } }, | |||
{ 0, XF86XK_AudioMute, spawn, {.v = mutevol } }, | |||
{ 0, XF86XK_AudioRaiseVolume, spawn, {.v = upvol } }, | |||
}; | |||
/* If you have a small laptop keyboard or don't want to spring your fingers too far away. */ | |||
static Key keys[] = { | |||
{ MODKEY, XK_F11, spawn, {.v = downvol } }, | |||
{ MODKEY, XK_F9, spawn, {.v = mutevol } }, | |||
{ MODKEY, XK_F12, spawn, {.v = upvol } }, | |||
}; |
@ -1,66 +0,0 @@ | |||
void | |||
fibonacci(Monitor *mon, int s) { | |||
unsigned int i, n, nx, ny, nw, nh; | |||
Client *c; | |||
for(n = 0, c = nexttiled(mon->clients); c; c = nexttiled(c->next), n++); | |||
if(n == 0) | |||
return; | |||
nx = mon->wx; | |||
ny = 0; | |||
nw = mon->ww; | |||
nh = mon->wh; | |||
for(i = 0, c = nexttiled(mon->clients); c; c = nexttiled(c->next)) { | |||
if((i % 2 && nh / 2 > 2 * c->bw) | |||
|| (!(i % 2) && nw / 2 > 2 * c->bw)) { | |||
if(i < n - 1) { | |||
if(i % 2) | |||
nh /= 2; | |||
else | |||
nw /= 2; | |||
if((i % 4) == 2 && !s) | |||
nx += nw; | |||
else if((i % 4) == 3 && !s) | |||
ny += nh; | |||
} | |||
if((i % 4) == 0) { | |||
if(s) | |||
ny += nh; | |||
else | |||
ny -= nh; | |||
} | |||
else if((i % 4) == 1) | |||
nx += nw; | |||
else if((i % 4) == 2) | |||
ny += nh; | |||
else if((i % 4) == 3) { | |||
if(s) | |||
nx += nw; | |||
else | |||
nx -= nw; | |||
} | |||
if(i == 0) | |||
{ | |||
if(n != 1) | |||
nw = mon->ww * mon->mfact; | |||
ny = mon->wy; | |||
} | |||
else if(i == 1) | |||
nw = mon->ww - nw; | |||
i++; | |||
} | |||
resize(c, nx, ny, nw - 2 * c->bw, nh - 2 * c->bw, False); | |||
} | |||
} | |||
void | |||
dwindle(Monitor *mon) { | |||
fibonacci(mon, 1); | |||
} | |||
void | |||
spiral(Monitor *mon) { | |||
fibonacci(mon, 0); | |||
} |
@ -0,0 +1,345 @@ | |||
#!/bin/bash | |||
KEEP_FILES=0 | |||
ECHO_COMMANDS=0 | |||
RUN_SCRIPT=0 | |||
DIRECTORY=. | |||
OUTPUT_DIRECTORY= | |||
KEEP_GITFILES=0 | |||
DEBUG=0 | |||
if [[ $# = 0 ]]; then | |||
set -- '-h' | |||
fi | |||
while (( $# )); do | |||
case "$1" in | |||
-d|--directory) | |||
shift | |||
DIRECTORY=$1 # source directory | |||
shift | |||
;; | |||
-o|--output) | |||
shift | |||
OUTPUT_DIRECTORY=$1 | |||
shift | |||
;; | |||
--debug) | |||
shift | |||
DEBUG=1 | |||
;; | |||
-r|--run) | |||
shift | |||
RUN_SCRIPT=1 | |||
;; | |||
-e|--echo) | |||
shift | |||
ECHO_COMMANDS=1 | |||
;; | |||
-k|--keep) | |||
shift | |||
KEEP_FILES=1 | |||
;; | |||
-g|--git) | |||
shift | |||
KEEP_GITFILES=1 | |||
;; | |||
-h|--help) | |||
shift | |||
fmt=" %-31s%s\n" | |||
printf "%s" "Usage: $(basename ${BASH_SOURCE[0]}) [OPTION?]" | |||
printf "\n" | |||
printf "\nThis is a custom pre-processor designed to remove unused flexipatch patches and create a final build." | |||
printf "\n\n" | |||
printf "$fmt" "-r, --run" "include this flag to confirm that you really do want to run this script" | |||
printf "\n" | |||
printf "$fmt" "-d, --directory <dir>" "the flexipatch source directory to process (defaults to current directory)" | |||
printf "$fmt" "-o, --output <dir>" "the output directory to store the processed files" | |||
printf "$fmt" "-h, --help" "display this help section" | |||
printf "$fmt" "-k, --keep" "keep temporary files and do not replace the original ones" | |||
printf "$fmt" "-g, --git" "keep .git files" | |||
printf "$fmt" "-e, --echo" "echo commands that will be run rather than running them" | |||
printf "$fmt" " --debug" "prints additional debug information to stderr" | |||
printf "\nWarning! This script alters and removes files within the source directory." | |||
printf "\nWarning! This process is irreversible! Use with care. Do make a backup before running this." | |||
printf "\n\n" | |||
exit | |||
;; | |||
*) | |||
echo "Ignoring unknown argument ($1)" | |||
shift | |||
;; | |||
esac | |||
done | |||
if [[ $RUN_SCRIPT = 0 ]]; then | |||
echo "Re-run this command with the --run option to confirm that you really want to run this script." | |||
echo "The changes this script makes are irreversible." | |||
exit 1 | |||
fi | |||
if [[ -z ${OUTPUT_DIRECTORY} ]]; then | |||
echo "Output directory not specified, see -o" | |||
exit 1 | |||
fi | |||
DIRECTORY=$(readlink -f "${DIRECTORY}") | |||
OUTPUT_DIRECTORY=$(readlink -f "${OUTPUT_DIRECTORY}") | |||
if [[ $DIRECTORY != $OUTPUT_DIRECTORY ]]; then | |||
mkdir -p "${OUTPUT_DIRECTORY}" | |||
cp -r -f "${DIRECTORY}/." -t "${OUTPUT_DIRECTORY}" | |||
DIRECTORY=${OUTPUT_DIRECTORY} | |||
fi | |||
if [[ ! -e ${DIRECTORY}/patches.h ]]; then | |||
printf "No patches.h file found. Make sure you run this script within a flexipatch source directory." | |||
exit 1 | |||
fi | |||
FILES_TO_DELETE=$(find $DIRECTORY -name "*.c" -o -name "*.h" | awk -v DEBUG="$DEBUG" -v DIRECTORY="$DIRECTORY" ' | |||
function istrue(f) { | |||
ret = 0 | |||
for ( i = 2; i in f; i++ ) { | |||
if ( f[i] == "||" ) { | |||
if ( ret == -1 ) { | |||
ret = 0 | |||
} else if ( ret == 1 ) { | |||
break | |||
} | |||
continue | |||
} else if ( f[i] == "&&" ) { | |||
if ( ret == 0 ) { | |||
ret = -1 | |||
} | |||
continue | |||
} else if ( ret == -1 ) { | |||
continue | |||
} else if ( f[i] !~ /_(PATCH|LAYOUT)$/ ) { | |||
ret = 1 | |||
} else if ( f[i] ~ /^!/ ) { | |||
ret = !patches[substr(f[i],2)] | |||
} else { | |||
ret = patches[f[i]] | |||
} | |||
} | |||
if ( ret == -1 ) { | |||
ret = 0 | |||
} | |||
return ret | |||
} | |||
function schedule_delete(file) { | |||
# Skip duplicates | |||
for ( i = 1; i in files_to_delete; i++ ) { | |||
if ( files_to_delete[i] == file) { | |||
return | |||
} | |||
} | |||
if (DEBUG) { | |||
print "Scheduling file " file " for deletion." > "/dev/stderr" | |||
} | |||
files_to_delete[i] = file | |||
} | |||
function is_flexipatch(patch) { | |||
return patch ~ /_(PATCH|LAYOUT)$/ | |||
} | |||
BEGIN { | |||
# Read patches.h and store patch settings in the patches associative array | |||
if (DEBUG) { | |||
print "Reading file " DIRECTORY "/patches.h" > "/dev/stderr" | |||
} | |||
while (( getline line < (DIRECTORY"/patches.h") ) > 0 ) { | |||
split(line,f) | |||
if ( f[1] ~ /^#define$/ ) { | |||
if ( f[3] == 0 || f[3] == 1 ) { | |||
patches[f[2]] = f[3] | |||
} else { | |||
patches[f[2]] = istrue(f) | |||
} | |||
if (DEBUG) { | |||
print "Found " f[2] " = " patches[f[2]] > "/dev/stderr" | |||
} | |||
} | |||
} | |||
files_to_delete[0] = "" | |||
} | |||
{ | |||
level = 0 | |||
do_print[level] = 1 | |||
has_printed[level] = 0 | |||
condition[level] = "" | |||
while (( getline line < $0) > 0 ) { | |||
split(line,f) | |||
if ( f[1] ~ /^#if$/ ) { | |||
level++; | |||
do_print[level] = do_print[level-1] | |||
has_printed[level] = 0 | |||
condition[level] = f[2] | |||
if ( do_print[level] ) { | |||
if ( istrue(f) ) { | |||
has_printed[level] = 1 | |||
do_print[level] = 1 | |||
} else { | |||
do_print[level] = 0 | |||
} | |||
} | |||
if ( is_flexipatch(condition[level]) ) { | |||
continue | |||
} | |||
} else if ( f[1] ~ /^#ifdef$/ || f[1] ~ /^#ifndef$/ ) { | |||
level++; | |||
do_print[level] = do_print[level-1] | |||
has_printed[level] = 0 | |||
condition[level] = f[2] | |||
if ( do_print[level] ) { | |||
has_printed[level] = 1 | |||
do_print[level] = 1 | |||
} | |||
if ( is_flexipatch(condition[level]) ) { | |||
continue | |||
} | |||
} else if ( f[1] ~ /^#elif$/ ) { | |||
if ( (!is_flexipatch(condition[level]) || has_printed[level] == 0) && do_print[level-1] == 1 ) { | |||
if ( istrue(f) ) { | |||
has_printed[level] = 1 | |||
do_print[level] = 1 | |||
} else { | |||
do_print[level] = 0 | |||
} | |||
} else { | |||
do_print[level] = 0 | |||
} | |||
if ( is_flexipatch(f[2]) ) { | |||
continue | |||
} | |||
} else if ( f[1] ~ /^#else$/ ) { | |||
if ( (!is_flexipatch(condition[level]) || has_printed[level] == 0) && do_print[level-1] == 1 ) { | |||
has_printed[level] = 1 | |||
do_print[level] = 1 | |||
} else { | |||
do_print[level] = 0 | |||
} | |||
if ( is_flexipatch(condition[level]) ) { | |||
continue | |||
} | |||
} else if ( f[1] ~ /^#include$/ && f[2] ~ /^"/ && (do_print[level] == 0 || f[2] == "\"patches.h\"") ) { | |||
dir = "" | |||
if ( $0 ~ /\// ) { | |||
dir = $0 | |||
sub("/[^/]+$", "/", dir) | |||
} | |||
schedule_delete(dir substr(f[2], 2, length(f[2]) - 2)) | |||
continue | |||
} else if ( f[1] ~ /^#endif$/ ) { | |||
if ( is_flexipatch(condition[level]) ) { | |||
level-- | |||
continue | |||
} | |||
level-- | |||
} | |||
if ( do_print[level] ) { | |||
print line > $0 ".~" | |||
} | |||
} | |||
} | |||
END { | |||
for ( i = 1; i in files_to_delete; i++ ) { | |||
print files_to_delete[i] | |||
} | |||
} | |||
') | |||
# Chmod and replace files | |||
for FILE in $(find $DIRECTORY -name "*.~"); do | |||
chmod --reference=${FILE%%.~} ${FILE} | |||
if [[ $KEEP_FILES = 0 ]] || [[ $ECHO_COMMANDS = 1 ]]; then | |||
if [[ $ECHO_COMMANDS = 1 ]]; then | |||
echo "mv ${FILE} ${FILE%%.~}" | |||
else | |||
mv ${FILE} ${FILE%%.~} | |||
fi | |||
fi | |||
done | |||
# Delete unnecessary files | |||
if [[ $KEEP_FILES = 0 ]] || [[ $ECHO_COMMANDS = 1 ]]; then | |||
# Remove dwmc shell script if patch not enabled | |||
if [[ -f ${DIRECTORY}/patch/dwmc ]] && [[ $(grep -cE '^#define DWMC_PATCH +0 *$' ${DIRECTORY}/patches.h) > 0 ]]; then | |||
if [[ $ECHO_COMMANDS = 1 ]]; then | |||
echo "rm ${DIRECTORY}/patch/dwmc" | |||
echo "sed -r -i -e '/cp -f patch\/dwmc/d' \"${DIRECTORY}/Makefile\"" | |||
else | |||
rm "${DIRECTORY}/patch/dwmc" | |||
sed -r -i -e '/cp -f patch\/dwmc/d' "${DIRECTORY}/Makefile" | |||
fi | |||
fi | |||
# Remove layoutmenu.sh shell script if patch not enabled | |||
if [[ -f ${DIRECTORY}/patch/layoutmenu.sh ]] && [[ $(grep -cE '^#define BAR_LAYOUTMENU_PATCH +0 *$' ${DIRECTORY}/patches.h) > 0 ]]; then | |||
if [[ $ECHO_COMMANDS = 1 ]]; then | |||
echo "rm ${DIRECTORY}/patch/layoutmenu.sh" | |||
else | |||
rm "${DIRECTORY}/patch/layoutmenu.sh" | |||
fi | |||
fi | |||
for FILE in $FILES_TO_DELETE ${DIRECTORY}/patches.def.h; do | |||
if [[ $ECHO_COMMANDS = 1 ]]; then | |||
echo "rm $FILE" | |||
else | |||
rm "$FILE" | |||
fi | |||
done | |||
if [[ -f $DIRECTORY/README.md ]]; then | |||
if [[ $ECHO_COMMANDS = 1 ]]; then | |||
echo "rm $DIRECTORY/README.md" | |||
else | |||
rm $DIRECTORY/README.md | |||
fi | |||
fi | |||
if [[ $KEEP_GITFILES = 0 ]]; then | |||
rm -rf $DIRECTORY/.git* | |||
fi | |||
# Remove empty include files | |||
INCLUDE_RE='*patch/*include.[hc]' | |||
if [[ $ECHO_COMMANDS = 1 ]]; then | |||
INCLUDE_RE='*patch/*include.[hc][.]~' | |||
fi | |||
for FILE in $(find $DIRECTORY -path "$INCLUDE_RE"); do | |||
if [[ $(grep -c "#include " $FILE) = 0 ]]; then | |||
if [[ $ECHO_COMMANDS = 1 ]]; then | |||
echo "rm ${FILE%%.~}" | |||
else | |||
rm "$FILE" | |||
fi | |||
for LINE in $(grep -Ern "#include \"patch/$(basename ${FILE%%.~})\"" $DIRECTORY | grep -v '.~:' | awk -F":" '{print $1 ":" $2 }'); do | |||
INCFILE=$(echo $LINE | cut -d":" -f1) | |||
LINE_NO=$(echo $LINE | cut -d":" -f2) | |||
if [[ $ECHO_COMMANDS = 1 ]]; then | |||
echo "sed -i \"${LINE_NO}d\" ${INCFILE}" | |||
else | |||
sed -i "${LINE_NO}d" ${INCFILE} | |||
fi | |||
done | |||
fi | |||
done | |||
fi | |||
# Clean up the Makefile | |||
sed -r -i -e 's/ patches.h$//' -e '/^patches.h:$/{N;N;d;}' "${DIRECTORY}/Makefile" |
@ -0,0 +1,132 @@ | |||
#include <X11/XF86keysym.h> | |||
/* key definitions */ | |||
#define MODKEY Mod4Mask | |||
#define TAGKEYS(KEY,TAG) \ | |||
{ MODKEY, KEY, view, {.ui = 1 << TAG} }, \ | |||
{ MODKEY|ControlMask, KEY, toggleview, {.ui = 1 << TAG} }, \ | |||
{ MODKEY|ShiftMask, KEY, tag, {.ui = 1 << TAG} }, \ | |||
{ MODKEY|ControlMask|ShiftMask, KEY, toggletag, {.ui = 1 << TAG} }, | |||
/* helper for spawning shell commands in the pre dwm-5.0 fashion */ | |||
#define SHCMD(cmd) { .v = (const char*[]){ "/bin/sh", "-c", cmd, NULL } } | |||
static char dmenumon[2] = "0"; | |||
static const char *dmenucmd[] = { "dmenu_run", "-m", dmenumon, "-z", "1900", "-x", "10", "-y", "10"}; | |||
static const char *termcmd[] = { "/usr/local/bin/st", NULL }; | |||
static const char *upvol[] = { "/home/yigit/.scripts/pacontrol.sh", "up", NULL }; | |||
static const char *downvol[] = { "/home/yigit/.scripts/pacontrol.sh", "down", NULL }; | |||
static const char *mutevol[] = { "/home/yigit/.scripts/pacontrol.sh", "togglemute", NULL }; | |||
static const char *upbright[] = {"/usr/bin/xbacklight","-inc","10",NULL}; | |||
static const char *downbright[] = {"/usr/bin/xbacklight","-dec","10",NULL}; | |||
static const char *lock[] = {"/usr/bin/betterlockscreen","-l","-t","Stay the fuck out!",NULL}; | |||
static const char *clipmenu[] = {"/usr/bin/clipmenu","-i",NULL}; | |||
static const char *play[] = {"/usr/bin/playerctl","play-pause",NULL}; | |||
static const char *prev[] = {"/usr/bin/playerctl","previous",NULL}; | |||
static const char *next[] = {"/usr/bin/playerctl","next",NULL}; | |||
static const char *outmenu[] = {"/home/yigit/.scripts/dmenu-logout"}; | |||
static const char *notification_off[] = {"/home/yigit/.scripts/dunst_toggle.sh","-s",NULL}; | |||
static const char *notification_on[] = {"/home/yigit/.scripts/dunst_toggle.sh", "-e",NULL}; | |||
static const char *bwmenu[] = {"/usr/bin/bwmenu", "--auto-lock", "-1", NULL}; | |||
static const char *network_manager[] = {"/home/yigit/.scripts/networkmanager_dmenu"}; | |||
static const char *trackpad[] = {"/home/yigit/.scripts/toggle_touchpad.sh"}; | |||
static const char *kdeconnect[] = {"/home/yigit/.local/bin/dmenu_kdeconnect.sh", NULL}; | |||
static const char *bluetooth[] = {"/home/yigit/.scripts/dmenu-bluetooth", NULL}; | |||
static const char *screenshot[] = { "scrot","-d","3", "%Y-%m-%d-%s_$wx$h.jpg", "-e","xclip -selection clipboard -t image/jpg < $f; mv $f ~/Pictures/Screenshots/;dunstify --icon='/home/yigit/.icons/Numix-Circle/48/apps/camera.svg' -a 'SNAP' 'Screenshot taken'", NULL }; | |||
static const char *windowshot[] = { "scrot", "-u", "-d","3", "%Y-%m-%d-%s_$wx$h.jpg", "-e","xclip -selection clipboard -t image/jpg < $f; mv $f ~/Pictures/Screenshots/;dunstify --icon='/home/yigit/.icons/Numix-Circle/48/apps/camera.svg' -a 'SNAP' 'Screenshot taken'", NULL }; | |||
/* commands */ | |||
static Key keys[] = { | |||
/* modifier key function argument */ | |||
{ MODKEY, XK_d, spawn, {.v = dmenucmd } }, | |||
{ MODKEY, XK_p, spawn, {.v = bwmenu } }, | |||
{ MODKEY, XK_Return, spawn, {.v = termcmd } }, | |||
{ MODKEY, XK_b, togglebar, {0} }, | |||
{ MODKEY, XK_j, focusstack, {.i = +1 } }, | |||
{ MODKEY, XK_n, spawn, {.v = notification_off} }, | |||
{ MODKEY|ShiftMask, XK_n, spawn, {.v = notification_on } }, | |||
{ MODKEY, XK_k, focusstack, {.i = -1 } }, | |||
{ MODKEY, XK_i, incnmaster, {.i = +1 } }, | |||
{ MODKEY, XK_s, incnmaster, {.i = -1 } }, | |||
{ MODKEY, XK_h, setmfact, {.f = -0.05} }, | |||
{ MODKEY, XK_l, setmfact, {.f = +0.05} }, | |||
{ MODKEY|ShiftMask, XK_Return, zoom, {0} }, | |||
{ MODKEY, XK_Tab, view, {0} }, | |||
{ MODKEY, XK_q, killclient, {0} }, | |||
{ MODKEY|ShiftMask, XK_j, movestack, {.i = +1 } }, | |||
{ MODKEY|ShiftMask, XK_k, movestack, {.i = -1 } }, | |||
{ MODKEY, XK_t, setlayout, {.v = &layouts[0]} }, /*tiled*/ | |||
{ MODKEY|Mod1Mask, XK_f, setlayout, {.v = &layouts[1]} }, /*Spiral*/ | |||
{ MODKEY|Mod1Mask, XK_g, setlayout, {.v = &layouts[2]} }, /*Grid*/ | |||
{ MODKEY|Mod1Mask, XK_c, setlayout, {.v = &layouts[3]} }, /*center*/ | |||
{ MODKEY, XK_m, setlayout, {.v = &layouts[5]} }, /*monocle*/ | |||
{ MODKEY|ShiftMask, XK_m, setlayout, {.v = &layouts[6]} }, /*Deck*/ | |||
// { MODKEY, XK_s, togglefloating, {0} }, [>float<] | |||
// { MODKEY, XK_f, togglefullscr, {0} }, [>Fullscreen<] | |||
{ MODKEY|Mod1Mask, XK_comma, cyclelayout, {.i = -1 } }, /*Ciclar layouts*/ | |||
{ MODKEY|Mod1Mask, XK_period, cyclelayout, {.i = +1 } }, /*Ciclar layouts*/ | |||
{ MODKEY, XK_a, view, {.ui = ~0 } }, | |||
{ MODKEY|ShiftMask, XK_a, tag, {.ui = ~0 } }, | |||
{ MODKEY, XK_comma, focusmon, {.i = -1 } }, | |||
{ MODKEY, XK_period, focusmon, {.i = +1 } }, | |||
{ MODKEY|ShiftMask, XK_comma, tagmon, {.i = -1 } }, | |||
{ MODKEY|ShiftMask, XK_period, tagmon, {.i = +1 } }, | |||
{ MODKEY|ShiftMask, XK_comma, tagmon, {.i = -1 } }, | |||
{ MODKEY|ShiftMask, XK_period, tagmon, {.i = +1 } }, | |||
TAGKEYS( XK_1, 0) | |||
TAGKEYS( XK_2, 1) | |||
TAGKEYS( XK_3, 2) | |||
TAGKEYS( XK_4, 3) | |||
TAGKEYS( XK_5, 4) | |||
TAGKEYS( XK_6, 5) | |||
TAGKEYS( XK_7, 6) | |||
TAGKEYS( XK_8, 7) | |||
TAGKEYS( XK_9, 8) | |||
TAGKEYS( XK_0, 9) | |||
{ MODKEY|ShiftMask, XK_q, spawn, {.v = outmenu} }, | |||
{ MODKEY|ShiftMask, XK_t, spawn, {.v = trackpad} }, | |||
{ MODKEY, XK_x, spawn, {.v = lock } }, | |||
{ MODKEY, XK_c, spawn, {.v = clipmenu } }, | |||
{ MODKEY|ShiftMask, XK_p, spawn, {.v = kdeconnect } }, | |||
{ MODKEY|ShiftMask, XK_b, spawn, {.v = bluetooth } }, | |||
{ MODKEY|Mod1Mask, XK_n, spawn, {.v = network_manager} }, /*Spiral*/ | |||
{ 0, XF86XK_AudioLowerVolume, spawn, {.v = downvol } }, | |||
{ 0, XF86XK_MonBrightnessUp, spawn, {.v = upbright } }, | |||
{ 0, XF86XK_MonBrightnessDown, spawn, {.v = downbright } }, | |||
{ 0, XF86XK_AudioMute, spawn, {.v = mutevol } }, | |||
{ 0, XF86XK_AudioRaiseVolume, spawn, {.v = upvol } }, | |||
{ 0, XF86XK_AudioPrev, spawn, {.v = prev } }, | |||
{ 0, XF86XK_AudioPlay, spawn, {.v = play } }, | |||
{ 0, XF86XK_AudioNext, spawn, {.v = next } }, | |||
{ 0, XK_Print, spawn, {.v = screenshot } }, | |||
{ MODKEY, XK_Print, spawn, {.v = windowshot } }, | |||
}; | |||
/* button definitions */ | |||
/* click can be ClkTagBar, ClkLtSymbol, ClkStatusText, ClkWinTitle, ClkClientWin, or ClkRootWin */ | |||
static Button buttons[] = { | |||
/* click event mask button function argument */ | |||
{ ClkLtSymbol, 0, Button1, setlayout, {0} }, | |||
{ ClkLtSymbol, 0, Button3, setlayout, {.v = &layouts[2]} }, | |||
{ ClkClientWin, MODKEY, Button1, movemouse, {0} }, | |||
{ ClkClientWin, MODKEY, Button2, togglefloating, {0} }, | |||
{ ClkClientWin, MODKEY, Button3, resizemouse, {0} }, | |||
{ ClkTagBar, 0, Button1, view, {0} }, | |||
{ ClkTagBar, 0, Button3, toggleview, {0} }, | |||
{ ClkTagBar, MODKEY, Button1, tag, {0} }, | |||
{ ClkTagBar, MODKEY, Button3, toggletag, {0} }, | |||
{ ClkStatusText, 0, Button1, sigdwmblocks, {.i = 1 } }, | |||
{ ClkStatusText, 0, Button2, sigdwmblocks, {.i = 2 } }, | |||
{ ClkStatusText, 0, Button3, sigdwmblocks, {.i = 3 } }, | |||
{ ClkLtSymbol, 0, Button3, layoutmenu, {0} }, | |||
}; | |||
@ -1,27 +0,0 @@ | |||
void | |||
grid(Monitor *m) { | |||
unsigned int i, n, cx, cy, cw, ch, aw, ah, cols, rows; | |||
Client *c; | |||
for(n = 0, c = nexttiled(m->clients); c; c = nexttiled(c->next)) | |||
n++; | |||
/* grid dimensions */ | |||
for(rows = 0; rows <= n/2; rows++) | |||
if(rows*rows >= n) | |||
break; | |||
cols = (rows && (rows - 1) * rows >= n) ? rows - 1 : rows; | |||
/* window geoms (cell height/width) */ | |||
ch = m->wh / (rows ? rows : 1); | |||
cw = m->ww / (cols ? cols : 1); | |||
for(i = 0, c = nexttiled(m->clients); c; c = nexttiled(c->next)) { | |||
cx = m->wx + (i / rows) * cw; | |||
cy = m->wy + (i % rows) * ch; | |||
/* adjust height/width of last row/column's windows */ | |||
ah = ((i + 1) % rows == 0) ? m->wh - ch * rows : 0; | |||
aw = (i >= rows * (cols - 1)) ? m->ww - cw * cols : 0; | |||
resize(c, cx, cy, cw - 2 * c->bw + aw, ch - 2 * c->bw + ah, False); | |||
i++; | |||
} | |||
} |
@ -0,0 +1,24 @@ | |||
void | |||
aspectresize(const Arg *arg) | |||
{ | |||
/* only floating windows can be moved */ | |||
Client *c; | |||
c = selmon->sel; | |||
float ratio; | |||
int w, h,nw, nh; | |||
if (!c || !arg) | |||
return; | |||
if (selmon->lt[selmon->sellt]->arrange && !c->isfloating) | |||
return; | |||
ratio = (float)c->w / (float)c->h; | |||
h = arg->i; | |||
w = (int)(ratio * h); | |||
nw = c->w + w; | |||
nh = c->h + h; | |||
XRaiseWindow(dpy, c->win); | |||
resize(c, c->x, c->y, nw, nh, True); | |||
} |
@ -0,0 +1 @@ | |||
static void aspectresize(const Arg *arg); |
@ -0,0 +1,42 @@ | |||
void | |||
attachx(Client *c) | |||
{ | |||
#if ATTACHABOVE_PATCH | |||
Client *at; | |||
if (!(c->mon->sel == NULL || c->mon->sel == c->mon->clients || c->mon->sel->isfloating)) { | |||
for (at = c->mon->clients; at->next != c->mon->sel; at = at->next); | |||
c->next = at->next; | |||
at->next = c; | |||
return; | |||
} | |||
#elif ATTACHASIDE_PATCH | |||
Client *at; | |||
unsigned int n; | |||
for (at = c->mon->clients, n = 0; at; at = at->next) | |||
if (!at->isfloating && ISVISIBLEONTAG(at, c->tags)) | |||
if (++n >= c->mon->nmaster) | |||
break; | |||
if (at && c->mon->nmaster) { | |||
c->next = at->next; | |||
at->next = c; | |||
return; | |||
} | |||
#elif ATTACHBELOW_PATCH | |||
if (!(c->mon->sel == NULL || c->mon->sel == c || c->mon->sel->isfloating)) { | |||
c->next = c->mon->sel->next; | |||
c->mon->sel->next = c; | |||
return; | |||
} | |||
#elif ATTACHBOTTOM_PATCH | |||
Client *at; | |||
for (at = c->mon->clients; at && at->next; at = at->next); | |||
if (at) { | |||
at->next = c; | |||
c->next = NULL; | |||
return; | |||
} | |||
#endif | |||
attach(c); // master (default) | |||
} |
@ -0,0 +1 @@ | |||
static void attachx(Client *c); |
@ -0,0 +1,83 @@ | |||
void | |||
runautostart(void) | |||
{ | |||
char *pathpfx; | |||
char *path; | |||
char *xdgdatahome; | |||
char *home; | |||
if ((home = getenv("HOME")) == NULL) | |||
/* this is almost impossible */ | |||
return; | |||
/* if $XDG_DATA_HOME is defined, use $XDG_DATA_HOME/dwm, | |||
* otherwise use ~/.local/share/dwm as autostart script directory | |||
*/ | |||
if ((xdgdatahome = getenv("XDG_DATA_HOME")) != NULL) { | |||
/* space for path segments, separators and nul */ | |||
if ((pathpfx = malloc(strlen(xdgdatahome) + strlen(dwmdir) + 2)) == NULL) | |||
return; | |||
if (sprintf(pathpfx, "%s/%s", xdgdatahome, dwmdir) <= 0) { | |||
free(pathpfx); | |||
return; | |||
} | |||
} else { | |||
/* space for path segments, separators and nul */ | |||
if ((pathpfx = malloc(strlen(home) + strlen(localshare) + strlen(dwmdir) + 3)) == NULL) | |||
return; | |||
if (sprintf(pathpfx, "%s/%s/%s", home, localshare, dwmdir) < 0) { | |||
free(pathpfx); | |||
return; | |||
} | |||
} | |||
/* check if the autostart script directory exists */ | |||
struct stat sb; | |||
if (! (stat(pathpfx, &sb) == 0 && S_ISDIR(sb.st_mode))) { | |||
/* the XDG conformant path does not exist or are not directories | |||
* so we try ~/.dwm instead | |||
*/ | |||
if (realloc(pathpfx, strlen(home) + strlen(dwmdir) + 3) == NULL) { | |||
free(pathpfx); | |||
return; | |||
} | |||
if (sprintf(pathpfx, "%s/.%s", home, dwmdir) <= 0) { | |||
free(pathpfx); | |||
return; | |||
} | |||
} | |||
/* try the blocking script first */ | |||
if ((path = malloc(strlen(pathpfx) + strlen(autostartblocksh) + 2)) == NULL) { | |||
free(pathpfx); | |||
return; | |||
} else | |||
if (sprintf(path, "%s/%s", pathpfx, autostartblocksh) <= 0) { | |||
free(path); | |||
free(pathpfx); | |||
} | |||
if (access(path, X_OK) == 0) | |||
system(path); | |||
/* now the non-blocking script */ | |||
if ((path = realloc(path, strlen(pathpfx) + strlen(autostartsh) + 4)) == NULL) { | |||
free(pathpfx); | |||
free(path); | |||
return; | |||
} else | |||
if (sprintf(path, "%s/%s", pathpfx, autostartsh) <= 0) { | |||
free(path); | |||
free(pathpfx); | |||
} | |||
if (access(path, X_OK) == 0) { | |||
system(strcat(path, " &")); | |||
free(pathpfx); | |||
free(path); | |||
} | |||
} |
@ -0,0 +1 @@ | |||
static void runautostart(void); |
@ -0,0 +1,42 @@ | |||
static int useargb = 0; | |||
static Visual *visual; | |||
static int depth; | |||
static Colormap cmap; | |||
void | |||
xinitvisual() | |||
{ | |||
XVisualInfo *infos; | |||
XRenderPictFormat *fmt; | |||
int nitems; | |||
int i; | |||
XVisualInfo tpl = { | |||
.screen = screen, | |||
.depth = 32, | |||
.class = TrueColor | |||
}; | |||
long masks = VisualScreenMask | VisualDepthMask | VisualClassMask; | |||
infos = XGetVisualInfo(dpy, masks, &tpl, &nitems); | |||
visual = NULL; | |||
for (i = 0; i < nitems; i ++) { | |||
fmt = XRenderFindVisualFormat(dpy, infos[i].visual); | |||
if (fmt->type == PictTypeDirect && fmt->direct.alphaMask) { | |||
visual = infos[i].visual; | |||
depth = infos[i].depth; | |||
cmap = XCreateColormap(dpy, root, visual, AllocNone); | |||
useargb = 1; | |||
break; | |||
} | |||
} | |||
XFree(infos); | |||
if (!visual) { | |||
visual = DefaultVisual(dpy, screen); | |||
depth = DefaultDepth(dpy, screen); | |||
cmap = DefaultColormap(dpy, screen); | |||
} | |||
} |
@ -0,0 +1,3 @@ | |||
#define OPAQUE 0xffU | |||
static void xinitvisual(); |
@ -0,0 +1,6 @@ | |||
void | |||
togglealttag() | |||
{ | |||
selmon->alttag = !selmon->alttag; | |||
drawbar(selmon); | |||
} |
@ -0,0 +1 @@ | |||
static void togglealttag(); |
@ -0,0 +1,82 @@ | |||
void | |||
managealtbar(Window win, XWindowAttributes *wa) | |||
{ | |||
Monitor *m; | |||
Bar *bar; | |||
int i; | |||
if (!(m = recttomon(wa->x, wa->y, wa->width, wa->height))) | |||
return; | |||
for (i = 0, bar = m->bar; bar && bar->win && bar->next; bar = bar->next, ++i); // find last bar | |||
if (!bar) { | |||
bar = m->bar = ecalloc(1, sizeof(Bar)); | |||
bar->topbar = topbar; | |||
} else if (bar && bar->win) { | |||
bar->next = ecalloc(1, sizeof(Bar)); | |||
bar->next->topbar = !bar->topbar; | |||
bar = bar->next; | |||
} | |||
bar->external = 1; | |||
bar->showbar = 1; | |||
bar->mon = m; | |||
bar->idx = i; | |||
bar->borderpx = 0; | |||
bar->win = win; | |||
bar->bh = wa->height; | |||
updatebarpos(m); | |||
arrange(m); | |||
XSelectInput(dpy, win, EnterWindowMask|FocusChangeMask|PropertyChangeMask|StructureNotifyMask); | |||
XMapWindow(dpy, win); | |||
XMoveResizeWindow(dpy, bar->win, bar->bx, -bar->by, wa->width, bar->bh); | |||
arrange(selmon); | |||
XChangeProperty(dpy, root, netatom[NetClientList], XA_WINDOW, 32, PropModeAppend, | |||
(unsigned char *) &win, 1); | |||
} | |||
void | |||
spawnbar() | |||
{ | |||
if (*altbarcmd) | |||
system(altbarcmd); | |||
} | |||
void | |||
unmanagealtbar(Window w) | |||
{ | |||
Monitor *m = wintomon(w); | |||
Bar *bar; | |||
if (!m) | |||
return; | |||
for (bar = m->bar; bar && bar->win; bar = bar->next) | |||
if (bar->win == w) { | |||
bar->win = 0; | |||
bar->by = 0; | |||
bar->bh = 0; | |||
break; | |||
} | |||
updatebarpos(m); | |||
arrange(m); | |||
} | |||
int | |||
wmclasscontains(Window win, const char *class, const char *name) | |||
{ | |||
XClassHint ch = { NULL, NULL }; | |||
int res = 1; | |||
if (XGetClassHint(dpy, win, &ch)) { | |||
if (ch.res_name && strstr(ch.res_name, name) == NULL) | |||
res = 0; | |||
if (ch.res_class && strstr(ch.res_class, class) == NULL) | |||
res = 0; | |||
} else | |||
res = 0; | |||
if (ch.res_class) | |||
XFree(ch.res_class); | |||
if (ch.res_name) | |||
XFree(ch.res_name); | |||
return res; | |||
} |
@ -0,0 +1,4 @@ | |||
static void managealtbar(Window win, XWindowAttributes *wa); | |||
static void spawnbar(); | |||
static void unmanagealtbar(Window w); | |||
static int wmclasscontains(Window win, const char *class, const char *name); |
@ -0,0 +1,79 @@ | |||
int | |||
width_awesomebar(Bar *bar, BarArg *a) | |||
{ | |||
return a->w; | |||
} | |||
int | |||
draw_awesomebar(Bar *bar, BarArg *a) | |||
{ | |||
int n = 0, scm, remainder = 0, tabw, pad; | |||
unsigned int i; | |||
#if BAR_TITLE_LEFT_PAD_PATCH && BAR_TITLE_RIGHT_PAD_PATCH | |||
int x = a->x + lrpad / 2, w = a->w - lrpad; | |||
#elif BAR_TITLE_LEFT_PAD_PATCH | |||
int x = a->x + lrpad / 2, w = a->w - lrpad / 2; | |||
#elif BAR_TITLE_RIGHT_PAD_PATCH | |||
int x = a->x, w = a->w - lrpad / 2; | |||
#else | |||
int x = a->x, w = a->w; | |||
#endif // BAR_TITLE_LEFT_PAD_PATCH | BAR_TITLE_RIGHT_PAD_PATCH | |||
Client *c; | |||
for (c = bar->mon->clients; c; c = c->next) | |||
if (ISVISIBLE(c)) | |||
n++; | |||
if (n > 0) { | |||
remainder = w % n; | |||
tabw = w / n; | |||
for (i = 0, c = bar->mon->clients; c; c = c->next, i++) { | |||
if (!ISVISIBLE(c)) | |||
continue; | |||
if (bar->mon->sel == c) | |||
scm = SchemeTitleSel; | |||
else if (HIDDEN(c)) | |||
scm = SchemeHid; | |||
else | |||
scm = SchemeTitleNorm; | |||
pad = lrpad / 2; | |||
#if BAR_CENTEREDWINDOWNAME_PATCH | |||
if (TEXTW(c->name) < tabw) | |||
pad = (tabw - TEXTW(c->name) + lrpad) / 2; | |||
#endif // BAR_CENTEREDWINDOWNAME_PATCH | |||
drw_setscheme(drw, scheme[scm]); | |||
drw_text(drw, x, a->y, tabw + (i < remainder ? 1 : 0), a->h, pad, c->name, 0, False); | |||
drawstateindicator(c->mon, c, 1, x, a->y, tabw + (i < remainder ? 1 : 0), a->h, 0, 0, c->isfixed); | |||
x += tabw + (i < remainder ? 1 : 0); | |||
} | |||
} | |||
return n; | |||
} | |||
int | |||
click_awesomebar(Bar *bar, Arg *arg, BarArg *a) | |||
{ | |||
int x = 0, n = 0; | |||
Client *c; | |||
for (c = bar->mon->clients; c; c = c->next) | |||
if (ISVISIBLE(c)) | |||
n++; | |||
c = bar->mon->clients; | |||
do { | |||
if (!c || !ISVISIBLE(c)) | |||
continue; | |||
else | |||
x += (1.0 / (double)n) * a->w; | |||
} while (c && a->x > x && (c = c->next)); | |||
if (c) { | |||
arg->v = c; | |||
return ClkWinTitle; | |||
} | |||
return -1; | |||
} |
@ -0,0 +1,3 @@ | |||
static int width_awesomebar(Bar *bar, BarArg *a); | |||
static int draw_awesomebar(Bar *bar, BarArg *a); | |||
static int click_awesomebar(Bar *bar, Arg *arg, BarArg *a); |
@ -0,0 +1,31 @@ | |||
static int dwmblockssig; | |||
pid_t dwmblockspid = 0; | |||
int | |||
getdwmblockspid() | |||
{ | |||
char buf[16]; | |||
FILE *fp = popen("pidof -s dwmblocks", "r"); | |||
if (fgets(buf, sizeof(buf), fp)); | |||
pid_t pid = strtoul(buf, NULL, 10); | |||
pclose(fp); | |||
dwmblockspid = pid; | |||
return pid != 0 ? 0 : -1; | |||
} | |||
void | |||
sigdwmblocks(const Arg *arg) | |||
{ | |||
union sigval sv; | |||
sv.sival_int = (dwmblockssig << 8) | arg->i; | |||
if (!dwmblockspid) | |||
if (getdwmblockspid() == -1) | |||
return; | |||
if (sigqueue(dwmblockspid, SIGUSR1, sv) == -1) { | |||
if (errno == ESRCH) { | |||
if (!getdwmblockspid()) | |||
sigqueue(dwmblockspid, SIGUSR1, sv); | |||
} | |||
} | |||
} |
@ -0,0 +1,2 @@ | |||
static int getdwmblockspid(); | |||
static void sigdwmblocks(const Arg *arg); |
@ -0,0 +1,52 @@ | |||
void | |||
setcurrentdesktop(void) | |||
{ | |||
long data[] = { 0 }; | |||
XChangeProperty(dpy, root, netatom[NetCurrentDesktop], XA_CARDINAL, 32, PropModeReplace, (unsigned char *)data, 1); | |||
} | |||
void | |||
setdesktopnames(void) | |||
{ | |||
int i; | |||
XTextProperty text; | |||
char *tags[NUMTAGS]; | |||
for (i = 0; i < NUMTAGS; i++) | |||
tags[i] = tagicon(selmon, i); | |||
Xutf8TextListToTextProperty(dpy, tags, NUMTAGS, XUTF8StringStyle, &text); | |||
XSetTextProperty(dpy, root, &text, netatom[NetDesktopNames]); | |||
} | |||
void | |||
setfloatinghint(Client *c) | |||
{ | |||
Atom target = XInternAtom(dpy, "_IS_FLOATING", 0); | |||
unsigned int floating[1] = {c->isfloating}; | |||
XChangeProperty(dpy, c->win, target, XA_CARDINAL, 32, PropModeReplace, (unsigned char *)floating, 1); | |||
} | |||
void | |||
setnumdesktops(void) | |||
{ | |||
long data[] = { NUMTAGS }; | |||
XChangeProperty(dpy, root, netatom[NetNumberOfDesktops], XA_CARDINAL, 32, PropModeReplace, (unsigned char *)data, 1); | |||
} | |||
void | |||
setviewport(void) | |||
{ | |||
long data[] = { 0, 0 }; | |||
XChangeProperty(dpy, root, netatom[NetDesktopViewport], XA_CARDINAL, 32, PropModeReplace, (unsigned char *)data, 2); | |||
} | |||
void | |||
updatecurrentdesktop(void) | |||
{ | |||
long rawdata[] = { selmon->tagset[selmon->seltags] }; | |||
int i = 0; | |||
while (*rawdata >> (i + 1)) { | |||
i++; | |||
} | |||
long data[] = { i }; | |||
XChangeProperty(dpy, root, netatom[NetCurrentDesktop], XA_CARDINAL, 32, PropModeReplace, (unsigned char *)data, 1); | |||
} |
@ -0,0 +1,6 @@ | |||
static void setcurrentdesktop(void); | |||
static void setdesktopnames(void); | |||
static void setfloatinghint(Client *c); | |||
static void setnumdesktops(void); | |||
static void setviewport(void); | |||
static void updatecurrentdesktop(void); |
@ -0,0 +1,70 @@ | |||
int | |||
width_fancybar(Bar *bar, BarArg *a) | |||
{ | |||
return a->w; | |||
} | |||
int | |||
draw_fancybar(Bar *bar, BarArg *a) | |||
{ | |||
int ftw, mw, ew = 0, n = 0; | |||
unsigned int i; | |||
Client *c; | |||
Monitor *m = bar->mon; | |||
#if BAR_TITLE_LEFT_PAD_PATCH && BAR_TITLE_RIGHT_PAD_PATCH | |||
int x = a->x + lrpad / 2, w = a->w - lrpad; | |||
#elif BAR_TITLE_LEFT_PAD_PATCH | |||
int x = a->x + lrpad / 2, w = a->w - lrpad / 2; | |||
#elif BAR_TITLE_RIGHT_PAD_PATCH | |||
int x = a->x, w = a->w - lrpad / 2; | |||
#else | |||
int x = a->x, w = a->w; | |||
#endif // BAR_TITLE_LEFT_PAD_PATCH | BAR_TITLE_RIGHT_PAD_PATCH | |||
for (c = m->clients; c; c = c->next) { | |||
if (ISVISIBLE(c)) | |||
n++; | |||
} | |||
if (n > 0) { | |||
ftw = TEXTW(m->sel->name); | |||
mw = (ftw >= w || n == 1) ? 0 : (w - ftw) / (n - 1); | |||
i = 0; | |||
for (c = m->clients; c; c = c->next) { | |||
if (!ISVISIBLE(c) || c == m->sel) | |||
continue; | |||
ftw = TEXTW(c->name); | |||
if (ftw < mw) | |||
ew += (mw - ftw); | |||
else | |||
i++; | |||
} | |||
if (i > 0) | |||
mw += ew / i; | |||
for (c = m->clients; c; c = c->next) { | |||
if (!ISVISIBLE(c)) | |||
continue; | |||
ftw = MIN(m->sel == c ? w : mw, TEXTW(c->name)); | |||
drw_setscheme(drw, scheme[m->sel == c ? SchemeTitleSel : SchemeTitleNorm]); | |||
if (ftw > 0) /* trap special handling of 0 in drw_text */ | |||
drw_text(drw, x, a->y, ftw, a->h, lrpad / 2, c->name, 0, False); | |||
drawstateindicator(c->mon, c, 1, x, a->y, ftw, a->h, 0, 0, c->isfixed); | |||
x += ftw; | |||
w -= ftw; | |||
} | |||
} | |||
return n; | |||
} | |||
int | |||
click_fancybar(Bar *bar, Arg *arg, BarArg *a) | |||
{ | |||
return ClkWinTitle; | |||
} | |||
@ -0,0 +1,3 @@ | |||
static int width_fancybar(Bar *bar, BarArg *a); | |||
static int draw_fancybar(Bar *bar, BarArg *a); | |||
static int click_fancybar(Bar *bar, Arg *arg, BarArg *a); |
@ -0,0 +1,436 @@ | |||
/* Flexwintitle properties, you can override these in your config.h if you want. */ | |||
#ifndef FLEXWINTITLE_BORDERS | |||
#define FLEXWINTITLE_BORDERS 1 // 0 = off, 1 = on | |||
#endif | |||
#ifndef FLEXWINTITLE_SHOWFLOATING | |||
#define FLEXWINTITLE_SHOWFLOATING 0 // whether to show titles for floating windows, hidden clients are always shown | |||
#endif | |||
#ifndef FLEXWINTITLE_MASTERWEIGHT | |||
#define FLEXWINTITLE_MASTERWEIGHT 9 // master weight compared to stack, hidden and floating window titles | |||
#endif | |||
#ifndef FLEXWINTITLE_STACKWEIGHT | |||
#define FLEXWINTITLE_STACKWEIGHT 3 // stack weight compared to master, hidden and floating window titles | |||
#endif | |||
#ifndef FLEXWINTITLE_HIDDENWEIGHT | |||
#define FLEXWINTITLE_HIDDENWEIGHT 1 // hidden window title weight | |||
#endif | |||
#ifndef FLEXWINTITLE_FLOATWEIGHT | |||
#define FLEXWINTITLE_FLOATWEIGHT 1 // floating window title weight, set to 0 to not show floating windows | |||
#endif | |||
#define SCHEMEFOR(c) getschemefor(m, c, groupactive == c) | |||
enum { GRP_NOSELECTION, GRP_MASTER, GRP_STACK1, GRP_STACK2, GRP_FLOAT, GRP_HIDDEN }; | |||
int | |||
width_flexwintitle(Bar *bar, BarArg *a) | |||
{ | |||
return a->w; | |||
} | |||
int | |||
draw_flexwintitle(Bar *bar, BarArg *a) | |||
{ | |||
drw_rect(drw, a->x, a->y, a->w, a->h, 1, 1); | |||
return flextitlecalculate(bar->mon, a->x, a->w, -1, flextitledraw, NULL, a); | |||
} | |||
int | |||
click_flexwintitle(Bar *bar, Arg *arg, BarArg *a) | |||
{ | |||
flextitlecalculate(bar->mon, 0, a->w, a->x, flextitleclick, arg, a); | |||
return ClkWinTitle; | |||
} | |||
Client * | |||
flextitledrawarea(Monitor *m, Client *c, int x, int r, int w, int max_clients, int scheme, int draw_tiled, int draw_hidden, int draw_floating, | |||
int passx, void(*tabfn)(Monitor *, Client *, int, int, int, int, Arg *arg, BarArg *barg), Arg *arg, BarArg *barg) | |||
{ | |||
int i; | |||
for (i = 0; c && i < max_clients; c = c->next) { | |||
if ( | |||
ISVISIBLE(c) && | |||
( | |||
(draw_tiled && !c->isfloating && !HIDDEN(c)) || | |||
(draw_floating && c->isfloating && !HIDDEN(c)) || | |||
(draw_hidden && HIDDEN(c)) | |||
) | |||
) { | |||
tabfn(m, c, passx, x, w + (i < r ? 1 : 0), scheme, arg, barg); | |||
x += w + (i < r ? 1 : 0); | |||
i++; | |||
} | |||
} | |||
return c; | |||
} | |||
int | |||
getschemefor(Monitor *m, int group, int activegroup) | |||
{ | |||
switch (group) { | |||
case GRP_NOSELECTION: | |||
case GRP_MASTER: | |||
case GRP_STACK1: | |||
case GRP_STACK2: | |||
#if BSTACK_LAYOUT | |||
if (m->lt[m->sellt]->arrange == &bstack) | |||
return (activegroup ? SchemeFlexActLTR : SchemeFlexInaLTR); | |||
#endif // BSTACK_LAYOUT | |||
#if BSTACKHORIZ_LAYOUT | |||
if (m->lt[m->sellt]->arrange == &bstackhoriz) { | |||
if (group == GRP_MASTER) | |||
return (activegroup ? SchemeFlexActLTR : SchemeFlexInaLTR); | |||
else | |||
return (activegroup ? SchemeFlexActTTB : SchemeFlexInaTTB); | |||
} | |||
#endif // BSTACKHORIZ_LAYOUT | |||
#if CENTEREDMASTER_LAYOUT | |||
if (m->lt[m->sellt]->arrange == ¢eredmaster) | |||
return (activegroup ? SchemeFlexActTTB : SchemeFlexInaTTB); | |||
#endif // CENTEREDMASTER_LAYOUT | |||
#if CENTEREDFLOATINGMASTER_LAYOUT | |||
if (m->lt[m->sellt]->arrange == ¢eredfloatingmaster) | |||
return (activegroup ? SchemeFlexActLTR : SchemeFlexInaLTR); | |||
#endif // CENTEREDFLOATINGMASTER_LAYOUT | |||
#if COLUMNS_LAYOUT | |||
if (m->lt[m->sellt]->arrange == &col) { | |||
if (group == GRP_MASTER) | |||
return (activegroup ? SchemeFlexActLTR : SchemeFlexInaLTR); | |||
else | |||
return (activegroup ? SchemeFlexActTTB : SchemeFlexInaTTB); | |||
} | |||
#endif // COLUMNS_LAYOUT | |||
#if DECK_LAYOUT | |||
if (m->lt[m->sellt]->arrange == &deck) { | |||
if (group == GRP_MASTER) | |||
return (activegroup ? SchemeFlexActTTB : SchemeFlexInaTTB); | |||
else | |||
return (activegroup ? SchemeFlexActMONO : SchemeFlexInaMONO); | |||
} | |||
#endif // DECK_LAYOUT | |||
#if FIBONACCI_DWINDLE_LAYOUT | |||
if (m->lt[m->sellt]->arrange == &dwindle) | |||
return (activegroup ? SchemeFlexActDWDL : SchemeFlexInaDWDL); | |||
#endif // FIBONACCI_DWINDLE_LAYOUT | |||
#if FIBONACCI_SPIRAL_LAYOUT | |||
if (m->lt[m->sellt]->arrange == &spiral) | |||
return (activegroup ? SchemeFlexActSPRL : SchemeFlexInaSPRL); | |||
#endif // FIBONACCI_SPIRAL_LAYOUT | |||
#if FLEXTILE_DELUXE_LAYOUT | |||
if (m->lt[m->sellt]->arrange == &flextile) | |||
return (activegroup ? SchemeFlexActTTB + m->ltaxis[group] : SchemeFlexInaTTB + m->ltaxis[group]); | |||
#endif // FLEXTILE_DELUXE_LAYOUT | |||
#if GAPPLESSGRID_LAYOUT | |||
if (m->lt[m->sellt]->arrange == &gaplessgrid) | |||
return (activegroup ? SchemeFlexActGRID : SchemeFlexInaGRID); | |||
#endif // GAPPLESSGRID_LAYOUT | |||
#if GRIDMODE_LAYOUT | |||
if (m->lt[m->sellt]->arrange == &grid) | |||
return (activegroup ? SchemeFlexActGRDM : SchemeFlexInaGRDM); | |||
#endif // GRIDMODE_LAYOUT | |||
#if HORIZGRID_LAYOUT | |||
if (m->lt[m->sellt]->arrange == &horizgrid) | |||
return (activegroup ? SchemeFlexActHGRD : SchemeFlexInaHGRD); | |||
#endif // HORIZGRID_LAYOUT | |||
#if NROWGRID_LAYOUT | |||
if (m->lt[m->sellt]->arrange == &nrowgrid) | |||
return (activegroup ? SchemeFlexActGRD1 : SchemeFlexInaGRD1); | |||
#endif // NROWGRID_LAYOUT | |||
#if TILE_LAYOUT | |||
if (m->lt[m->sellt]->arrange == &tile) | |||
return (activegroup ? SchemeFlexActTTB : SchemeFlexInaTTB); | |||
#endif // TILE_LAYOUT | |||
#if MONOCLE_LAYOUT | |||
if (m->lt[m->sellt]->arrange == &monocle) | |||
return (activegroup ? SchemeFlexActMONO : SchemeFlexInaMONO); | |||
#endif // MONOCLE_LAYOUT | |||
return SchemeTitleNorm; | |||
case GRP_HIDDEN: | |||
return SchemeHid; | |||
case GRP_FLOAT: | |||
return (activegroup ? SchemeFlexActFloat : SchemeFlexInaFloat); | |||
} | |||
return SchemeTitleNorm; | |||
} | |||
int | |||
getselschemefor(int scheme) | |||
{ | |||
if (scheme == SchemeFlexActFloat || scheme == SchemeFlexInaFloat) | |||
return SchemeFlexSelFloat; | |||
if (scheme >= SchemeFlexInaTTB) | |||
return scheme + SchemeFlexInaTTB - SchemeFlexActTTB; | |||
if (scheme >= SchemeFlexActTTB) | |||
return scheme + SchemeFlexSelTTB - SchemeFlexActTTB; | |||
return SchemeTitleSel; | |||
} | |||
void | |||
flextitledraw(Monitor *m, Client *c, int unused, int x, int w, int tabscheme, Arg *arg, BarArg *barg) | |||
{ | |||
if (!c) | |||
return; | |||
int i, nclienttags = 0, nviewtags = 0, pad = lrpad / 2; | |||
int clientscheme = ( | |||
c == selmon->sel | |||
? getselschemefor(tabscheme) | |||
: HIDDEN(c) | |||
? SchemeHid | |||
: c->isurgent | |||
? SchemeUrg | |||
: tabscheme | |||
); | |||
drw_setscheme(drw, scheme[clientscheme]); | |||
XSetWindowBorder(dpy, c->win, scheme[clientscheme][ColBorder].pixel); | |||
if (w <= TEXTW("A") - lrpad + pad) // reduce text padding if wintitle is too small | |||
pad = (w - TEXTW("A") + lrpad < 0 ? 0 : (w - TEXTW("A") + lrpad) / 2); | |||
#if BAR_CENTEREDWINDOWNAME_PATCH | |||
else if (TEXTW(c->name) < w) | |||
pad = (w - TEXTW(c->name) + lrpad) / 2; | |||
#endif // BAR_CENTEREDWINDOWNAME_PATCH | |||
drw_text(drw, x, barg->y, w, barg->h, pad, c->name, 0, False); | |||
drawstateindicator(m, c, 1, x + 2, barg->y, w, barg->h, 0, 0, 0); | |||
if (FLEXWINTITLE_BORDERS) { | |||
XSetForeground(drw->dpy, drw->gc, scheme[SchemeSel][ColBorder].pixel); | |||
XFillRectangle(drw->dpy, drw->drawable, drw->gc, x, barg->y, 1, barg->h); | |||
XFillRectangle(drw->dpy, drw->drawable, drw->gc, x + w - (x + w >= barg->w ? 1 : 0), barg->y, 1, barg->h); | |||
} | |||
/* Optional tags icons */ | |||
for (i = 0; i < NUMTAGS; i++) { | |||
if ((m->tagset[m->seltags] >> i) & 1) | |||
nviewtags++; | |||
if ((c->tags >> i) & 1) | |||
nclienttags++; | |||
} | |||
if (TAGSINDICATOR == 2 || nclienttags > 1 || nviewtags > 1) | |||
drawindicator(m, c, 1, x, barg->y, w, barg->h, 0, 0, 0, INDICATOR_RIGHT_TAGS); | |||
} | |||
#ifndef HIDDEN | |||
#define HIDDEN(C) 0 | |||
#endif | |||
void | |||
flextitleclick(Monitor *m, Client *c, int passx, int x, int w, int unused, Arg *arg, BarArg *barg) | |||
{ | |||
if (passx >= x && passx <= x + w) | |||
arg->v = c; | |||
} | |||
int | |||
flextitlecalculate( | |||
Monitor *m, int offx, int tabw, int passx, | |||
void(*tabfn)(Monitor *, Client *, int, int, int, int, Arg *arg, BarArg *barg), | |||
Arg *arg, BarArg *barg | |||
) { | |||
Client *c; | |||
int n, center = 0, mirror = 0, fixed = 0; // layout configuration | |||
int clientsnmaster = 0, clientsnstack = 0, clientsnfloating = 0, clientsnhidden = 0; | |||
int i, w, r, num = 0, den, fulllayout = 0; | |||
int clientsnstack2 = 0; | |||
int groupactive = 0; | |||
int selidx = 0; | |||
int dualstack = 0; | |||
int rw, rr; | |||
int mas_x = offx, st1_x = offx, st2_x = offx, hid_x = offx, flt_x = offx; | |||
int mas_w, st1_w, st2_w, hid_w; | |||
for (i = 0, c = m->clients; c; c = c->next) { | |||
if (!ISVISIBLE(c)) | |||
continue; | |||
if (HIDDEN(c)) { | |||
if (FLEXWINTITLE_HIDDENWEIGHT) | |||
clientsnhidden++; | |||
continue; | |||
} | |||
if (c->isfloating) { | |||
if (FLEXWINTITLE_FLOATWEIGHT) | |||
clientsnfloating++; | |||
continue; | |||
} | |||
if (m->sel == c) | |||
selidx = i; | |||
if (i < m->nmaster) | |||
clientsnmaster++; | |||
#if FLEXTILE_DELUXE_LAYOUT | |||
else if (m->nstack) { | |||
if (clientsnstack < m->nstack) | |||
clientsnstack++; | |||
else | |||
clientsnstack2++; | |||
} | |||
#endif // FLEXTILE_DELUXE_LAYOUT | |||
else if ((i - m->nmaster) % 2) | |||
clientsnstack2++; | |||
else | |||
clientsnstack++; | |||
i++; | |||
} | |||
if (!m->sel) | |||
groupactive = GRP_NOSELECTION; | |||
else if (HIDDEN(m->sel)) | |||
groupactive = GRP_HIDDEN; | |||
else if (m->sel->isfloating) | |||
groupactive = GRP_FLOAT; | |||
else if (selidx < clientsnmaster) | |||
groupactive = GRP_MASTER; | |||
else if (selidx < clientsnmaster + clientsnstack) | |||
groupactive = GRP_STACK1; | |||
else if (selidx < clientsnmaster + clientsnstack + clientsnstack2) | |||
groupactive = GRP_STACK2; | |||
n = clientsnmaster + clientsnstack + clientsnstack2 + clientsnfloating + clientsnhidden; | |||
if (n == 0) | |||
return 0; | |||
#if FLEXTILE_DELUXE_LAYOUT | |||
else if (m->lt[m->sellt]->arrange == &flextile) { | |||
int layout = m->ltaxis[LAYOUT]; | |||
if (layout < 0) { | |||
mirror = 1; | |||
layout *= -1; | |||
} | |||
if (layout > FLOATING_MASTER) { | |||
layout -= FLOATING_MASTER; | |||
fixed = 1; | |||
} | |||
if (layout == SPLIT_HORIZONTAL_DUAL_STACK || layout == SPLIT_HORIZONTAL_DUAL_STACK_FIXED) | |||
dualstack = 1; | |||
else if (layout == SPLIT_CENTERED_VERTICAL && (fixed || n - m->nmaster > 1)) | |||
center = 1; | |||
else if (layout == FLOATING_MASTER) | |||
center = 1; | |||
else if (layout == SPLIT_CENTERED_HORIZONTAL) { | |||
if (fixed || n - m->nmaster > 1) | |||
center = 1; | |||
} | |||
} | |||
#endif // FLEXTILE_DELUXE_LAYOUT | |||
#if CENTEREDMASTER_LAYOUT | |||
else if (m->lt[m->sellt]->arrange == ¢eredmaster && (fixed || n - m->nmaster > 1)) | |||
center = 1; | |||
#endif // CENTEREDMASTER_LAYOUT | |||
#if CENTEREDFLOATINGMASTER_LAYOUT | |||
else if (m->lt[m->sellt]->arrange == ¢eredfloatingmaster) | |||
center = 1; | |||
#endif // CENTEREDFLOATINGMASTER_LAYOUT | |||
/* Certain layouts have no master / stack areas */ | |||
if (!m->lt[m->sellt]->arrange // floating layout | |||
|| (!n || (!fixed && m->nmaster && n <= m->nmaster)) // no master | |||
#if MONOCLE_LAYOUT | |||
|| m->lt[m->sellt]->arrange == &monocle | |||
#endif // MONOCLE_LAYOUT | |||
#if GRIDMODE_LAYOUT | |||
|| m->lt[m->sellt]->arrange == &grid | |||
#endif // GRIDMODE_LAYOUT | |||
#if HORIZGRID_LAYOUT | |||
|| m->lt[m->sellt]->arrange == &horizgrid | |||
#endif // HORIZGRID_LAYOUT | |||
#if GAPPLESSGRID_LAYOUT | |||
|| m->lt[m->sellt]->arrange == &gaplessgrid | |||
#endif // GAPPLESSGRID_LAYOUT | |||
#if NROWGRID_LAYOUT | |||
|| m->lt[m->sellt]->arrange == &nrowgrid | |||
#endif // NROWGRID_LAYOUT | |||
#if FLEXTILE_DELUXE_LAYOUT | |||
|| (m->lt[m->sellt]->arrange == &flextile && m->ltaxis[LAYOUT] == NO_SPLIT) | |||
#endif // FLEXTILE_DELUXE_LAYOUT | |||
) | |||
fulllayout = 1; | |||
num = tabw; | |||
c = m->clients; | |||
/* floating mode */ | |||
if ((fulllayout && FLEXWINTITLE_FLOATWEIGHT > 0) || clientsnmaster + clientsnstack == 0 || !m->lt[m->sellt]->arrange) { | |||
den = clientsnmaster + clientsnstack + clientsnstack2 + clientsnfloating + clientsnhidden; | |||
w = num / den; | |||
r = num % den; // rest | |||
c = flextitledrawarea(m, c, mas_x, r, w, den, !m->lt[m->sellt]->arrange ? SchemeFlexActFloat : SCHEMEFOR(GRP_MASTER), 1, FLEXWINTITLE_HIDDENWEIGHT, FLEXWINTITLE_FLOATWEIGHT, passx, tabfn, arg, barg); // floating | |||
/* no master and stack mode, e.g. monocole, grid layouts, fibonacci */ | |||
} else if (fulllayout) { | |||
den = clientsnmaster + clientsnstack + clientsnstack2 + clientsnhidden; | |||
w = num / den; | |||
r = num % den; // rest | |||
c = flextitledrawarea(m, c, mas_x, r, w, den, SCHEMEFOR(GRP_MASTER), 1, FLEXWINTITLE_HIDDENWEIGHT, 0, passx, tabfn, arg, barg); // full | |||
/* tiled mode */ | |||
} else { | |||
den = clientsnmaster * FLEXWINTITLE_MASTERWEIGHT + (clientsnstack + clientsnstack2) * FLEXWINTITLE_STACKWEIGHT + clientsnfloating * FLEXWINTITLE_FLOATWEIGHT + clientsnhidden * FLEXWINTITLE_HIDDENWEIGHT; | |||
w = num / den; // weight width per client | |||
r = num % den; // weight rest width | |||
rw = r / n; // rest incr per client | |||
rr = r % n; // rest rest | |||
#if FLEXTILE_DELUXE_LAYOUT | |||
if ((!center && !dualstack) || (center && n <= m->nmaster + (m->nstack ? m->nstack : 1))) | |||
#else | |||
if ((!center && !dualstack) || (center && n <= m->nmaster + 1)) | |||
#endif // FLEXTILE_DELUXE_LAYOUT | |||
{ | |||
clientsnstack += clientsnstack2; | |||
clientsnstack2 = 0; | |||
if (groupactive == GRP_STACK2) | |||
groupactive = GRP_STACK1; | |||
} | |||
mas_w = clientsnmaster * rw + w * clientsnmaster * FLEXWINTITLE_MASTERWEIGHT + (rr > 0 ? MIN(rr, clientsnmaster) : 0); | |||
rr -= clientsnmaster; | |||
st1_w = clientsnstack * (rw + w * FLEXWINTITLE_STACKWEIGHT) + (rr > 0 ? MIN(rr, clientsnstack) : 0); | |||
rr -= clientsnstack; | |||
st2_w = clientsnstack2 * (rw + w * FLEXWINTITLE_STACKWEIGHT) + (rr > 0 ? MIN(rr, clientsnstack2) : 0); | |||
rr -= clientsnstack2; | |||
hid_w = clientsnhidden * (rw + w * FLEXWINTITLE_HIDDENWEIGHT) + (rr > 0 ? MIN(rr, clientsnhidden) : 0); | |||
rr -= clientsnhidden; | |||
rr = r % n; | |||
if (mirror) { | |||
if (center && clientsnstack2) { | |||
mas_x = st1_x + st1_w; | |||
st2_x = mas_x + mas_w; | |||
hid_x = st2_x + st2_w; | |||
} else { | |||
if (clientsnstack2) { | |||
st2_x = st1_x + st1_w; | |||
mas_x = st2_x + st2_w; | |||
} else | |||
mas_x = st1_x + st1_w; | |||
hid_x = mas_x + mas_w; | |||
} | |||
} else { | |||
if (center && clientsnstack2) { | |||
mas_x = st2_x + st2_w; | |||
st1_x = mas_x + mas_w; | |||
hid_x = st1_x + st1_w; | |||
} else { | |||
st1_x = mas_x + mas_w; | |||
if (clientsnstack2) { | |||
st2_x = st1_x + st1_w; | |||
hid_x = st2_x + st2_w; | |||
} else | |||
hid_x = st1_x + st1_w; | |||
} | |||
} | |||
flt_x = hid_x + hid_w; | |||
c = flextitledrawarea(m, c, mas_x, rr, w * FLEXWINTITLE_MASTERWEIGHT + rw, clientsnmaster, SCHEMEFOR(GRP_MASTER), 1, 0, 0, passx, tabfn, arg, barg); // master | |||
rr -= clientsnmaster; | |||
c = flextitledrawarea(m, c, st1_x, rr, w * FLEXWINTITLE_STACKWEIGHT + rw, clientsnstack, SCHEMEFOR(GRP_STACK1), 1, 0, 0, passx, tabfn, arg, barg); // stack1 | |||
rr -= clientsnstack; | |||
if (clientsnstack2) { | |||
c = flextitledrawarea(m, c, st2_x, rr, w * FLEXWINTITLE_STACKWEIGHT + rw, clientsnstack2, SCHEMEFOR(GRP_STACK2), 1, 0, 0, passx, tabfn, arg, barg); // stack2 | |||
rr -= clientsnstack2; | |||
} | |||
c = flextitledrawarea(m, m->clients, hid_x, rr, w * FLEXWINTITLE_HIDDENWEIGHT + rw, clientsnhidden, SCHEMEFOR(GRP_HIDDEN), 0, 1, 0, passx, tabfn, arg, barg); // hidden | |||
rr -= clientsnhidden; | |||
c = flextitledrawarea(m, m->clients, flt_x, rr, w * FLEXWINTITLE_FLOATWEIGHT + rw, clientsnfloating, SCHEMEFOR(GRP_FLOAT), 0, 0, 1, passx, tabfn, arg, barg); // floating | |||
} | |||
return 1; | |||
} |
@ -0,0 +1,10 @@ | |||
static int width_flexwintitle(Bar *bar, BarArg *a); | |||
static int draw_flexwintitle(Bar *bar, BarArg *a); | |||
static int click_flexwintitle(Bar *bar, Arg *arg, BarArg *a); | |||
static void flextitledraw(Monitor *m, Client *c, int unused, int x, int w, int groupactive, Arg *arg, BarArg *barg); | |||
static void flextitleclick(Monitor *m, Client *c, int passx, int x, int w, int unused, Arg *arg, BarArg *barg); | |||
static int flextitlecalculate(Monitor *m, int offx, int w, int passx, void(*tabfn)(Monitor *, Client *, int, int, int, int, Arg *arg, BarArg *barg), Arg *arg, BarArg *barg); | |||
static int getschemefor(Monitor *m, int group, int activegroup); | |||
static int getselschemefor(int scheme); | |||
static Client *flextitledrawarea(Monitor *m, Client *c, int x, int r, int w, int max_clients, int tabscheme, int draw_tiled, int draw_hidden, int draw_floating, int passx, void(*tabfn)(Monitor *, Client *, int, int, int, int, Arg *arg, BarArg *barg), Arg *arg, BarArg *barg); |
@ -0,0 +1,37 @@ | |||
void | |||
holdbar(const Arg *arg) | |||
{ | |||
if (selmon->showbar) | |||
return; | |||
Bar *bar; | |||
selmon->showbar = 2; | |||
updatebarpos(selmon); | |||
for (bar = selmon->bar; bar; bar = bar->next) | |||
XMoveResizeWindow(dpy, bar->win, bar->bx, bar->by, bar->bw, bar->bh); | |||
} | |||
void | |||
keyrelease(XEvent *e) | |||
{ | |||
Bar *bar; | |||
if (XEventsQueued(dpy, QueuedAfterReading)) { | |||
XEvent ne; | |||
XPeekEvent(dpy, &ne); | |||
if (ne.type == KeyPress && ne.xkey.time == e->xkey.time && | |||
ne.xkey.keycode == e->xkey.keycode) { | |||
XNextEvent(dpy, &ne); | |||
return; | |||
} | |||
} | |||
if (e->xkey.keycode == XKeysymToKeycode(dpy, HOLDKEY) && selmon->showbar == 2) { | |||
selmon->showbar = 0; | |||
updatebarpos(selmon); | |||
for (bar = selmon->bar; bar; bar = bar->next) | |||
XMoveResizeWindow(dpy, bar->win, bar->bx, bar->by, bar->bw, bar->bh); | |||
arrange(selmon); | |||
} | |||
#if COMBO_PATCH | |||
combo = 0; | |||
#endif // COMBO_PATCH | |||
} |
@ -0,0 +1,2 @@ | |||
static void keyrelease(XEvent *e); | |||
static void holdbar(const Arg *arg); |
@ -0,0 +1,110 @@ | |||
/* Indicator properties, you can override these in your config.h if you want. */ | |||
#ifndef TAGSINDICATOR | |||
#define TAGSINDICATOR 1 // 0 = off, 1 = on if >1 client/view tag, 2 = always on | |||
#endif | |||
#ifndef TAGSPX | |||
#define TAGSPX 5 // # pixels for tag grid boxes | |||
#endif | |||
#ifndef TAGSROWS | |||
#define TAGSROWS 3 // # rows in tag grid (9 tags, e.g. 3x3) | |||
#endif | |||
void | |||
drawindicator(Monitor *m, Client *c, unsigned int occ, int x, int y, int w, int h, unsigned int tag, int filled, int invert, int type) | |||
{ | |||
int i, boxw, boxs, indn = 0; | |||
if (!(occ & 1 << tag) || type == INDICATOR_NONE) | |||
return; | |||
boxs = drw->fonts->h / 9; | |||
boxw = drw->fonts->h / 6 + 2; | |||
if (filled == -1) | |||
filled = m == selmon && m->sel && m->sel->tags & 1 << tag; | |||
switch (type) { | |||
default: | |||
case INDICATOR_TOP_LEFT_SQUARE: | |||
drw_rect(drw, x + boxs, y + boxs, boxw, boxw, filled, invert); | |||
break; | |||
case INDICATOR_TOP_LEFT_LARGER_SQUARE: | |||
drw_rect(drw, x + boxs + 2, y + boxs+1, boxw+1, boxw+1, filled, invert); | |||
break; | |||
case INDICATOR_TOP_BAR: | |||
drw_rect(drw, x + boxw, y, w - ( 2 * boxw + 1), boxw/2, filled, invert); | |||
break; | |||
case INDICATOR_TOP_BAR_SLIM: | |||
drw_rect(drw, x + boxw, y, w - ( 2 * boxw + 1), 1, 0, invert); | |||
break; | |||
case INDICATOR_BOTTOM_BAR: | |||
drw_rect(drw, x + boxw, y + h - boxw/2, w - ( 2 * boxw + 1), boxw/2, filled, invert); | |||
break; | |||
case INDICATOR_BOTTOM_BAR_SLIM: | |||
drw_rect(drw, x + boxw, y + h - 1, w - ( 2 * boxw + 1), 1, 0, invert); | |||
break; | |||
case INDICATOR_BOX: | |||
drw_rect(drw, x + boxw, y, w - 2 * boxw, h, 0, invert); | |||
break; | |||
case INDICATOR_BOX_WIDER: | |||
drw_rect(drw, x + boxw/2, y, w - boxw, h, 0, invert); | |||
break; | |||
case INDICATOR_BOX_FULL: | |||
drw_rect(drw, x, y, w - 2, h, 0, invert); | |||
break; | |||
case INDICATOR_CLIENT_DOTS: | |||
for (c = m->clients; c; c = c->next) { | |||
if (c->tags & (1 << tag)) { | |||
drw_rect(drw, x, 1 + (indn * 2), m->sel == c ? 6 : 1, 1, 1, invert); | |||
indn++; | |||
} | |||
if (h <= 1 + (indn * 2)) { | |||
indn = 0; | |||
x += 2; | |||
} | |||
} | |||
break; | |||
case INDICATOR_RIGHT_TAGS: | |||
if (!c) | |||
break; | |||
for (i = 0; i < NUMTAGS; i++) { | |||
drw_rect(drw, | |||
( x + w - 2 - ((NUMTAGS / TAGSROWS) * TAGSPX) | |||
- (i % (NUMTAGS/TAGSROWS)) + ((i % (NUMTAGS / TAGSROWS)) * TAGSPX) | |||
), | |||
( y + 2 + ((i / (NUMTAGS/TAGSROWS)) * TAGSPX) | |||
- ((i / (NUMTAGS/TAGSROWS))) | |||
), | |||
TAGSPX, TAGSPX, (c->tags >> i) & 1, 0 | |||
); | |||
} | |||
break; | |||
case INDICATOR_PLUS_AND_LARGER_SQUARE: | |||
boxs += 2; | |||
boxw += 2; | |||
/* falls through */ | |||
case INDICATOR_PLUS_AND_SQUARE: | |||
drw_rect(drw, x + boxs, y + boxs, boxw % 2 ? boxw : boxw + 1, boxw % 2 ? boxw : boxw + 1, filled, invert); | |||
/* falls through */ | |||
case INDICATOR_PLUS: | |||
if (!(boxw % 2)) | |||
boxw += 1; | |||
drw_rect(drw, x + boxs + boxw / 2, y + boxs, 1, boxw, filled, invert); // | | |||
drw_rect(drw, x + boxs, y + boxs + boxw / 2, boxw + 1, 1, filled, invert); // ‒ | |||
break; | |||
} | |||
} | |||
void | |||
drawstateindicator(Monitor *m, Client *c, unsigned int occ, int x, int y, int w, int h, unsigned int tag, int filled, int invert) | |||
{ | |||
#if FAKEFULLSCREEN_CLIENT_PATCH | |||
if (c->fakefullscreen && c->isfloating) | |||
drawindicator(m, c, occ, x, y, w, h, tag, filled, invert, floatfakefsindicatortype); | |||
else if (c->fakefullscreen) | |||
drawindicator(m, c, occ, x, y, w, h, tag, filled, invert, fakefsindicatortype); | |||
else | |||
#endif // FAKEFULLSCREEN_CLIENT_PATCH | |||
if (c->isfloating) | |||
drawindicator(m, c, occ, x, y, w, h, tag, filled, invert, floatindicatortype); | |||
else | |||
drawindicator(m, c, occ, x, y, w, h, tag, filled, invert, tiledindicatortype); | |||
} |
@ -0,0 +1,20 @@ | |||
enum { | |||
INDICATOR_NONE, | |||
INDICATOR_TOP_LEFT_SQUARE, | |||
INDICATOR_TOP_LEFT_LARGER_SQUARE, | |||
INDICATOR_TOP_BAR, | |||
INDICATOR_TOP_BAR_SLIM, | |||
INDICATOR_BOTTOM_BAR, | |||
INDICATOR_BOTTOM_BAR_SLIM, | |||
INDICATOR_BOX, | |||
INDICATOR_BOX_WIDER, | |||
INDICATOR_BOX_FULL, | |||
INDICATOR_CLIENT_DOTS, | |||
INDICATOR_RIGHT_TAGS, | |||
INDICATOR_PLUS, | |||
INDICATOR_PLUS_AND_SQUARE, | |||
INDICATOR_PLUS_AND_LARGER_SQUARE, | |||
}; | |||
static void drawindicator(Monitor *m, Client *c, unsigned int occ, int x, int y, int w, int h, unsigned int tag, int filled, int invert, int type); | |||
static void drawstateindicator(Monitor *m, Client *c, unsigned int occ, int x, int y, int w, int h, unsigned int tag, int filled, int invert); |
@ -0,0 +1,18 @@ | |||
void | |||
layoutmenu(const Arg *arg) { | |||
FILE *p; | |||
char c[3], *s; | |||
int i; | |||
if (!(p = popen(layoutmenu_cmd, "r"))) | |||
return; | |||
s = fgets(c, sizeof(c), p); | |||
pclose(p); | |||
if (!s || *s == '\0' || c == '\0') | |||
return; | |||
i = atoi(c); | |||
setlayout(&((Arg) { .v = &layouts[i] })); | |||
} | |||
@ -0,0 +1 @@ | |||
static void layoutmenu(const Arg *arg); |