feat: Emacs shell compatibility and SCAD mode #10

Closed
aniva wants to merge 0 commits from emacs into main
20 changed files with 383 additions and 1148 deletions

View File

@ -22,7 +22,8 @@ recommended.
* Status Bar and Tray
- ~eww-wayland~: Requires ~socat~ to listen for hyprland events.
- ~waybar~: Status bar, use ~waybar-hyprland-git~ AUR repo to enable clicking!
- ~eww-wayland~: Another status bar, requires ~socat~ to listen for hyprland events.
* Sway
@ -56,6 +57,7 @@ This is another choice of a compositor.
Note: Setup does not work with NVIDIA GPU without some tweaks.
- [[https://wiki.archlinux.org/title/Hyprland][hyprland]]: window manager
Until [[https://github.com/elkowar/eww/issues/111][tray support]] has been added to ~eww~ in a stable manner, tray will be handled with waybar.
- ~hyprpaper~: Wallpaper engine
The command to set a wallpaper:
@ -77,14 +79,12 @@ convert $IN \
#+end_src
- ~swaync~: Notification server
- ~wofi~: finding programs, drop in replacement for ~rofi~
- ~grimblast~: Screenshot engine
- ~dolphin~: File explorer
- ~swaylock-effects~: A simple lockscreen
- ~wl-clipboard~: Provides copying
- ~blueman~: Bluetooth connector
- ~grim~: Screenshot engine
- ~flameshot~: Screenshot utility; On Arch Linux, install ~flameshot-git~.
** Configuration
- ~lxappearance~: Used to configure GTK3 themes

View File

@ -28,12 +28,6 @@ Install and set the default shell to ~fish~.
** Utilities
- ~kando~: Pie menu
Note the complicated key binding in the ~kando~ menu configuration. This is
used for MacOS and desktop environments where some keys cannot bind to a
command (e.g. Wacom tablets)
- ~alacritty~: terminal emulator
Note that in order to set this as the default terminal, you may need to put

View File

@ -1,17 +0,0 @@
#+title: System
Helpful guides for system installation.
* Partition Schemes
Recommended partition scheme for directories:
#+begin_src
/
├ /boot # Same drive
├ /nix
├ /home # Different drive
#+end_src
** Encrypted Partitions
See [[https://wiki.archlinux.org/title/Dm-crypt/Mounting_at_login][DM-Crypt on Arch Wiki]]

View File

@ -257,13 +257,8 @@ numbers are disabled. For relative line numbers, set this to ~relative~.
(setq whitespace-line-column nil)
(display-line-numbers-mode)
)
(add-hook 'text-mode-hook #'custom/common-richtext-hook)
(setq
typescript-indent-level 3
css-indent-offset 3
sh-indentation 3
nxml-indent-offset 3
)
#+end_src
** Shells
@ -648,6 +643,47 @@ Add the necessary hooks for LilyPond mode.
(setq rustic-indent-offset standard-indent))
#+end_src
*** SCAD
#+begin_src emacs-lisp :tangle packages.el
(package! scad-mode)
#+end_src
#+begin_src emacs-lisp
(use-package! scad-mode
:after-call scad-mode
:config
(add-hook 'scad-mode-hook #'custom/common-program-hook))
(map! :after scad-mode
:localleader
:map scad-mode-map
:desc "Open" "o" #'scad-open
:desc "Export" "e" #'scad-export
:desc "Preview" "p" #'scad-preview
)
(map! :after scad-mode
:mode scad-preview-mode
:map scad-preview-mode-map
:desc "Size+" "+" #'scad-preview-size+
:desc "Size-" "-" #'scad-preview-size-
:desc "Distance+" "[" #'scad-preview-distance+
:desc "Distance-" "]" #'scad-preview-distance-
:desc "Toggle Projection" "p" #'scad-preview-projection
:desc "Translate x-" "h" #'scad-preview-translate-x-
:desc "Translate x+" "l" #'scad-preview-translate-x+
:desc "Translate y-" "j" #'scad-preview-translate-y-
:desc "Translate y+" "k" #'scad-preview-translate-y+
:desc "Translate z-" "n" #'scad-preview-translate-z-
:desc "Translate z+" "m" #'scad-preview-translate-z+
:desc "Rotate x-" "H" #'scad-preview-rotate-x-
:desc "Rotate x+" "L" #'scad-preview-rotate-x+
:desc "Rotate y-" "J" #'scad-preview-rotate-y-
:desc "Rotate y+" "K" #'scad-preview-rotate-y+
:desc "Rotate z-" "N" #'scad-preview-rotate-z-
:desc "Rotate z+" "M" #'scad-preview-rotate-z+
)
#+end_src
** Tools
*** Language Server Protocol (LSP)
@ -656,7 +692,7 @@ File path is removed from breadcrumb since it is provided by ~doom-modeline~ and
since it clutters the header-line.
#+begin_src emacs-lisp
(setq-hook! 'lsp-mode-hook
(setq-hook! lsp-mode
lsp-headerline-breadcrumb-enable t
lsp-headerline-breadcrumb-enable-symbol-numbers nil
lsp-headerline-breadcrumb-segments '(symbols)
@ -786,26 +822,3 @@ FIXME: Cleanup ~ein:markdown-mode-map~.
:desc "Move cell up" "M-k" 'ein:worksheet-move-cell-up
)
#+end_src
*** Telegram
#+begin_src emacs-lisp :tangle packages.el
(package! telega)
#+end_src
On Arch Linux, ~telegram-tdlib~ installs to
#+begin_src text :tangle no
telegram-tdlib /usr/
telegram-tdlib /usr/include/
telegram-tdlib /usr/include/td/...
telegram-tdlib /usr/lib/
telegram-tdlib /usr/lib/cmake/
telegram-tdlib /usr/lib/cmake/...
telegram-tdlib /usr/lib/libtdactor.a
telegram-tdlib /usr/lib/...
telegram-tdlib /usr/lib/pkgconfig/...
#+end_src
#+begin_src emacs-lisp
(setq telega-server-libs-prefix "/usr")
#+end_src

View File

@ -63,7 +63,7 @@
;;objed ; text object editing for the innocent
;;parinfer ; turn lisp into python, sort of
;;rotate-text ; cycle region at point between text candidates
;;snippets ; my elves. They type so I don't have to
snippets ; my elves. They type so I don't have to
;;word-wrap ; soft wrapping with language-aware indent
:emacs
@ -95,8 +95,8 @@
(eval +overlay) ; run code, run (also, repls)
;;gist ; interacting with github gists
lookup ; navigate your code and its documentation
(lsp +peek +eglot) ; M-x vscode
(magit +forge) ; a git porcelain for Emacs
(lsp +peek) ; M-x vscode
magit ; a git porcelain for Emacs
make ; run make tasks from Emacs
pass ; password manager for nerds
pdf ; pdf enhancements
@ -161,7 +161,7 @@
;;qt ; the 'cutest' gui framework ever
;;racket ; a DSL for DSLs
;;raku ; the artist formerly known as perl6
rest ; Emacs as a REST client
;;rest ; Emacs as a REST client
;;rst ; ReST in peace
;;(ruby +rails) ; 1.step {|i| p "Ruby is #{i.even? ? 'love' : 'life'}"}
(rust +lsp +tree-sitter) ; Fe2O3.unwrap().unwrap().unwrap().unwrap()

View File

@ -12,76 +12,32 @@ $workspace_hover: #2E5DFF;
$widget_bg: rgba(40, 40, 40, 0.5);
$weather: rgba(180, 220, 235, 0.8);
$weather_end: rgba(171, 233, 179, 0.8);
$trough: rgba(0, 0, 0, 0.5);
$media-progress-bg: rgba(224, 232, 224, 0.1);
$media-paused-progress-bg: rgba(224, 200, 200, 0.1);
$media: rgba(224, 232, 224, 0.7);
$media-paused: rgba(224, 200, 200, 0.7);
$media-stopped: rgba(200, 200, 200, 0.5);
// Dims the desktop background so status icons can be seen against darker wallpapers
$status_bg: rgba(0, 0, 0, 0.3);
$tray-bg: rgba(204, 204, 204, 0.3);
$tray: #c9cbff;
$keyboard: #f2cdcd;
$idle_inhibitor: #f28fad;
$volume: #fae3b0;
$microphone: #e8a2af;
$network: #f8bd96;
$backlight: #bd93f9;
// Unused in favour of bluetooth widget
$bluetooth: #88bbeb;
$cpu: #96cdfb;
$cpu-bg: rgba(150, 205, 251, 0.3);
// previously bluetooth colour
$memory: #caa9c7;
$memory-bg: rgba(202, 169, 199, 0.3);
$temperature-bg: rgba(180, 180, 180, 0.5);
$temperature: #b6b2b3;
$temperature-critical: #ff6666;
$battery: #ddb6f2;
$clock: rgba(171, 233, 179, 1.0);
$media: rgba(224, 232, 224, 0.7);
// Styles all scales
progressbar trough {
all: unset;
background-color: $trough;
border-radius: 5px;
min-height: 80px;
min-width: 2px;
//margin: .3rem 0 .3rem 0;
}
trough progress {
all: unset;
background-color: #00ff00; // Diagnostics colour
border-radius: 5px;
min-width: 2px;
}
scale trough {
all: unset;
background-color: $trough;
border-radius: 5px;
min-height: 80px;
min-width: 2px;
}
trough highlight {
all: unset;
background-color: #ff0000; // Diagnostics colour
border-radius: 5px;
}
trough slider {
background-color: transparent;
border-width: 0px;
border-radius: 10px;
}
$tray_background: rgba(204, 204, 204, 0.1);
$tray: #c9cbff;
$keyboard: #f2cdcd;
$language: #e8a2af;
$idle_inhibitor: #f28fad;
$audio: #fae3b0;
$backlight: #f8bd96;
$bluetooth: #caa9c7;
$network: #bd93f9;
$cpu: #96cdfb;
$memory: #88bbeb;
$temperature: #ddb6f2;
$temperature_critical: #ff6666;
$battery: #b6b2b3;
.vbar {
background-color: transparent;
font-size: 20px;
* { all: unset; }
.bar {
//background-color: transparent;
margin-left: 5px;
font-size: 15px;
font-family: monospace;
}
@ -90,16 +46,13 @@ trough slider {
}
.launcher {
all: unset;
font-size: 40px;
margin: -2px -5px 5px 0px;
margin: -7px -5px 15px -5px;
}
box .workspaces {
all: unset;
border-radius: 10px;
button {
all: unset;
font-size: 30px;
padding-left: 2px;
margin: -5px -5px -5px -5px;
@ -122,11 +75,6 @@ box .workspaces {
}
}
box .status {
background-color: $status_bg;
border-radius: 5px;
}
box .weather {
color: $weather;
margin-left: 2px;
@ -157,112 +105,63 @@ box .weather {
color: mix($weather_end, $weather, 100%);
}
}
.volume {
background-color: transparent;
color: $volume;
border-left: 2px solid $volume;
margin-bottom: 1px;
margin-top: 1px;
highlight {
background-color: $volume;
}
}
.microphone {
background-color: transparent;
color: $microphone;
border-left: 2px solid $microphone;
margin-bottom: 1px;
margin-top: 1px;
highlight {
background-color: $microphone;
}
box .clock {
background-color: $widget_bg;
border-radius: 5;
color: $clock;
margin-bottom: 30px;
padding: 2pt 2pt 2pt 2pt;
}
.media {
font-family: "Noto Serif";
min-height: 15px;
min-width: 15px;
font-size: 15px;
padding-left: 5px;
padding-right: 5px;
color: $media;
border: none;
.media-text-playing {
padding-left: 10px;
}
.media-text-paused {
padding-left: 10px;
color: $media-paused;
}
.media-text-stopped {
padding-left: 10px;
color: $media-stopped;
}
.media-playing {
background-color: $media-progress-bg;
}
.media-paused {
background-color: $media-paused-progress-bg;
color: $media-paused;
}
.media-stopped {
background-color: $media-paused-progress-bg;
color: $media-stopped;
}
}
box .temperature {
color: $temperature;
border-left: 2px solid $temperature-bg;
margin-bottom: 1px;
margin-top: 1px;
}
.temperature-icon {
padding-left: 5px;
font-size: 20px;
color: mix($temperature, transparent, 70%);
}
.temperature-progress-cpu trough progress {
background-color: mix($cpu, $temperature, 50%);
}
.temperature-progress-gpu trough progress {
background-color: $temperature;
}
.temperature-progress-critical trough progress {
background-color: $temperature-critical;
.status {
font-family: monospace;
}
box .cpu {
.progress {
background-color: $cpu-bg;
// Styles on classes (see eww.yuck for more information)
.status slider {
all: unset;
color: #ffd5cd;
}
color: $cpu;
border-left: 2px solid $cpu;
margin-bottom: 1px;
margin-top: 1px;
.metric scale trough highlight {
all: unset;
background-color: #D35D6E;
color: #000000;
border-radius: 10px;
}
box .memory {
.progress {
background-color: $memory-bg;
.metric scale trough {
all: unset;
background-color: #4e4e4e;
border-radius: 50px;
min-height: 3px;
min-width: 50px;
margin-left: 10px;
margin-right: 20px;
}
color: $memory;
border-left: 2px solid $memory;
margin-bottom: 1px;
margin-top: 1px;
.metric scale trough highlight {
all: unset;
background-color: #D35D6E;
color: #000000;
border-radius: 10px;
}
box .clock {
background-color: transparent;
color: $clock;
margin-top: 1px;
font-family: Ariel;
border-left: 2px solid $clock;
.icon {
color: mix($clock, transparent, 50%);
.metric scale trough {
all: unset;
background-color: #4e4e4e;
border-radius: 50px;
min-height: 3px;
min-width: 50px;
margin-left: 10px;
margin-right: 20px;
}
}
.systray {
//background-color: $tray_bg;
color: $tray;
border-left: 2px dotted $tray;
border-bottom: 2px dotted $tray-bg;
.label-ram {
font-size: large;
}

View File

@ -1,257 +1,95 @@
(defvar amixer "amixer -D pulse")
(defvar temperature-monitor "psensor")
(defvar eww "eww --config $HOME/.config/eww/bar")
(defwindow vbar
(defwindow bar
:exclusive true
:monitor 0
:windowtype "dock"
:geometry (geometry
:x "0%"
:y "0%"
:width "25px"
:height "99%"
:width "20px"
:height "100%"
:anchor "center left"
)
:reserve (struts :side "left" :distance "20px")
(centerbox
:orientation "v"
(box
:orientation "v" :space-evenly false :valign "start"
(label
:class "launcher"
:valign "start"
:width "20px" :height "20px"
:unindent true
:tooltip active-window-title
:text "☯")
(widget-workspaces)
)
(widget-media)
(box
:class "status"
:orientation "v" :space-evenly false :valign "end"
(widget-tray)
(widget-volume)
(widget-microphone)
(widget-temperature)
(widget-cpu)
(widget-memory)
(widget-clock)
(widget_bar))
(defwidget widget_bar []
(centerbox :orientation "v"
(box :orientation "v" :space-evenly false :valign "start"
(box :class "launcher" :valign "start" "☯")
(widget_workspaces)
)
(widget_weather :orientation "v")
(widget_clock)
;(media)
;(widget_status)
))
(defwidget widget-workspaces []
(defwidget widget_workspaces []
(box
:class "workspaces"
:orientation "v"
:space-evenly true
:spacing 10
:valign "start"
:valign "start" :spacing 10
:halign "center"
(for workspace in workspaces
(button
:class "${workspace.class}"
:onclick "scripts/compositor-control set-workspace ${workspace.id}"
:onclick "scripts/workspace.sh ${workspace.id}"
"${workspace.text}"))
))
(deflisten workspaces :initial "[]"
"scripts/compositor-control workspaces")
(deflisten active-window-title :initial ""
"scripts/compositor-control active-title")
"scripts/get-workspaces")
(defwidget widget-media []
; Weather
(defwidget widget_weather [orientation]
(box
:class "media"
:orientation "v"
:class "weather"
:orientation orientation
:space-evenly false
:valign "end"
:tooltip "${media-status == 'Paused' ? '' : media-status == 'Playing' ? '' : ''} ${media-position-text}"
(eventbox
:onclick "playerctl play-pause"
(circular-progress
:class { media-status == "Paused" ? "media-paused" : media-status == "Stopped" ? "media-stopped" : "media-playing" }
:width 20
:height 20
:thickness 5
:value {100.0 * media-position / (media-length / 1000000.0)}
:visible {media-current != "" && media-status != "Stopped"}
))
(label
:class { media-status == "Paused" ? "media-text-paused" : media-status == "Stopped" ? "media-text-stopped" : "media-text-playing" }
:angle 270
:xalign 0.5
:yalign 0.5
:justify "center"
:limit-width 120
:text "『${media-current}』"
)))
(deflisten media-current :initial ""
"playerctl --follow metadata --format '{{ artist }} / {{album}} / {{ title }}' || true")
(deflisten media-position-text :initial ""
"playerctl --follow metadata --format '{{duration(position)}} / {{duration(mpris:length)}}' || true")
(defpoll media-position
:interval "1s" :run-while {media-current != ""}
"playerctl position")
(deflisten media-length :initial 1
"playerctl --follow metadata mpris:length")
(deflisten media-status :initial "Stopped"
"playerctl --follow status")
(defwidget widget-volume []
(eventbox
:onhover "${EWW_CMD} update show-volume=true"
:onhoverlost "${EWW_CMD} update show-volume=false"
:onclick "${amixer} sset Master toggle"
(box
:class "volume"
:orientation "v"
:space-evenly "false"
:spacing "2"
:tooltip "Volume: ${current-volume}% [${current-volume-state}]"
(revealer :transition "slideup" :reveal show-volume
(scale
:class "scale"
:value current-volume
:orientation "v"
:flipped true
:marks true
:max 101
:min 0
:onchange "${amixer} sset Master {}%" ))
(label
:class "icon"
:width 25 :height 20
:xalign 0.5 :yalign 0.5
:noindent true
:text { current-volume-state == "off" ? "" : (current-volume > 50 ? "" : "") }))))
(defwidget widget-microphone []
(eventbox
:onhover "${EWW_CMD} update show-microphone=true"
:onhoverlost "${EWW_CMD} update show-microphone=false"
:onclick "${amixer} sset Capture toggle"
(box
:class "microphone"
:orientation "v"
:space-evenly "false"
:spacing "2"
:tooltip "Capture : ${current-microphone}% [${current-microphone-state}]"
(revealer :transition "slideup" :reveal show-microphone
(scale
:class "scale"
:value current-microphone
:orientation "v"
:flipped true
:marks true
:max 101
:min 0
:onchange "${amixer} sset Capture {}%" ))
(label
:class "icon"
:width 25 :height 20
:xalign 0.5 :yalign 0.5
:noindent true
:text { current-microphone-state == "off" ? "" : "" }))))
(defpoll current-volume :interval "1s" "amixer sget Master | grep 'Left:' | awk -F'[][]' '{ print $2 }' | tr -d '%'")
(defpoll current-volume-state :interval "1s" "amixer sget Master | grep 'Left:' | awk -F'[][]' '{ print $4 }'")
(defpoll current-microphone :interval "1s" "amixer sget Capture | grep 'Left:' | awk -F'[][]' '{ print $2 }' | tr -d '%'")
(defpoll current-microphone-state :interval "1s" "amixer sget Capture | grep 'Left:' | awk -F'[][]' '{ print $4 }'")
(defvar show-volume false)
(defvar show-microphone false)
; FIXME: Maybe use a env var here instead
(defvar cpu-temp-key "ASUS_WMI_SENSORS_CPU_TEMPERATURE")
(defvar gpu-temp-key "AMDGPU_MEM")
(defvar temperature-threshold 80)
(defwidget widget-temperature []
(eventbox
:onclick temperature-monitor
(overlay
(box
:class "temperature"
:orientation "h"
:tooltip "CPU ${EWW_TEMPS[cpu-temp-key]}°C; GPU ${EWW_TEMPS[gpu-temp-key]}°C"
:space-evenly true
:width 25
:halign "" :valign "center"
(progress
:class { EWW_TEMPS[cpu-temp-key] > temperature-threshold ? "temperature-progress-critical" : "temperature-progress-cpu" }
:halign "center" :valign "center"
:flipped "true"
:width 3
:orientation "v"
:value {EWW_TEMPS[cpu-temp-key]}
)
(progress
:class { EWW_TEMPS[gpu-temp-key] > temperature-threshold ? "temperature-progress-critical" : "temperature-progress-gpu" }
:halign "center" :valign "center"
:flipped "true"
:width 3
:orientation "v"
:value {EWW_TEMPS[gpu-temp-key]}
)
)
(label
:valign "end"
:class "temperature-icon"
:text "")
)))
(defwidget widget-cpu []
(box
:class "cpu"
:orientation "v"
(circular-progress
:class "progress"
:width 25
:height 25
:thickness 4
:tooltip " ${round(EWW_CPU.avg, 2)}%"
:value {EWW_CPU.avg}
)))
(defwidget widget-memory []
(box
:class "memory"
:orientation "v"
(circular-progress
:class "progress"
:width 25
:height 25
:thickness 4
:tooltip " ${round(100 * EWW_RAM.used_mem / EWW_RAM.total_mem, 2)}%"
:value {EWW_RAM.used_mem_perc}
)))
(defwidget widget-tray []
(systray
:class "systray"
:orientation "v"
:space-evenly true
:icon-size 25
:prepend-new true
:valign "start"
:halign "center"
:spacing 10
(button :onclick "scripts/popup weather" "W")
))
(defwidget widget-clock []
(defpoll weather_text :initial "" :interval "180s"
"curl --max-time 2 wttr.in")
(defwindow weather
:geometry (geometry :x "70px"
:y "50%"
:width "270px"
:height "60px")
(box weather_text))
(defwindow calendar
:geometry (geometry :x "70px"
:y "65%"
:width "270px"
:height "60px")
(cal))
(defwidget widget_clock []
(box
:class "clock"
:orientation "v"
:valign "end"
:spacing 1
:width 20
:tooltip {formattime(EWW_TIME, "%F (%a) %T [%Z]")}
;(box :class "icon" "")
(label :text "${clock.m}")
(label :text "${clock.d}")
(label :class "icon" :text "")
(label :text "${clock.H}")
(label :text "${clock.M}")
(box :class "icon" "")
"${clock.H}" "${clock.M}"
(box :class "icon" "")
"${clock.m}" "${clock.d}"
))
(defpoll clock :initial "{}" :interval "10s"
"date '+{\"H\":\"%H\", \"M\":\"%M\", \"d\":\"%d\", \"m\":\"%m\", \"b\":\"%b\", \"Y\":\"%Y\"}'")
; Calendar
(defwindow calendar
@ -277,25 +115,56 @@
(defpoll calendar_year :interval "10h"
"date '+%Y'")
; Weather
; Unused widgets
(defwidget widget-weather [orientation]
(defwidget widget_status []
(box
:class "weather"
:orientation orientation
:class "status"
:orientation "v"
:space-evenly false
:valign "end"
(metric :label ""
:value volume
:onchange "amixer -D pulse sset Master {}%")
(metric :label ""
:value {EWW_RAM.used_mem_perc}
:onchange "")
(widget_clock)))
(defwidget widget_media []
(box :class "media"
:orientation "v"
:space-evenly false
:valign "center"
{music_listener != "" ? "『${music_listener}』" : ""}
(box :class "music_status"
:orientation "h"
:space-evenly false
:valign "start"
:halign "center"
:spacing 10
(button :onclick "scripts/popup weather" "W")
{music_listener != "" ? "${music_status_listener}" : ""})
))
(defpoll weather-text :initial "" :interval "180s"
"curl --max-time 2 wttr.in")
(deflisten music_listener :initial ""
"playerctl --follow metadata --format '{{ artist }} / {{album}} / {{ title }}' || true")
(deflisten music_status_listener :initial ""
"playerctl --follow metadata --format '{{duration(position)}}/{{duration(mpris:length)}}' || true")
(defwindow weather
:geometry (geometry :x "70px"
:y "50%"
:width "270px"
:height "60px")
(box weather-text))
(defwidget metric [label value onchange]
(box
:orientation "v"
:class "metric"
:space-evenly false
(box :class "label" label)
(circular-progress :value value)
;(scale
; :min 0 :max 101
; :active {onchange != ""}
; :orientation "v"
; :value value
; :onchange onchange)
))
(defpoll volume :initial 0 :interval "1s"
"scripts/get-volume.sh")

View File

@ -1,193 +0,0 @@
#!/usr/bin/env python
"""
Utility to generate the workspace buttons for eww bar widget.
Currently it fetches information from monitor 0. If all monitors have
synchronised workspaces this should not be a problem.
"""
import os, sys, socket, json, subprocess
from typing import Optional, Self, override
WORKSPACE_ICONS = [
(1, "☱"),
(2, "☲"),
(3, "☳"),
(4, "☴"),
(5, "☵"),
(6, "☶"),
(7, "☷"),
(8, "☰"),
]
EXTRA_WORKSPACE_ICONS = [
(0, "⚌"),
(1, "⚍"),
(2, "⚎"),
(3, "⚏"),
]
Workspaces = dict[int, bool]
def get_widgets(workspaces: Workspaces) -> str:
"""
Create widget sexp from workspace information
"""
def get_class(k):
key = workspaces.get(k, None)
if key is None:
return "inactive"
elif key:
return "focused"
else:
return "active"
buttons = [
f'"id": "{k}", "text": "{icon}", "class": "{get_class(k)}"'
for k, icon in WORKSPACE_ICONS
]
buttons = ", ".join(["{" + b + "}" for b in buttons])
return "[" + buttons + "]"
class CompositorHandler:
@staticmethod
def create() -> Optional[Self]:
"""
Main entry point, detects the type of compositor used
"""
if signature := os.environ.get("HYPRLAND_INSTANCE_SIGNATURE", None):
# See https://wiki.hyprland.org/IPC/
xdg_runtime_dir = os.environ["XDG_RUNTIME_DIR"]
socket_addr = f"/{xdg_runtime_dir}/hypr/{signature}/.socket2.sock"
return HyprlandHandler(socket_addr)
if signature := os.environ.get("SWAYSOCK", None):
return SwayHandler()
return None
def listen_workspaces(self):
pass
def listen_active_window_title(self):
pass
def set_workspace(self, i):
pass
class HyprlandHandler(CompositorHandler):
def __init__(self, socket_addr):
self.socket_addr = socket_addr
def listen(self, callback):
sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
try:
sock.connect(self.socket_addr)
except Exception as e:
print(f"Could not connect to {self.socket_addr}", file=sys.stderr)
raise e
callback(data=None)
while sock:
data = sock.recv(1024)
callback(data)
@staticmethod
def get_workspace_info() -> Workspaces:
workspaces = json.loads(os.popen("hyprctl workspaces -j").read())
monitors = json.loads(os.popen("hyprctl monitors -j").read())
if not monitors:
print("No monitor found!", file=sys.stderr)
return []
monitor, active = [
(m["name"], m["activeWorkspace"]["id"]) for m in monitors if m["id"] == 0
][0]
workspaces = {
w["id"]: w["id"] == active
for w in workspaces if w["monitor"] == monitor
}
return workspaces
@override
def listen_workspaces(self):
def callback(data):
if data is None or b"workspace" in data:
workspace_info = HyprlandHandler.get_workspace_info()
print(get_widgets(workspace_info), flush=True)
self.listen(callback)
@override
def listen_active_window_title(self):
def callback(data):
if data is None:
active_window = json.loads(os.popen("hyprctl activewindow -j").read())
print(active_window["title"], flush=True)
return
prefix = b"activewindow>>"
payload = next((l[len(prefix):]
for l in reversed(data.split(b'\n'))
if l.startswith(prefix)), None)
if payload is None:
return
if b',' in payload:
_, title = payload.decode('utf-8').split(',', 1)
print(title, flush=True)
else:
print("", flush=True)
self.listen(callback)
@override
def set_workspace(self, i):
result = os.popen(f"hyprctl dispatch workspace {i}").read()
if result != "ok\n":
raise RuntimeError(f"Failed to set workspace: {result}")
# FIXME: Set the wallpaper too.
# ~/.config/hypr/wallpaper.sh $1
class SwayHandler(CompositorHandler):
@staticmethod
def get_workspace_info() -> Workspaces:
workspaces = json.loads(os.popen("swaymsg --raw -t get_workspaces").read())
# REVIEW: This has not been tested on a multi-monitor setup.
workspaces = {
int(w["num"]): w["focused"]
for w in workspaces
}
return workspaces
def listen_workspaces(self):
with subprocess.Popen(["swaymsg", "-t", "subscribe", "-m", '["workspace"]'], stdout=subprocess.PIPE) as proc:
workspace_info = SwayHandler.get_workspace_info()
print(get_widgets(workspace_info), flush=True)
while line := proc.stdout.readline():
# FIXME: Use the swaymsg subscribe output
# Not needed
#info = json.loads(line)
workspace_info = SwayHandler.get_workspace_info()
print(get_widgets(workspace_info), flush=True)
if __name__ == "__main__":
if len(sys.argv) == 1:
print("Usage: wm-control workspaces|title")
handler = CompositorHandler.create()
if handler is None:
print("No compositor found.", file=sys.stderr)
key = sys.argv[1]
if key == "workspaces":
handler.listen_workspaces()
elif key == "active-title":
handler.listen_active_window_title()
elif key == "set-workspace":
if len(sys.argv) != 3:
print(f"Set workspace must be accompanied by an argument", file=sys.stderr)
arg = sys.argv[2]
handler.set_workspace(arg)
else:
print(f"Unknown key: {key}", file=sys.stderr)

3
eww/bar/scripts/get-volume.sh Executable file
View File

@ -0,0 +1,3 @@
#!/bin/bash
amixer -D pulse sget Master | grep 'Left:' | awk -F'[][]' '{ print $2 }' | tr -d '%' | head -1

126
eww/bar/scripts/get-workspaces Executable file
View File

@ -0,0 +1,126 @@
#!/usr/bin/env python
"""
Utility to generate the workspace buttons for eww bar widget.
Currently it fetches information from monitor 0. If all monitors have
synchronised workspaces this should not be a problem.
"""
import os, sys, socket, json, subprocess
from typing import Optional
WORKSPACE_ICONS = [
(1, "☱"),
(2, "☲"),
(3, "☳"),
(4, "☴"),
(5, "☵"),
(6, "☶"),
(7, "☷"),
(8, "☰"),
]
EXTRA_WORKSPACE_ICONS = [
(0, "⚌"),
(1, "⚍"),
(2, "⚎"),
(3, "⚏"),
]
Workspaces = dict[int, bool]
def get_widgets(workspaces: Workspaces) -> str:
"""
Create widget sexp from workspace information
"""
def get_class(k):
key = workspaces.get(k, None)
if key is None:
return "inactive"
elif key:
return "focused"
else:
return "active"
buttons = [
f'"id": "{k}", "text": "{icon}", "class": "{get_class(k)}"'
for k, icon in WORKSPACE_ICONS
]
buttons = ", ".join(["{" + b + "}" for b in buttons])
return "[" + buttons + "]"
# Compositor specific information
def hypr_get_workspace_info() -> Workspaces:
workspaces = json.loads(os.popen("hyprctl workspaces -j").read())
monitors = json.loads(os.popen("hyprctl monitors -j").read())
if not monitors:
print("No monitor found!", file=sys.stderr)
return []
monitor, active = [
(m["name"], m["activeWorkspace"]["id"]) for m in monitors if m["id"] == 0
][0]
workspaces = {
w["id"]: w["id"] == active
for w in workspaces if w["monitor"] == monitor
}
return workspaces
def hypr_listen(socket_addr: str):
sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
try:
sock.connect(socket_addr)
except:
print(f"Could not connect to {socket_addr}", file=sys.stderr)
raise
# The flush here is very important since python by default buffers!
workspace_info = hypr_get_workspace_info()
print(get_widgets(workspace_info), flush=True)
while sock:
data = sock.recv(1024)
if b"workspace" in data:
workspace_info = hypr_get_workspace_info()
print(get_widgets(workspace_info), flush=True)
def sway_get_workspace_info() -> Workspaces:
workspaces = json.loads(os.popen("swaymsg --raw -t get_workspaces").read())
# REVIEW: This has not been tested on a multi-monitor setup.
workspaces = {
int(w["num"]): w["focused"]
for w in workspaces
}
return workspaces
def sway_listen():
proc = subprocess.Popen(
["swaymsg", "-t", "subscribe", "-m", '["workspace"]'], stdout=subprocess.PIPE
)
workspace_info = sway_get_workspace_info()
print(get_widgets(workspace_info), flush=True)
while line := proc.stdout.readline():
# Not needed
info = json.loads(line)
workspace_info = sway_get_workspace_info()
print(get_widgets(workspace_info), flush=True)
def detect():
"""
Main entry point, detects the type of compositor used
"""
if signature := os.environ.get("HYPRLAND_INSTANCE_SIGNATURE", None):
socket_addr = f"/tmp/hypr/{signature}/.socket2.sock"
return hypr_listen(socket_addr)
if signature := os.environ.get("SWAYSOCK", None):
return sway_listen()
print("No compositor found.", file=sys.stderr)
return None
if __name__ == "__main__":
detect()

4
eww/bar/scripts/workspace.sh Executable file
View File

@ -0,0 +1,4 @@
#!/bin/bash
hyprctl dispatch workspace $1
~/.config/hypr/wallpaper.sh $1

View File

@ -40,54 +40,39 @@ end
function fish_prompt
# set_color white
# printf '['
# echo -n '['
set -l FishStatusCodeCache $status
set_color -o 00FF00 -b 333333
printf (whoami)
set_color -o 00FF00
echo -n (whoami)
set_color normal
set_color -b 333333
printf '@'
echo -n '@'
set_color AFD75F
printf (prompt_hostname)
echo -n (hostname)
set_color white
printf ':'
echo -n ':'
set_color 8787FF
printf (prompt_abbr $PWD)
echo -n (prompt_abbr $PWD)
set -l vcs_prompt (fish_vcs_prompt)
if test -n "$vcs_prompt"
printf " "
set_color 5F87D7
printf '\uf126'
printf (fish_vcs_prompt)
end
echo -n (__fish_vcs_prompt)
if test $FishStatusCodeCache -eq 0
#set_color 18C412
else
set_color E84505
printf " [$FishStatusCodeCache]"
echo -n " [$FishStatusCodeCache]"
end
# set_color white
# printf "]"
# echo -n "]"
set_color 999999
printf " "
printf (date '+%Y/%m/%d %H:%M:%S')
printf "\n"
#set_color -o white
#printf "> "
set_color normal
set_color -o green
printf "⮀ "
set_color -o white
echo -n "> "
set_color normal
end

View File

@ -0,0 +1,8 @@
#!/bin/fish
function fish_right_prompt
set_color grey
printf (date '+%H:%M:%S')
set_color normal
end

View File

@ -10,7 +10,8 @@ monitor=,preferred,auto,auto
# See https://wiki.hyprland.org/Configuring/Keywords/ for more
exec-once = hyprpaper & swaync & fcitx5 & flameshot & eww --config ~/.config/eww/bar open vbar & kando
# waybar is executed after eww to have the intersection effect at top left
exec-once = hyprpaper & swaync & fcitx5 & eww --config ~/.config/eww/bar open bar & waybar
# Source a file (multi-file configs)
# source = ~/.config/hypr/myColors.conf
@ -51,6 +52,10 @@ decoration {
# See https://wiki.hyprland.org/Configuring/Variables/ for more
rounding = 10
blur = yes
blur_size = 3
blur_passes = 1
blur_new_optimizations = on
drop_shadow = yes
shadow_range = 4
@ -84,7 +89,7 @@ dwindle {
master {
# See https://wiki.hyprland.org/Configuring/Master-Layout/ for more
#new_is_master = true
new_is_master = true
}
gestures {
@ -130,8 +135,8 @@ bind = $mainMod, space, exec, wofi --show drun -I -G
#bind = $mainMod, X, exec, hyprctl switchxkblayout wooting-wootingtwo next
# code:107 = PrintSc
bind = $mainMod, code:107, exec, flameshot gui
#bind = $mainMod SHIFT, code:107, exec, grimblast copy area
bind = $mainMod, code:107, exec, grimblast copy screen
bind = $mainMod SHIFT, code:107, exec, grimblast copy area
# Move focus with mainMod + arrow keys
bind = $mainMod, left, movefocus, l
@ -142,9 +147,6 @@ bind = $mainMod SHIFT, left, movewindow, l
bind = $mainMod SHIFT, right, movewindow, r
bind = $mainMod SHIFT, up, movewindow, u
bind = $mainMod SHIFT, down, movewindow, d
bind = , SUPER_R, global, kando:main
# F13
bind = , code:191, global, kando:edit
# Workspaces
$w1 = ~/.config/hypr/wallpaper.sh 1
@ -197,12 +199,3 @@ bind = $mainMod, mouse_up, workspace, e-1
# Move/resize windows with mainMod + LMB/RMB and dragging
bindm = $mainMod, mouse:272, movewindow
bindm = $mainMod, mouse:273, resizewindow
bind = SUPER,Tab,cyclenext, # change focus to another window
bind = SUPER,Tab,bringactivetotop, # bring it to the top
# Functional keys
bindl=, XF86AudioMute, exec, amixer set Master toggle
bindl=, XF86AudioPlay, exec, playerctl play-pause
bindl=, XF86AudioNext, exec, playerctl next
bindl=, XF86AudioPrev, exec, playerctl previous

2
kando/.gitignore vendored
View File

@ -1,2 +0,0 @@
/Singleton*
/session

View File

@ -1,15 +0,0 @@
{
"menuTheme": "neon-lights",
"darkMenuTheme": "default",
"menuThemeColors": {
"neon-lights": {
"glow-color": "rgba(168, 255, 196, 1)",
"connector-color": "rgba(173, 222, 255, 0.75)"
}
},
"darkMenuThemeColors": {},
"enableDarkModeForMenuThemes": false,
"sidebarVisible": false,
"enableVersionCheck": true,
"zoomFactor": 1
}

View File

@ -1,429 +0,0 @@
{
"menus": [
{
"shortcut": "Control+Alt+Shift+U",
"shortcutID": "main",
"centered": false,
"root": {
"type": "submenu",
"name": "Main",
"icon": "award_star",
"iconTheme": "material-symbols-rounded",
"children": [
{
"type": "submenu",
"name": "Apps",
"icon": "apps",
"iconTheme": "material-symbols-rounded",
"children": [
{
"type": "command",
"data": {
"command": "x-www-browser"
},
"name": "Web Browser",
"icon": "globe",
"iconTheme": "material-symbols-rounded"
},
{
"type": "command",
"data": {
"command": "xdg-email"
},
"name": "E-Mail",
"icon": "mail",
"iconTheme": "material-symbols-rounded"
},
{
"type": "command",
"data": {
"command": "gimp"
},
"name": "GIMP",
"icon": "gimp",
"iconTheme": "simple-icons"
},
{
"type": "command",
"data": {
"command": "xdg-open ~"
},
"name": "File Browser",
"icon": "folder_shared",
"iconTheme": "material-symbols-rounded"
},
{
"type": "command",
"data": {
"command": "x-terminal-emulator"
},
"name": "Terminal",
"icon": "terminal",
"iconTheme": "material-symbols-rounded"
}
]
},
{
"type": "submenu",
"name": "Web Links",
"icon": "public",
"iconTheme": "material-symbols-rounded",
"children": [
{
"type": "uri",
"data": {
"uri": "https://www.google.com"
},
"name": "Google",
"icon": "google",
"iconTheme": "simple-icons"
},
{
"type": "uri",
"data": {
"uri": "https://github.com/kando-menu/kando"
},
"name": "Kando on GitHub",
"icon": "github",
"iconTheme": "simple-icons"
},
{
"type": "uri",
"data": {
"uri": "https://ko-fi.com/schneegans"
},
"name": "Kando on Ko-fi",
"icon": "kofi",
"iconTheme": "simple-icons"
},
{
"type": "uri",
"data": {
"uri": "https://www.youtube.com/@simonschneegans"
},
"name": "Kando on YouTube",
"icon": "youtube",
"iconTheme": "simple-icons"
},
{
"type": "uri",
"data": {
"uri": "https://discord.gg/hZwbVSDkhy"
},
"name": "Kando on Discord",
"icon": "discord",
"iconTheme": "simple-icons"
}
]
},
{
"type": "hotkey",
"data": {
"hotkey": "ControlLeft+AltLeft+ArrowRight",
"delayed": false
},
"name": "Next Workspace",
"icon": "arrow_forward",
"iconTheme": "material-symbols-rounded"
},
{
"type": "submenu",
"name": "Clipboard",
"icon": "assignment",
"iconTheme": "material-symbols-rounded",
"children": [
{
"type": "hotkey",
"data": {
"hotkey": "ControlLeft+KeyV",
"delayed": true
},
"name": "Paste",
"icon": "content_paste_go",
"iconTheme": "material-symbols-rounded",
"angle": 90
},
{
"type": "hotkey",
"data": {
"hotkey": "ControlLeft+KeyC",
"delayed": true
},
"name": "Copy",
"icon": "content_copy",
"iconTheme": "material-symbols-rounded"
},
{
"type": "hotkey",
"data": {
"hotkey": "ControlLeft+KeyX",
"delayed": true
},
"name": "Cut",
"icon": "cut",
"iconTheme": "material-symbols-rounded"
}
]
},
{
"type": "submenu",
"name": "Audio",
"icon": "play_circle",
"iconTheme": "material-symbols-rounded",
"children": [
{
"type": "hotkey",
"data": {
"hotkey": "MediaTrackNext",
"delayed": false
},
"name": "Next Track",
"icon": "skip_next",
"iconTheme": "material-symbols-rounded",
"angle": 90
},
{
"type": "hotkey",
"data": {
"hotkey": "MediaPlayPause",
"delayed": false
},
"name": "Play / Pause",
"icon": "play_pause",
"iconTheme": "material-symbols-rounded"
},
{
"type": "hotkey",
"data": {
"hotkey": "AudioVolumeMute",
"delayed": false
},
"name": "Mute",
"icon": "music_off",
"iconTheme": "material-symbols-rounded"
},
{
"type": "hotkey",
"data": {
"hotkey": "MediaTrackPrevious",
"delayed": false
},
"name": "Previous Track",
"icon": "skip_previous",
"iconTheme": "material-symbols-rounded",
"angle": 270
}
]
},
{
"type": "submenu",
"name": "Windows",
"icon": "select_window",
"iconTheme": "material-symbols-rounded",
"children": [
{
"type": "hotkey",
"data": {
"hotkey": "MetaLeft+ArrowUp",
"delayed": true
},
"name": "Toggle Maximize",
"icon": "open_in_full",
"iconTheme": "material-symbols-rounded",
"angle": 0
},
{
"type": "hotkey",
"data": {
"hotkey": "MetaLeft+ArrowRight",
"delayed": true
},
"name": "Tile Right",
"icon": "text_select_jump_to_end",
"iconTheme": "material-symbols-rounded",
"angle": 90
},
{
"type": "hotkey",
"data": {
"hotkey": "AltLeft+F4",
"delayed": true
},
"name": "Close Window",
"icon": "cancel_presentation",
"iconTheme": "material-symbols-rounded"
},
{
"type": "hotkey",
"data": {
"hotkey": "MetaLeft+ArrowLeft",
"delayed": true
},
"name": "Tile Left",
"icon": "text_select_jump_to_beginning",
"iconTheme": "material-symbols-rounded",
"angle": 270
}
]
},
{
"type": "hotkey",
"data": {
"hotkey": "ControlLeft+AltLeft+ArrowLeft",
"delayed": false
},
"name": "Previous Workspace",
"icon": "arrow_back",
"iconTheme": "material-symbols-rounded"
},
{
"type": "submenu",
"name": "Bookmarks",
"icon": "folder_special",
"iconTheme": "material-symbols-rounded",
"children": [
{
"type": "command",
"data": {
"command": "xdg-open \"$(xdg-user-dir DOWNLOAD)\""
},
"name": "Downloads",
"icon": "download",
"iconTheme": "material-symbols-rounded"
},
{
"type": "command",
"data": {
"command": "xdg-open \"$(xdg-user-dir VIDEOS)\""
},
"name": "Videos",
"icon": "video_camera_front",
"iconTheme": "material-symbols-rounded"
},
{
"type": "command",
"data": {
"command": "xdg-open \"$(xdg-user-dir PICTURES)\""
},
"name": "Pictures",
"icon": "imagesmode",
"iconTheme": "material-symbols-rounded"
},
{
"type": "command",
"data": {
"command": "xdg-open \"$(xdg-user-dir DOCUMENTS)\""
},
"name": "Documents",
"icon": "text_ad",
"iconTheme": "material-symbols-rounded"
},
{
"type": "command",
"data": {
"command": "xdg-open \"$(xdg-user-dir DESKTOP)\""
},
"name": "Desktop",
"icon": "desktop_windows",
"iconTheme": "material-symbols-rounded"
},
{
"type": "command",
"data": {
"command": "xdg-open ~"
},
"name": "Home",
"icon": "home",
"iconTheme": "material-symbols-rounded"
},
{
"type": "command",
"data": {
"command": "xdg-open \"$(xdg-user-dir MUSIC)\""
},
"name": "Music",
"icon": "music_note",
"iconTheme": "material-symbols-rounded"
}
]
}
]
}
},
{
"root": {
"type": "submenu",
"name": "Edit",
"icon": "trip_origin",
"iconTheme": "material-symbols-rounded",
"children": [
{
"type": "hotkey",
"data": {
"hotkey": "ControlLeft+KeyZ",
"delayed": true
},
"name": "Undo",
"icon": "undo",
"iconTheme": "material-symbols-rounded"
},
{
"type": "hotkey",
"data": {
"hotkey": "ControlLeft+ShiftLeft+KeyZ",
"delayed": true
},
"name": "Redo",
"icon": "redo",
"iconTheme": "material-symbols-rounded"
},
{
"type": "hotkey",
"data": {
"hotkey": "ControlLeft+KeyR",
"delayed": true
},
"name": "Remesh",
"icon": "background_grid_small",
"iconTheme": "material-symbols-rounded"
},
{
"type": "hotkey",
"data": {
"hotkey": "F3",
"delayed": true
},
"name": "Search",
"icon": "search",
"iconTheme": "material-symbols-rounded"
},
{
"type": "hotkey",
"data": {
"hotkey": "ShiftLeft+Space",
"delayed": true
},
"name": "Brush",
"icon": "brush",
"iconTheme": "material-symbols-rounded"
},
{
"type": "hotkey",
"data": {
"hotkey": "",
"delayed": true
},
"name": "Save",
"icon": "save",
"iconTheme": "material-symbols-rounded"
}
]
},
"shortcut": "Control+Alt+Shift+Y",
"shortcutID": "edit",
"centered": false,
"anchored": false
}
],
"templates": []
}

View File

@ -3,7 +3,7 @@ set $term alacritty
# Launcher
set $menu wofi --show drun -I -G
set $lock swaylock
set $screenshot_full gscreenshot -s -c
set $screenshot_full gscreenshot -s
set $screenshot_area /usr/share/swayfx/scripts/grimshot copy area
xwayland enable

View File

@ -61,6 +61,3 @@ bindsym $mod+Shift+5 move container to workspace number 5; workspace number 5
bindsym $mod+Shift+6 move container to workspace number 6; workspace number 6
bindsym $mod+Shift+7 move container to workspace number 7; workspace number 7
bindsym $mod+Shift+8 move container to workspace number 8; workspace number 8
bindsym $mod+Shift+f opacity 1.0
bindsym $mod+Shift+g opacity 0.9

View File

@ -1,9 +1,9 @@
exec /usr/share/swayfx/scripts/inactive-windows-transparency --opacity 0.8
exec_always eww --config ~/.config/eww/bar open vbar
#bar {
# position top
# swaybar_command waybar
#}
exec_always eww --config ~/.config/eww/bar open bar
bar {
position top
swaybar_command waybar
}
exec_always multibg-sway ~/.config/sway/wallpapers
exec swaync
exec fcitx5