diff --git a/bin/screenshot_wayland.py b/bin/screenshot_wayland.py
index ca8cdd8..8171d7f 100755
--- a/bin/screenshot_wayland.py
+++ b/bin/screenshot_wayland.py
@@ -4,7 +4,8 @@
# Additional dependencies:
# - Python's pillow library
# - pip install --user pillow-avif-plugin # Until PIL merges avif support
-# - grim
+# - grim on wayland or scrot on x11
+# - slurp on wayland slop on x11
# - swappy
import argparse, os, sys, time, re, shutil, tarfile, tempfile, pillow_avif
from subprocess import run, Popen, PIPE, DEVNULL
@@ -15,20 +16,46 @@ from typing import *
# Global constants
RELATIVE_DIR = Path("Pictures/screenshots_wayland") # Default save directory
-DIR = Path.home()/RELATIVE_DIR
+DIR = Path.home() / RELATIVE_DIR
DIMENSIONS_REGEX = "([0-9]+),([0-9]+) ([0-9]+)x([0-9]+)"
+SLOP_REGEX = "([0-9]+)x([0-9]+)\+([0-9]+)\+([0-9]+)"
EXTENSION_REGEX = "\.([A-z0-9]+)$"
ORIGINAL_REGEX = "([0-9]+(_[0-9]{0,3})?)\.png"
EDIT_REGEX = "([0-9]+(_[0-9]{0,3})?)_edit_([0-9]+)" + EXTENSION_REGEX
-DS_BG_LIGHT = 'white'
-DS_SHADOW_LIGHT = 'black'
-DS_BG_DARK = '#d3869b'
-DS_SHADOW_DARK = 'black'
+DS_BG_LIGHT = "white"
+DS_SHADOW_LIGHT = "black"
+DS_BG_DARK = "#d3869b"
+DS_SHADOW_DARK = "black"
DEFAULT_EDIT_QUALITY = 50
-DEFAULT_EDIT_EXTENSION = 'avif'
+DEFAULT_EDIT_EXTENSION = "avif"
+
+
+class ScreenshotDimensions:
+ def __init__(self, x, y, w, h):
+ self.x = int(x)
+ self.y = int(y)
+ self.w = int(w)
+ self.h = int(h)
+
+ @classmethod
+ def from_string(self, s: str):
+ s = s.strip()
+ if m := re.fullmatch(DIMENSIONS_REGEX, s):
+ return self(m[1], m[2], m[3], m[4])
+ elif m := re.fullmatch(SLOP_REGEX, s):
+ return self(m[3], m[4], m[1], m[2])
+
+ raise Exception(f"`{s}` does not match pattern {DIMENSIONS_REGEX}")
+
+ def as_grim(self) -> str:
+ return f"{self.x},{self.y} {self.w}x{self.h}"
+
+ def as_scrot(self) -> str:
+ return f"{self.x},{self.y},{self.w},{self.h}"
+
# Returns a path to an unused screenshot in DIR
def get_sceenshot_path() -> Path:
@@ -40,6 +67,7 @@ def get_sceenshot_path() -> Path:
else:
return p
+
# Returns a free path to the last screenshot in DIR
# (original_path, new_edit_path)
#
@@ -59,18 +87,38 @@ def get_edit_path(ext: str) -> (Path, Path):
return DIR / originals[-1], DIR / f"{latest}_edit_{new_index}.{ext}"
+
# Copies the image to the wayland clipboard
def copy_to_clipboard(pic: Path):
- with open(pic, 'r') as img:
- run(["wl-copy"], stdin=img, timeout=4)
+ # TODO: xcompatability
+ try:
+ if "wayland" in os.environ["XDG_SESSION_TYPE"]:
+ with open(pic, "r") as img:
+ run(["wl-copy"], stdin=img, timeout=4)
+ return
+ except KeyError:
+ pass
+
+ run(
+ ["xclip", "-selection", "clipboard", "-t", "image/png", "-i", str(pic)],
+ timeout=4,
+ )
+
# Interactively gets user to select an area of the screen
-# String matches regex /[0-9]+,[0-9]+ [0-9]+x[0-9]+/
-# Example: 287,526 474x369
-def select_area() -> str:
- slurp = run(["slurp"], text=True, stdout=PIPE)
- slurp.check_returncode()
- return slurp.stdout.strip()
+def select_area() -> ScreenshotDimensions:
+ try:
+ if "wayland" in os.environ["XDG_SESSION_TYPE"]:
+ slurp = run(["slurp"], text=True, stdout=PIPE)
+ slurp.check_returncode()
+ return ScreenshotDimensions.from_string(slurp.stdout)
+ except KeyError:
+ pass
+
+ slop = run(["slop"], text=True, stdout=PIPE)
+ slop.check_returncode()
+ return ScreenshotDimensions.from_string(slop.stdout)
+
# Adds drop-shadow to an image. If `img` and `save` are the same path, the image
# is overwritten
@@ -79,26 +127,39 @@ def add_drop_shadow(img: Path, save: Path, back_color: str, shadow_color: str):
mode = img.mode
border_width = max(img.size) // 20 # 5% of larger dim
- width = img.size[0] + border_width*2
- height = img.size[1] + border_width*2
+ width = img.size[0] + border_width * 2
+ height = img.size[1] + border_width * 2
shadow = Image.new(mode, img.size, color=shadow_color)
canvas = Image.new(mode, (width, height), color=back_color)
- canvas.paste(shadow, box=(border_width, int(border_width*1.16)))
+ canvas.paste(shadow, box=(border_width, int(border_width * 1.16)))
canvas = canvas.filter(GaussianBlur(border_width // 4))
canvas.paste(img, box=(border_width, border_width))
canvas.save(save, optimize=True)
-# Uses grim for wayland to take a screenshot. Default to full screen without
-# dims. exact_dims must match a slurp regex
-def take_screenshot(path, exact_dims=None):
+
+# Uses grim for wayland, scropt for x to take a screenshot. Default to full
+# screen without dims. exact_dims must match a slurp regex
+def take_screenshot(path, exact_dims: ScreenshotDimensions = None):
+ try:
+ if "wayland" in os.environ["XDG_SESSION_TYPE"]:
+ if exact_dims is not None:
+ run(["grim", "-g", exact_dims.as_grim(), path]).check_returncode()
+ else:
+ run(["grim", path]).check_returncode()
+ return
+ except KeyError:
+ pass
+
+ # x11 version
if exact_dims is not None:
- run(["grim", "-g", exact_dims, path]).check_returncode()
+ run(["scrot", "-a", exact_dims.as_scrot(), "-F", path]).check_returncode()
else:
- run(["grim", path]).check_returncode()
+ run(["scrot", "-F", path]).check_returncode()
+
# Takes a screenshot as specified by args
def take_subcommand(args):
@@ -120,6 +181,7 @@ def take_subcommand(args):
if args.file is not None:
shutil.copyfile(save_path, args.file)
+
# Applies an edit to the latest screenshot
def edit_subcommand(args):
og_path, edit_path = get_edit_path(args.extension)
@@ -148,6 +210,7 @@ def edit_subcommand(args):
if args.file is not None:
shutil.copyfile(edit_path, args.file)
+
# Saves `DIR` into a tar file
def archive_subcommand(args):
# Get the list of images to backup
@@ -161,16 +224,17 @@ def archive_subcommand(args):
# Write compressed pictures to tmpdir with progress bar
TMP_DIR = tempfile.mkdtemp()
- ext = f'.{args.extension}'
+ ext = f".{args.extension}"
count = len(pics)
sys.stdout.write(
- f"Compressing {count} images into {ext} @ quality {args.quality}\n")
- sys.stdout.write(f'Progress: 0/{count}')
+ f"Compressing {count} images into {ext} @ quality {args.quality}\n"
+ )
+ sys.stdout.write(f"Progress: 0/{count}")
for i, pic in enumerate(pics):
- og = DIR/pic
- out = TMP_DIR/pic.with_suffix(ext)
+ og = DIR / pic
+ out = TMP_DIR / pic.with_suffix(ext)
with Image.open(og) as img:
img.save(out, quality=args.quality, method=6, optimize=True)
@@ -178,20 +242,21 @@ def archive_subcommand(args):
stat = os.stat(og)
os.utime(out, times=(stat.st_atime, stat.st_mtime))
- sys.stdout.write(f'\rProgress: {i+1}/{count}' + ' ' * 40)
+ sys.stdout.write(f"\rProgress: {i+1}/{count}" + " " * 40)
- sys.stdout.write('\nDone!\n')
+ sys.stdout.write("\nDone!\n")
- pics = [TMP_DIR/pic.with_suffix(ext) for pic in pics]
+ pics = [TMP_DIR / pic.with_suffix(ext) for pic in pics]
# Pack into tar file
with tarfile.open(args.tar_path, "w:gz") as tar:
for pic in pics:
tar.add(pic, arcname=pic.name)
+
# Provides a drawing editor for the latest image
def markup_subcommand(args):
- og_path, edit_path = get_edit_path('png')
+ og_path, edit_path = get_edit_path("png")
if args.show_latest:
print(og_path)
@@ -205,6 +270,7 @@ def markup_subcommand(args):
if args.file is not None:
shutil.copyfile(edit_path, args.file)
+
# ===================================================================
# Parse args
# ===================================================================
@@ -214,20 +280,19 @@ def parser_dir(s: str):
else:
raise NotADirectoryError(f"`{s}` is not a directory")
-def parse_dimensions(s: str) -> str:
- s = s.strip()
- if re.fullmatch(DIMENSIONS_REGEX, s):
- return s
- raise Exception(f"`{s}` does not match pattern {DIMENSIONS_REGEX}")
+def parse_dimensions(s: str) -> ScreenshotDimensions:
+ return ScreenshotDimensions.from_string(s)
+
def parse_percent(s: str) -> int:
s = s.strip()
try:
- return int(re.fullmatch('([0-9]+)%?', s)[1])
+ return int(re.fullmatch("([0-9]+)%?", s)[1])
except IndexError:
raise Exception(f"{s} is not a valid percent. Does not match /[0-9]+%?/")
+
def parse_size(s: str) -> (int, int):
s = s.strip()
try:
@@ -236,6 +301,7 @@ def parse_size(s: str) -> (int, int):
except:
raise Exception(f"{s} does not match size regex /[0-9]+[x ][0-9]+/")
+
def parse_tar(s: str) -> Path:
s = s.strip()
@@ -244,151 +310,172 @@ def parse_tar(s: str) -> Path:
else:
raise Exception(f"{s} is not a path to a .{{tar,tgz,tar.gz}} file")
+
parser = argparse.ArgumentParser(
- prog="Screenshot Wayland v1.0.0",
- description='Take a screenshot on Sway'
-);
+ prog="Screenshot Wayland v1.0.0", description="Take a screenshot on Sway"
+)
parser.add_argument(
- '-s', '--screenshot-dir',
+ "-s",
+ "--screenshot-dir",
type=parser_dir,
metavar="
",
help=f"Save and edit directory. Default: ~/{RELATIVE_DIR}",
-);
+)
# Subcommands ====
-subcommands = parser.add_subparsers(dest='subcommand', required=True);
+subcommands = parser.add_subparsers(dest="subcommand", required=True)
# Take ====
-take_subcmd = subcommands.add_parser(
- "take", help="Takes a screenshot");
+take_subcmd = subcommands.add_parser("take", help="Takes a screenshot")
-take_common = argparse.ArgumentParser(add_help=False);
+take_common = argparse.ArgumentParser(add_help=False)
take_common.add_argument(
- '-c', '--clipboard',
- action='store_true',
- help='Save the screenshot to your clipboard',
-);
+ "-c",
+ "--clipboard",
+ action="store_true",
+ help="Save the screenshot to your clipboard",
+)
take_common.add_argument(
- "file", nargs='?', type=Path,
- help="Save the screenshot to this file name"
-);
+ "file", nargs="?", type=Path, help="Save the screenshot to this file name"
+)
# Different possible screenshot regions
-region = take_subcmd.add_subparsers(dest='region', required=True);
+region = take_subcmd.add_subparsers(dest="region", required=True)
full = region.add_parser(
- 'full', parents=[take_common],
- help='Take a screenshot of the entire screen'
-);
+ "full", parents=[take_common], help="Take a screenshot of the entire screen"
+)
exact = region.add_parser(
- 'exact', parents=[take_common],
- help="Exact dimensions of screenshot: 'x,y width,height'"
-);
+ "exact",
+ parents=[take_common],
+ help="Exact dimensions of screenshot: 'x,y width,height'",
+)
select = region.add_parser(
- 'select', parents=[take_common],
- help="Use `slurp` to select a region with your mouse"
-);
+ "select",
+ parents=[take_common],
+ help="Use `slurp` or `slop` to select a region with your mouse",
+)
exact.add_argument(
- 'dimensions',
+ "dimensions",
type=parse_dimensions,
metavar="'N,N NxN'",
- help="Exact dimensions of screenshot: 'x,y width,height'"
-);
+ help="Exact dimensions of screenshot: 'x,y widthxheight'",
+)
# Edit ====
edit_subcmd = subcommands.add_parser(
- "edit", help="Apply an edit to the latest screenshot");
+ "edit", help="Apply an edit to the latest screenshot"
+)
edit_subcmd.add_argument(
- '-r', '--rescale',
+ "-r",
+ "--rescale",
type=parse_percent,
metavar="",
- help='Rescale the latest screenshot to of the original',
-);
+ help="Rescale the latest screenshot to of the original",
+)
edit_subcmd.add_argument(
- '-s', '--size',
+ "-s",
+ "--size",
type=parse_size,
metavar="",
- help='Set the dimensions of the latest screenshot',
-);
+ help="Set the dimensions of the latest screenshot",
+)
edit_subcmd.add_argument(
- '-c', '--clipboard',
- action='store_true',
- help='Save the edited screenshot to your clipboard',
-);
+ "-c",
+ "--clipboard",
+ action="store_true",
+ help="Save the edited screenshot to your clipboard",
+)
edit_subcmd.add_argument(
- '-d', '--drop-shadow',
- action='store',
- choices=['light', 'dark'],
- help='Apply a drop shadow with a light/dark background',
-);
+ "-d",
+ "--drop-shadow",
+ action="store",
+ choices=["light", "dark"],
+ help="Apply a drop shadow with a light/dark background",
+)
edit_subcmd.add_argument(
- '-e', '--extension', metavar="",
+ "-e",
+ "--extension",
+ metavar="",
type=str,
default=DEFAULT_EDIT_EXTENSION,
- help='Change image extension and image type saved',
-);
+ help="Change image extension and image type saved",
+)
edit_subcmd.add_argument(
- '-q', '--quality',
+ "-q",
+ "--quality",
type=parse_percent,
default=DEFAULT_EDIT_QUALITY,
- metavar='',
- help='Set quality of new image. [0, 100], higher means bigger file',
-);
+ metavar="",
+ help="Set quality of new image. [0, 100], higher means bigger file",
+)
edit_subcmd.add_argument(
- '--overwrite',
- action='store_true',
- help='Overwrite original image with the edited image',
-);
+ "--overwrite",
+ action="store_true",
+ help="Overwrite original image with the edited image",
+)
edit_subcmd.add_argument(
- "file", nargs='?', type=Path,
+ "file",
+ nargs="?",
+ type=Path,
help="Save the edited screenshot to this file name",
-);
+)
# Archive ====
archive_subcmd = subcommands.add_parser(
- "archive", help="Backup the screenshots directory to a tar file");
+ "archive", help="Backup the screenshots directory to a tar file"
+)
archive_subcmd.add_argument(
- '-e', '--extension',
+ "-e",
+ "--extension",
type=str,
default=DEFAULT_EDIT_EXTENSION,
metavar="",
- help='Change image extension and image type backed up',
-);
+ help="Change image extension and image type backed up",
+)
archive_subcmd.add_argument(
- '-q', '--quality',
+ "-q",
+ "--quality",
type=parse_percent,
default=DEFAULT_EDIT_QUALITY,
- metavar='',
- help='Set quality of backed up images. [0, 100], higher means bigger file',
-);
+ metavar="",
+ help="Set quality of backed up images. [0, 100], higher means bigger file",
+)
archive_subcmd.add_argument(
- 'which', metavar='',
- choices=['all', 'unedited'],
- help='One of {all,unedited}. Backup only unedited images or all of them',
-);
+ "which",
+ metavar="",
+ choices=["all", "unedited"],
+ help="One of {all,unedited}. Backup only unedited images or all of them",
+)
archive_subcmd.add_argument(
- "tar_path", metavar='',
+ "tar_path",
+ metavar="",
type=parse_tar,
help="Destination tar file. Should be .{tar,tgz,tar.gz}",
-);
+)
# Markup ====
markup_subcmd = subcommands.add_parser(
- "markup", help="Markup the latest screenshot in swappy");
+ "markup", help="Markup the latest screenshot in swappy"
+)
markup_subcmd.add_argument(
- '-c', '--clipboard',
- action='store_true',
- help='Save the screenshot to your clipboard',
-);
+ "-c",
+ "--clipboard",
+ action="store_true",
+ help="Save the screenshot to your clipboard",
+)
markup_subcmd.add_argument(
- '-s', '--show-latest',
- action='store_true',
- help='Show the path to the latest image and exit',
-);
+ "-s",
+ "--show-latest",
+ action="store_true",
+ help="Show the path to the latest image and exit",
+)
markup_subcmd.add_argument(
- "file", nargs='?', type=Path,
+ "file",
+ nargs="?",
+ type=Path,
help="Save the edited screenshot to this file name",
-);
+)
-args = parser.parse_args();
+args = parser.parse_args()
# Second layer of parser checks
if args.screenshot_dir is not None:
diff --git a/i3/brightness_lock.sh b/i3/brightness_lock.sh
new file mode 100755
index 0000000..8865042
--- /dev/null
+++ b/i3/brightness_lock.sh
@@ -0,0 +1,58 @@
+#!/usr/bin/env bash
+# Locks the screens and lowers-brightness. Restores brightness on unlock
+
+# Defaults for levels when restored
+declare light_restore=3
+declare ddc_restore=3
+
+# Levels when dimmed
+declare -ri light_dim=1
+declare -ri ddc_dim=1
+
+get_brightness() {
+ local lvl
+
+ if command -v light &>/dev/null; then
+ lvl="$(light -G)"
+
+ if [[ $lvl =~ ^[0-9]+(\.[0-9]+)?$ ]]; then
+ light_restore="$lvl"
+ fi
+ fi
+
+ if command -v ddcutil &>/dev/null; then
+ lvl="$(ddcutil getvcp 10 | awk '{
+ gsub(" ",""); # Remove spaces
+ split($0, a, "=");
+ split(a[2], a, ","); print a[1]
+ }')"
+
+ if [[ $lvl =~ ^[0-9]+$ ]]; then
+ ddc_restore="$lvl"
+ fi
+ fi
+}
+
+set_brightness() {
+ local -r light_lvl="$1"
+ local -r ddc_lvl="$2"
+
+ if command -v light &>/dev/null; then
+ light -S "$light_lvl"
+ fi
+
+ if command -v ddcutil &>/dev/null; then
+ ddcutil setvcp 10 "$ddc_lvl"
+ fi
+}
+
+i3lock -i ~/.config/swaylock/default_wallpaper.png &
+
+get_brightness
+set_brightness $light_dim $ddc_dim
+
+while pgrep i3lock &>/dev/null; do
+ sleep 1
+done
+
+set_brightness $light_restore $ddc_restore
diff --git a/i3/config b/i3/config
new file mode 100644
index 0000000..01e75ad
--- /dev/null
+++ b/i3/config
@@ -0,0 +1,405 @@
+# This file has been auto-generated by i3-config-wizard(1).
+# It will not be overwritten, so edit it as you like.
+#
+# Should you change your keyboard layout some time, delete
+# this file and re-run i3-config-wizard(1).
+#
+
+# i3 config file (v4)
+#
+# Please see https://i3wm.org/docs/userguide.html for a complete reference!
+
+set $mod1 Mod4
+set $mod2 Mod4+Shift
+
+# Font for window titles. Will also be used by the bar unless a different font
+# is used in the bar {} block below.
+#font Meslo LG M Regular Nerd Font Complete:monospace 11
+font pango:monospace 11
+
+# This font is widely installed, provides lots of unicode glyphs, right-to-left
+# text rendering and scalability on retina/hidpi displays (thanks to pango).
+#font pango:DejaVu Sans Mono 8
+
+# Pane movement keys - vim
+set $pane_left h
+set $pane_down j
+set $pane_up k
+set $pane_right l
+
+# Symmetric keys - mpv
+set $left_outer q
+set $left_inner w
+set $right_inner e
+set $right_outer r
+
+# Space movement keys - gaming
+set $space_up w
+set $space_left a
+set $space_down s
+set $space_right d
+
+# Resize keys - vim windows
+set $size_up comma
+set $size_down period
+
+set $term alacritty
+set $menu rlaunch
+
+# Sound effects and additional features
+set $volume_command ~/.config/sway/pulse_audio_volume.sh
+set $volume_change_sound ~/.config/sway/volume_change_sound.mp3
+set $screenshot_sound ffplay -nodisp -autoexit -v error ~/.config/sway/screenshot_sound.mp3
+
+# Duplicate screenshot path, for quick uploads
+set $screenshot_tmp /dev/shm/screenshot_shm.png
+set $screenshot_script ~/.configs_pointer/bin/screenshot_wayland.py
+
+#╔─────────────────────────────────────────────────────────────────────────────╗
+#│ Pαηεs αηd cδηταiηεrs |
+#╚─────────────────────────────────────────────────────────────────────────────╝
+ # Move focus
+bindsym $mod1+$pane_left focus left
+bindsym $mod1+$pane_down focus down
+bindsym $mod1+$pane_up focus up
+bindsym $mod1+$pane_right focus right
+
+ # Move focused window
+bindsym $mod2+$pane_left move left
+bindsym $mod2+$pane_down move down
+bindsym $mod2+$pane_up move up
+bindsym $mod2+$pane_right move right
+
+ # Layout modifiers
+bindsym $mod2+f floating toggle
+bindsym $mod2+s layout stacking
+bindsym $mod2+t layout tabbed
+bindsym $mod2+r layout toggle
+
+ # Binary split container
+bindsym $mod1+b split toggle
+#bindsym $mod1+v split none # Does not exist in i3
+bindsym $mod1+m fullscreen
+
+ # Switch container layout Stacking|Tabbed|Rotate-bsp
+bindsym $mod1+s layout stacking
+bindsym $mod1+t layout tabbed
+bindsym $mod1+r layout toggle split
+
+ # Traverse window tree
+bindsym $mod1+u focus parent
+bindsym $mod1+d focus child
+
+ # Alt-tab cycles windows, like on proprietary systems
+bindsym $mod1+tab exec ~/.config/sway/cycle_windows.py next
+bindsym $mod2+tab exec ~/.config/sway/cycle_windows.py previous
+
+ # Swap focus between the tiling area and the floating area
+#bindsym $mod+space focus mode_toggle
+
+ # Important rust jerk
+bindsym $mod1+ctrl+r exec wtype "asExpectedOfRust"
+#bindsym $mod1+ctrl+s reload
+
+ # Automatic upload image to discord
+bindsym $mod1+ctrl+u exec sudo -u emiliko ~/.configs_pointer/bin/auto_image_upload_discord.sh
+
+ # Paste by typing
+bindsym Mod4+Ctrl+v exec ~/.configs_pointer/bin/wpastetype.py
+
+# rEsize containers ====
+bindsym $mod1+e mode "resize"
+
+mode "resize" {
+ # Horizontal, shift for fine adjust
+ bindsym $size_up resize grow width 16px
+ bindsym $size_down resize shrink width 16px
+ bindsym $size_up+Shift resize grow width 4px
+ bindsym $size_down+Shift resize shrink width 4px
+
+ # Vertical, shift for fine adjust
+ bindsym $mod1+$size_down resize shrink height 16px
+ bindsym $mod1+$size_up resize grow height 16px
+ bindsym $mod1+$size_down+Shift resize shrink height 4px
+ bindsym $mod1+$size_up+Shift resize grow height 4px
+
+ # Escaping
+ bindsym $mod1+f mode "workspace_tuning"
+
+ bindsym Shift+end mode "default"
+ bindsym Ctrl+k mode "default"
+ bindsym Ctrl+bracketleft mode "default"
+ bindsym Escape mode "default"
+}
+
+# Gaps ====
+bindsym $mod2+bracketright gaps inner current minus 3
+bindsym $mod2+bracketleft gaps inner current plus 3
+#bindsym $mod2+equal gaps toggle
+ # Standard outer gapping for 27" screen
+bindsym $mod2+c gaps vertical current set 100, \
+ gaps horizontal current set 200
+ # Same as above, shifted left slightly
+bindsym $mod2+g gaps vertical current set 100, \
+ gaps left current set 150, \
+ gaps right current set 250
+
+bindsym $mod1+g mode "gapping"
+mode "gapping" {
+ # General resize
+ bindsym $mod2+bracketright gaps inner current minus 3
+ bindsym $mod2+bracketleft gaps inner current plus 3
+
+ # Horizontal
+ bindsym q gaps left current minus 2
+ bindsym w gaps left current plus 2
+ bindsym e gaps right current plus 2
+ bindsym r gaps right current minus 2
+
+ # Vertical
+ bindsym $mod1+q gaps top current minus 2
+ bindsym $mod1+w gaps top current plus 2
+ bindsym $mod1+e gaps bottom current plus 2
+ bindsym $mod1+r gaps bottom current minus 2
+
+ # Escaping
+ bindsym Shift+end mode "default"
+ bindsym Ctrl+k mode "default"
+ bindsym Ctrl+bracketleft mode "default"
+ bindsym Escape mode "default"
+}
+
+#╔─────────────────────────────────────────────────────────────────────────────╗
+#│ Wδrksραcεs |
+#╚─────────────────────────────────────────────────────────────────────────────╝
+bindsym $mod1+bracketleft workspace prev
+bindsym $mod1+bracketright workspace next
+
+bindsym $mod2+a workspace prev
+bindsym $mod2+d workspace next
+
+bindsym $mod1+1 workspace number 1
+bindsym $mod1+2 workspace number 2
+bindsym $mod1+3 workspace number 3
+bindsym $mod1+4 workspace number 4
+bindsym $mod1+5 workspace number 5
+bindsym $mod1+6 workspace number 6
+bindsym $mod1+7 workspace number 7
+bindsym $mod1+8 workspace number 8
+bindsym $mod1+9 workspace number 9
+
+# Advanced workspace tuning ====
+bindsym $mod1+f mode "workspace_tuning"
+
+mode "workspace_tuning" {
+
+ # Move focused container to workspace
+ bindsym 1 move container to workspace number 1
+ bindsym 2 move container to workspace number 2
+ bindsym 3 move container to workspace number 3
+ bindsym 4 move container to workspace number 4
+ bindsym 5 move container to workspace number 5
+ bindsym 6 move container to workspace number 6
+ bindsym 7 move container to workspace number 7
+ bindsym 8 move container to workspace number 8
+ bindsym 9 move container to workspace number 9
+
+ # Swap containers
+ bindsym $mod1+$pane_left mark --add "_swap", focus left, swap container with mark "_swap", focus left, unmark "_swap"
+ bindsym $mod1+$pane_down mark --add "_swap", focus down, swap container with mark "_swap", focus down, unmark "_swap"
+ bindsym $mod1+$pane_up mark --add "_swap", focus up, swap container with mark "_swap", focus up, unmark "_swap"
+ bindsym $mod1+$pane_right mark --add "_swap", focus right, swap container with mark "_swap", focus right, unmark "_swap"
+
+ # Stack, Tab, Rotate
+ bindsym s layout stacking
+ bindsym t layout tabbed
+ bindsym r layout toggle split
+
+ bindsym f fullscreen
+ bindsym Shift+f fullscreen
+
+ # Escaping
+ bindsym $mod1+e mode "resize"
+
+ bindsym Shift+end mode "default"
+ bindsym Ctrl+k mode "default"
+ bindsym Ctrl+bracketleft mode "default"
+ bindsym Escape mode "default"
+}
+
+#╔─────────────────────────────────────────────────────────────────────────────╗
+#│ Iηρμτ αηd δμτρμτ αdjμsτmεητs |
+#╚─────────────────────────────────────────────────────────────────────────────╝
+# Volume controls
+bindsym XF86AudioRaiseVolume exec $volume_command 4 $volume_change_sound
+bindsym XF86AudioLowerVolume exec $volume_command -4 $volume_change_sound
+bindsym shift+XF86AudioRaiseVolume exec $volume_command 1 $volume_change_sound
+bindsym shift+XF86AudioLowerVolume exec $volume_command -1 $volume_change_sound
+bindsym XF86AudioMute exec ~/.config/sway/toggle_mute.sh
+
+# External brightness control
+ # For laptops
+bindsym XF86MonBrightnessUp exec light -A 1
+bindsym XF86MonBrightnessDown exec light -U 1
+ # For external monitors
+bindsym XF86AudioNext exec ddcutil setvcp 10 + 4
+bindsym XF86AudioPrev exec ddcutil setvcp 10 - 4
+bindsym F10 exec ddcutil setvcp 10 + 1
+bindsym F8 exec ddcutil setvcp 10 - 1
+bindsym XF86Back exec ddcutil setvcp 12 + 2
+bindsym Menu exec ddcutil setvcp 12 - 2
+
+#╔─────────────────────────────────────────────────────────────────────────────╗
+#│ Hδτkεy dαεmδη |
+#╚─────────────────────────────────────────────────────────────────────────────╝
+ # Terminal
+bindsym $mod2+Return exec $term
+ # Chromium
+bindsym $mod2+n exec chromium
+ # Firefox
+bindsym $mod2+p exec MOZ_ENABLE_WAYLAND=1 firefox --private-window
+ # App launcher, like spotlight
+bindsym $mod1+space exec $menu
+ # Reload sway config
+bindsym $mod1+ctrl+s reload
+ # Close window
+bindsym $mod2+q kill
+ # Dragging windows
+#floating_modifier $mod1 normal
+floating_modifier $mod1
+ # Screenlock
+bindsym $mod2+i exec ~/.config/i3/brightness_lock.sh
+ # Scratchpad
+bindsym $mod1+Shift+minus move scratchpad
+bindsym $mod1+minus scratchpad show
+ # Warpd - Keyboard-driven mouse
+bindsym Mod4+Control+j exec warpd --hint
+bindsym Mod4+Control+n exec warpd --normal
+ # Xremap
+exec sudo ~/.configs_pointer/bin/switch_keyboard.sh pc
+ # IME Module
+bindsym $mod1+i exec ~/.config/sway/toggle_fcitx.sh
+ # Mako notifications
+exec mako
+ # Exit sway (hit 3 times repeatedly to force exit)
+bindsym $mod2+Escape exec ~/.config/sway/sway_exit.sh
+
+exec --no-startup-id feh --bg-fill ~/.configs_pointer/sway/default_wallpaper.png
+
+client.focused #F4A66E #F4A66E #000000 #F4A66E #F4A66E
+
+# Screenshots ====
+bindsym $mod2+6 mode "screenshots"
+bindsym $mod2+5 mode "screenshots"; exec $screenshot_script take select -c $screenshot_tmp && $screenshot_sound
+bindsym $mod2+4 mode "screenshots"; exec $screenshot_script take exact "$(~/.config/sway/window_dimensions.py)" && \
+ $screenshot_script edit -c -q 100 -d "$(colo.sh -t)" -e png --overwrite $screenshot_tmp && $screenshot_sound
+
+mode "screenshots" {
+ # Screenshooting in processing "steps"
+ # 1. Get screenshot
+ bindsym a exec $screenshot_script take select -c $screenshot_tmp && $screenshot_sound
+ bindsym f exec $screenshot_script take full -c $screenshot_tmp && $screenshot_sound
+ bindsym m mode "default"; exec $screenshot_script markup -c $screenshot_tmp && $screenshot_sound
+ bindsym g mode "default"; exec gimp $($screenshot_script markup --show-latest)
+
+ # 2. Downsize the screenshot, since 4k is too big
+ bindsym 1 exec $screenshot_script edit -c -e png -q '40%' -r '50%' $screenshot_tmp && $screenshot_sound
+ bindsym 2 exec $screenshot_script edit -c -e png -q '40%' -r '60%' $screenshot_tmp && $screenshot_sound
+ bindsym 3 exec $screenshot_script edit -c -e png -q '40%' -r '70%' $screenshot_tmp && $screenshot_sound
+ bindsym 4 exec $screenshot_script edit -c -e png -q '40%' -r '80%' $screenshot_tmp && $screenshot_sound
+ bindsym 5 exec $screenshot_script edit -c -e png -q '80%' -r '50%' $screenshot_tmp && $screenshot_sound
+ bindsym 6 exec $screenshot_script edit -c -e png -q '80%' -r '60%' $screenshot_tmp && $screenshot_sound
+ bindsym 7 exec $screenshot_script edit -c -e png -q '80%' -r '70%' $screenshot_tmp && $screenshot_sound
+ bindsym 8 exec $screenshot_script edit -c -e png -q '80%' -r '80%' $screenshot_tmp && $screenshot_sound
+ bindsym 9 exec $screenshot_script edit -c -e png -q '80%' -r '90%' $screenshot_tmp && $screenshot_sound
+ bindsym 0 exec $screenshot_script edit -c -e png -q '80%' -r '100%' $screenshot_tmp && $screenshot_sound
+
+ # OR compress image
+ bindsym space mode "shrink_screenshots"
+
+ # 3. Copy the smaller image back to clipboard and return to default
+ bindsym return mode "default"
+ bindsym Shift+end mode "default"
+ bindsym Ctrl+k mode "default"
+ bindsym Ctrl+bracketleft mode "default"
+ bindsym Escape mode "default"
+}
+
+mode "shrink_screenshots" {
+ # 2. Compress the screenshot, hard
+ bindsym 1 exec $screenshot_script edit -c -e webp -q '40%' -r '50%' $screenshot_tmp && $screenshot_sound
+ bindsym 2 exec $screenshot_script edit -c -e webp -q '40%' -r '60%' $screenshot_tmp && $screenshot_sound
+ bindsym 3 exec $screenshot_script edit -c -e webp -q '40%' -r '70%' $screenshot_tmp && $screenshot_sound
+ bindsym 4 exec $screenshot_script edit -c -e webp -q '40%' -r '80%' $screenshot_tmp && $screenshot_sound
+ bindsym 5 exec $screenshot_script edit -c -e webp -q '80%' -r '50%' $screenshot_tmp && $screenshot_sound
+ bindsym 6 exec $screenshot_script edit -c -e webp -q '80%' -r '60%' $screenshot_tmp && $screenshot_sound
+ bindsym 7 exec $screenshot_script edit -c -e webp -q '80%' -r '70%' $screenshot_tmp && $screenshot_sound
+ bindsym 8 exec $screenshot_script edit -c -e webp -q '80%' -r '80%' $screenshot_tmp && $screenshot_sound
+ bindsym 9 exec $screenshot_script edit -c -e webp -q '80%' -r '90%' $screenshot_tmp && $screenshot_sound
+ bindsym 0 exec $screenshot_script edit -c -e webp -q '80%' -r '100%' $screenshot_tmp && $screenshot_sound
+
+ bindsym space mode "screenshots"
+
+ # 3. Back out
+ bindsym return mode "default"
+ bindsym Shift+end mode "default"
+ bindsym Ctrl+k mode "default"
+ bindsym Ctrl+bracketleft mode "default"
+ bindsym Escape mode "default"
+}
+
+# Global mpv ====
+bindsym $mod2+m mode "mpv_global"
+
+mode "mpv_global" {
+ bindsym j exec ~/.config/sway/mpv_keys.sh 'j'
+ bindsym k exec ~/.config/sway/mpv_keys.sh 'k'
+ bindsym l exec ~/.config/sway/mpv_keys.sh 'l'
+ bindsym Left exec ~/.config/sway/mpv_keys.sh 'LEFT'
+ bindsym Right exec ~/.config/sway/mpv_keys.sh 'RIGHT'
+ bindsym space exec ~/.config/sway/mpv_keys.sh 'SPACE'
+ bindsym bracketleft exec ~/.config/sway/mpv_keys.sh '['
+ bindsym bracketright exec ~/.config/sway/mpv_keys.sh ']'
+ bindsym minus exec ~/.config/sway/mpv_keys.sh '-'
+ bindsym equal exec ~/.config/sway/mpv_keys.sh '='
+ bindsym m exec ~/.config/sway/mpv_keys.sh 'm'
+ bindsym A exec ~/.config/sway/mpv_keys.sh 'A'
+
+ bindsym return mode "default"
+ bindsym Ctrl+k mode "default"
+ bindsym Shift+End mode "default"
+ bindsym Escape mode "default"
+}
+
+#╔─────────────────────────────────────────────────────────────────────────────╗
+#│ Sτylεs |
+#╚─────────────────────────────────────────────────────────────────────────────╝
+bar {
+ #status_command i3status
+ i3bar_command i3bar --transparency
+ position bottom
+
+ binding_mode_indicator yes
+
+ # Vertical horizontal
+ #gaps 0 10
+
+ mode dock
+
+ status_command while ~/.config/sway/swaybar_status.sh; do sleep 60; done
+
+ colors {
+ # Foreground color
+ statusline #e7d6ad
+ background #22222200
+
+ # border background text
+ focused_workspace #F4B36Eff #F4B36Eff #000000
+ inactive_workspace #00000000 #444444 #e7d6ad
+ urgent_workspace #fadb2f #fadb2f #000000
+
+ binding_mode #8ec07c #8ec07c #000000
+ binding_mode #8ec07c #d3869b #000000
+ binding_mode #fadb2f #fadb2f #000000
+ }
+}
diff --git a/install.sh b/install.sh
index 2577720..9120c8f 100755
--- a/install.sh
+++ b/install.sh
@@ -35,6 +35,7 @@ declare -r UNIX_CONFIGS=(\
) \
LINUX_ONLY=(\
# Swayland
+ i3 ~/.config/i3
sway ~/.config/sway
swaylock ~/.config/swaylock
xremap ~/.config/xremap
diff --git a/sway/window_dimensions.py b/sway/window_dimensions.py
index aca3ee4..bfc8800 100755
--- a/sway/window_dimensions.py
+++ b/sway/window_dimensions.py
@@ -6,33 +6,37 @@
# The printed dimensions will match the slup regex below:
# /[0-9]+,[0-9]+ [0-9]+x[0-9]+/
import json
+import os
from subprocess import run, PIPE
from typing import Optional
+
def get_sway_tree() -> dict:
- swaymsg = run(["swaymsg", "-t", "get_tree"], stdout=PIPE)
+ swaymsg = run([MSG_COMMAND, "-t", "get_tree"], stdout=PIPE)
swaymsg.check_returncode()
return json.loads(swaymsg.stdout)
+
def format_window_coords(window: dict, rect: dict) -> str:
if sum(window.values()) == 0: # Multiple windows selected
- x = rect['x']
- y = rect['y']
- w = rect['width']
- h = rect['height']
- elif window['y'] + window['height'] == rect['height']: # Account for border
- x = rect['x'] + window['x']
- y = rect['y']
- w = window['width']
- h = window['height'] - window['y']
+ x = rect["x"]
+ y = rect["y"]
+ w = rect["width"]
+ h = rect["height"]
+ elif window["y"] + window["height"] == rect["height"]: # Account for border
+ x = rect["x"] + window["x"]
+ y = rect["y"]
+ w = window["width"]
+ h = window["height"] - window["y"]
else:
- x = rect['x'] + window['x']
- y = rect['y'] + window['y']
- w = window['width']
- h = window['height']
+ x = rect["x"] + window["x"]
+ y = rect["y"] + window["y"]
+ w = window["width"]
+ h = window["height"]
return f"{x},{y} {w}x{h}"
+
# Return dict indexing path to the first entry with "key" matching "val"
# For example, it may return
# ['nodes', 1, 'nodes', 1, 'nodes', 0, 'nodes', 0]
@@ -57,17 +61,27 @@ def trace_json_path(js, find_key, find_val) -> Optional[list]:
return None
+
def focused_sway_area():
tree = get_sway_tree()
- trace = trace_json_path(tree, 'focused', True)
+ trace = trace_json_path(tree, "focused", True)
if trace is None:
- print('No focused window was found')
+ print("No focused window was found")
exit(1)
for i in trace:
tree = tree[i]
- return format_window_coords(tree['window_rect'], tree['rect'])
+ return format_window_coords(tree["window_rect"], tree["rect"])
+
+
+if __name__ == "__main__":
+ try:
+ if "wayland" in os.environ["XDG_SESSION_TYPE"]:
+ MSG_COMMAND = "swaymsg"
+ else:
+ MSG_COMMAND = "i3-msg"
+ except KeyError:
+ MSG_COMMAND = "i3-msg"
-if __name__ == '__main__':
print(focused_sway_area())