diff --git a/README.md b/README.md index 23d3df5..b41271b 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@ include_toc: true #### Programs -# TODO: add a file manager +# TODO: add a file manager and desktop manager | Name | Type | | -----------------: | :------------------ | @@ -23,6 +23,7 @@ include_toc: true | Neovim | Text editor | | Firefox | Web browser | | qBittorrent | Torrent client | +| mpv | Video player | | LibreOffice | Office suite | | GIMP | Photo editor | | Inkscape | Vector image editor | diff --git a/common/home.nix b/common/home.nix index b2de5c3..df4f0c4 100644 --- a/common/home.nix +++ b/common/home.nix @@ -1,4 +1,4 @@ -{ pkgs, config, inputs, ... }: { +{ pkgs, config, ... }: { home = { username = "avery"; homeDirectory = "/home/avery"; diff --git a/common/nixos.nix b/common/nixos.nix index 9676bb7..26ab9f5 100644 --- a/common/nixos.nix +++ b/common/nixos.nix @@ -17,6 +17,7 @@ users = { defaultUserShell = pkgs.zsh; users.avery = { + description = "Avery"; extraGroups = [ "wheel" ]; isNormalUser = true; hashedPasswordFile = config.sops.secrets.avery_password.path; diff --git a/common/zsh/powerline10k/p10k.zsh b/common/zsh/powerline10k/p10k.zsh index 503b77c..5b3f90b 100644 --- a/common/zsh/powerline10k/p10k.zsh +++ b/common/zsh/powerline10k/p10k.zsh @@ -67,7 +67,7 @@ typeset -g POWERLEVEL9K_EMPTY_LINE_RIGHT_PROMPT_FIRST_SEGMENT_START_SYMBOL='%{%}' fi - typeset -g POWERLEVEL9K_BACKGROUND="#181825" + typeset -g POWERLEVEL9K_BACKGROUND="black" typeset -g POWERLEVEL9K_LEFT_SUBSEGMENT_SEPARATOR='' typeset -g POWERLEVEL9K_RIGHT_SUBSEGMENT_SEPARATOR='' @@ -93,11 +93,11 @@ typeset -g POWERLEVEL9K_PROMPT_CHAR_LEFT_PROMPT_FIRST_SEGMENT_START_SYMBOL= typeset -g POWERLEVEL9K_PROMPT_CHAR_LEFT_{LEFT,RIGHT}_WHITESPACE= - typeset -g POWERLEVEL9K_DIR_FOREGROUND="#89dceb" + typeset -g POWERLEVEL9K_DIR_FOREGROUND="blue" typeset -g POWERLEVEL9K_SHORTEN_STRATEGY=truncate_to_unique typeset -g POWERLEVEL9K_SHORTEN_DELIMITER= - typeset -g POWERLEVEL9K_DIR_SHORTENED_FOREGROUND="#89dceb" - typeset -g POWERLEVEL9K_DIR_ANCHOR_FOREGROUND="#74c7ec" + typeset -g POWERLEVEL9K_DIR_SHORTENED_FOREGROUND="blue" + typeset -g POWERLEVEL9K_DIR_ANCHOR_FOREGROUND="blue" typeset -g POWERLEVEL9K_DIR_ANCHOR_BOLD=true local anchor_files=( .bzr @@ -544,7 +544,7 @@ typeset -g POWERLEVEL9K_WIFI_FOREGROUND=68 - typeset -g POWERLEVEL9K_TIME_FOREGROUND="#cdd6f4" + typeset -g POWERLEVEL9K_TIME_FOREGROUND="white" typeset -g POWERLEVEL9K_TIME_FORMAT='%D{%H:%M:%S}' typeset -g POWERLEVEL9K_TIME_UPDATE_ON_COMMAND=false typeset -g POWERLEVEL9K_TIME_PREFIX='%F{#bac2de}at ' diff --git a/hosts/totsugeki/home-manager/desktop/ags/config/assets/gpu-symbolic.svg b/hosts/totsugeki/home-manager/desktop/ags/config/assets/gpu-symbolic.svg new file mode 100644 index 0000000..52c38c4 --- /dev/null +++ b/hosts/totsugeki/home-manager/desktop/ags/config/assets/gpu-symbolic.svg @@ -0,0 +1,91 @@ + + + + + + + + + + + + + + + + diff --git a/hosts/totsugeki/home-manager/desktop/ags/config/assets/ram-custom-symbolic.svg b/hosts/totsugeki/home-manager/desktop/ags/config/assets/ram-custom-symbolic.svg index 4c186ff..b7c40d1 100644 --- a/hosts/totsugeki/home-manager/desktop/ags/config/assets/ram-custom-symbolic.svg +++ b/hosts/totsugeki/home-manager/desktop/ags/config/assets/ram-custom-symbolic.svg @@ -1,10 +1,12 @@ diff --git a/hosts/totsugeki/home-manager/desktop/ags/config/config.js b/hosts/totsugeki/home-manager/desktop/ags/config/config.js index 2ce3452..f06b4cc 100644 --- a/hosts/totsugeki/home-manager/desktop/ags/config/config.js +++ b/hosts/totsugeki/home-manager/desktop/ags/config/config.js @@ -1,13 +1,17 @@ -import cpu from "./services/cpu.js"; -import ram from "./services/ram.js"; +import { ProfilePicture } from "./widgets/bar/profile-picture.js"; +import { + ProcessorUsage, + MemoryUsage, + graphics_card_usage, + volume_widget, +} from "./widgets/bar/system-stats.js"; +import { extended_bar } from "./widgets/bar-extended/extended-bar.js"; import { launcher } from "./widgets/launcher/launcher.js"; -const { speaker } = await Service.import("audio"); +import { popup_clock } from "./widgets/popup-clock/popup-clock.js"; +import { date } from "./state.js"; const mpris = await Service.import("mpris"); const sway = await Service.import("sway"); const players = mpris.bind("players"); -const date = Variable("", { - poll: [1000, 'date "+%H %M %S %b %e"'], -}); App.addIcons(`${App.configDir}/assets`); @@ -43,67 +47,6 @@ function Workspaces() { }); } -function ProcessorUsage() { - return Widget.Box({ - class_name: "system-stats", - css: cpu.bind("current-usage").as((usage) => { - let level = (usage * 100).toFixed(0); - return ` - background: linear-gradient( - 90deg, rgba(26, 27, 38, 0.6) ${level}%, rgba(26, 27, 38, 0.4) ${level}% - )`; - }), - children: [ - Widget.Icon({ - class_names: ["system-stats-icon"], - icon: "microchip-symbolic", - size: 16, - }), - Widget.Label({ - class_names: ["system-stats-text"], - hexpand: true, - label: cpu.bind("current-usage").as((usage) => { - return `${(usage * 100).toFixed(0)}%`; - }), - }), - ], - vertical: true, - }); -} - -function MemoryUsage() { - return Widget.Box({ - class_name: "system-stats", - css: ram.bind("current-usage-percentage").as((usage) => { - let level = (usage * 100).toFixed(0); - return ` - background: linear-gradient( - 90deg, rgba(26, 27, 38, 0.6) ${level}%, rgba(26, 27, 38, 0.4) ${level}% - )`; - }), - children: [ - Widget.Icon({ - class_names: ["system-stats-icon"], - icon: "ram-custom-symbolic", - size: 16, - }), - Widget.Label({ - class_names: ["system-stats-text"], - hexpand: true, - label: ram.bind("current-usage-percentage").as((usage) => { - return `${(usage * 100).toFixed(0)}%`; - }), - }), - ], - tooltip_text: ram.bind("current-usage").as((usage) => { - let usageGb = (usage / 1024 ** 2).toFixed(2); - let totalGb = (ram.total_available / 1024 ** 2).toFixed(2); - return `${usageGb}/${totalGb}GB (${(ram.current_usage_percentage * 100).toFixed(2)}%)`; - }), - vertical: true, - }); -} - function CurrentSong(player) { return Widget.Box({ class_name: "music-indicator", @@ -155,57 +98,6 @@ function CTest() { }); } -function ProfilePicture() { - return Widget.Box({ - class_name: "profile-picture", - css: "background-image: url('/home/avery/.face.icon'); background-size: cover", - }); -} - -function VolumeWidget() { - return Widget.Box({ - class_name: "volume-widget", - children: [ - Widget.Icon({ - icon: speaker.bind("volume").as((l) => { - let level = l.toFixed(2) * 100; - if (level == 0) { - return "audio-volume-muted"; - } else if (level > 0 && level <= 30) { - return "audio-volume-low"; - } else if (level > 30 && level <= 70) { - return "audio-volume-medium"; - } else { - return "audio-volume-high"; - } - }), - size: 10, - }), - Widget.Box({ - class_name: "volume-box", - css: speaker.bind("volume").as((l) => { - let level = l.toFixed(2) * 100; - if (level >= 0 && level <= 100) { - return ` - background: linear-gradient( - 90deg, #7aa2f7 ${level}%, #24283b ${level}% - ); - `; - } else if (level > 100 && level <= 200) { - return ` - background: linear-gradient( - 90deg, #f7768e ${level - 100}%, #7aa2f7 ${level - 100}% - ); - `; - } else { - return "background: #f7768e"; - } - }), - hpack: "fill", - }), - ], - }); -} function ClockWidget() { return Widget.Box({ children: [ @@ -239,11 +131,14 @@ const barContainer = Widget.Box({ spacing: 6, vertical: true, children: [ + ProfilePicture(), Workspaces(), Widget.Box({ expand: true, vpack: "fill" }), // Separator CTest(), ProcessorUsage(), MemoryUsage(), + graphics_card_usage, + volume_widget, ClockWidget(), ], }); @@ -252,6 +147,7 @@ const bar = Widget.Window({ name: "bar", anchor: ["left", "top", "bottom"], exclusivity: "exclusive", + margins: [4, 0, 4, 4], child: barContainer, }); @@ -266,5 +162,5 @@ Utils.monitorFile( App.config({ style: "./style.css", - windows: [bar, launcher], + windows: [bar, extended_bar, launcher, popup_clock], }); diff --git a/hosts/totsugeki/home-manager/desktop/ags/config/services/cpu.js b/hosts/totsugeki/home-manager/desktop/ags/config/services/cpu.js index 70de005..e4a4e2a 100644 --- a/hosts/totsugeki/home-manager/desktop/ags/config/services/cpu.js +++ b/hosts/totsugeki/home-manager/desktop/ags/config/services/cpu.js @@ -1,16 +1,25 @@ class CPUService extends Service { static { - Service.register(this, {}, { "current-usage": ["float", "r"] }); + Service.register( + this, + {}, + { "current-usage": ["float", "r"], temperature: ["float", "r"] }, + ); } #previousIdle = 0.0; #previousTotal = 0.0; #currentUsage = 0.0; + #temperature = 0.0; get current_usage() { return this.#currentUsage; } + get temperature() { + return this.#temperature; + } + constructor() { super(); this.#update(); @@ -20,6 +29,10 @@ class CPUService extends Service { } #update() { + this.#temperature = + Utils.exec( + "sh -c 'cat /sys/devices/pci0000:00/0000:00:18.3/hwmon/hwmon1/temp1_input'", + ) / 1000; const currentValues = Utils.exec( "sh -c 'cat /proc/stat | grep cpu | head -n 1 | tr -s \" \"'", ) @@ -33,6 +46,7 @@ class CPUService extends Service { this.#previousIdle = idle; this.#previousTotal = total; this.changed("current-usage"); + this.changed("temperature"); } } diff --git a/hosts/totsugeki/home-manager/desktop/ags/config/services/gpu.js b/hosts/totsugeki/home-manager/desktop/ags/config/services/gpu.js new file mode 100644 index 0000000..48b0162 --- /dev/null +++ b/hosts/totsugeki/home-manager/desktop/ags/config/services/gpu.js @@ -0,0 +1,43 @@ +class GPUService extends Service { + static { + Service.register( + this, + {}, + { "current-usage": ["float", "r"], temperature: ["float", "r"] }, + ); + } + + #currentUsage = 0; + #temperature = 0; + + get current_usage() { + return this.#currentUsage; + } + + get temperature() { + return this.#temperature; + } + + constructor() { + super(); + this.#update(); + const interval = setInterval(() => { + this.#update(); + }, 2000); + } + + #update() { + this.#currentUsage = Utils.exec( + "sh -c 'cat /sys/class/drm/card?/device/gpu_busy_percent'", + ); + this.#temperature = + Utils.exec( + "sh -c 'cat /sys/class/drm/card?/device/hwmon/hwmon?/temp1_input'", + ) / 1000; + this.changed("current-usage"); + this.changed("temperature"); + } +} + +const gpu = new GPUService(); +export default gpu; diff --git a/hosts/totsugeki/home-manager/desktop/ags/config/state.js b/hosts/totsugeki/home-manager/desktop/ags/config/state.js new file mode 100644 index 0000000..cdac73e --- /dev/null +++ b/hosts/totsugeki/home-manager/desktop/ags/config/state.js @@ -0,0 +1,55 @@ +export const date = Variable("", { + poll: [1000, 'date "+%H %M %S %Y-%m-%d"'], +}); + +class User { + constructor(user_string) { + var split_string = user_string.split(":"); + this.username = split_string[0]; + this.uid = split_string[2]; + this.gid = split_string[3]; + this.real_name = split_string[4]; + if (this.real_name == "") { + this.real_name = this.username; + } + this.home_directory = split_string[5]; + } +} + +var is_bar_extended = false; +var current_menu = "bar"; + +// TODO(maybe): watch for changes to real name +var uid = Utils.exec("sh -c 'echo $UID'"); +export const CURRENT_USER = new User( + Utils.exec(`grep -P '\\w+:x:${uid}' /etc/passwd`), +); +export const HOSTNAME = Utils.exec("hostname"); + +export function on_window_event(_, window_name, visible) { + console.log(`is ${window_name} visible => ${visible}`); + console.log(`current menu is ${current_menu}`); + console.log(`is bar extended => ${is_bar_extended}`); + if (window_name != "bar" && visible) { + var previous_menu = current_menu; + current_menu = window_name; + if (previous_menu != "bar") { + App.getWindow(previous_menu).visible = false; + } + App.getWindow("bar").css = "opacity: 0"; + if (window_name == "bar_extended") { + is_bar_extended = true; + } + } else if (window_name != "bar" && !visible) { + if ( + (window_name == "bar_extended" && current_menu == "bar_extended") || + !is_bar_extended + ) { + is_bar_extended = false; + current_menu = "bar"; + App.getWindow("bar").css = "opacity: 1"; + } else if (window_name != "bar_extended" && is_bar_extended) { + App.getWindow("bar_extended").visible = true; + } + } +} diff --git a/hosts/totsugeki/home-manager/desktop/ags/config/style.css b/hosts/totsugeki/home-manager/desktop/ags/config/style.css index 4d27b99..623b2f0 100644 --- a/hosts/totsugeki/home-manager/desktop/ags/config/style.css +++ b/hosts/totsugeki/home-manager/desktop/ags/config/style.css @@ -11,11 +11,15 @@ window { .bar { background-color: @bg; border-radius: 8px; - margin: 4px 0px 4px 4px; padding: 4px 4px; min-width: 32px; } +.extended_bar, +.launcher { + min-width: 360px; +} + .volume-widget { margin: 4px; } @@ -30,7 +34,7 @@ window { border-radius: 8px; background-color: @bg; font-weight: bold; - font-family: "Iosevka Nerd Font"; + font-family: monospace; font-size: 1.2em; padding: 4px 0; } @@ -67,8 +71,8 @@ window { background-color: @bg; border-radius: 8px; color: white; - font-family: "Iosevka Nerd Font"; - font-weight: bold; + font-family: monospace; + font-weight: 700; font-size: 0.95em; padding: 4px 0; } @@ -77,7 +81,7 @@ window { background-color: @bg; border-radius: 8px; color: #c0caf5; - font-family: "Iosevka Nerd Font"; + font-family: monospace; font-weight: bold; font-size: 1em; min-height: 20px; @@ -89,13 +93,11 @@ window { } .occupied { - border-radius: 8px; min-width: 24px; } .active { color: @bg-noa; - border-radius: 8px; min-width: 32px; } @@ -158,7 +160,6 @@ window { .launcher-search:focus { border: 2px solid white; - outline: none; } @@ -180,6 +181,117 @@ window { } .application_name { - font-size: 1.4em; + font-size: 1.2em; font-weight: 500; } + +/* +* Extended bar +*/ + +.profile_block { + background-color: @bg; + background-blend-mode: darken; + border-radius: 8px; + color: white; + padding: 4px; +} + +.username { + font-weight: 600; + font-size: 1.4em; + text-shadow: 1px 1px 2px rgba(0, 0, 0, 0.6); +} + +.hostname { + font-weight: 600; + font-size: 1.1em; + color: rgba(255, 255, 255, 0.6); + font-family: monospace; +} + +.workspace-button-big { + color: white; + font-family: "sans-serif"; + font-weight: 400; + padding: 4px 8px; +} + +.active { + color: @bg-noa; +} + +.workspace-name { + font-weight: 500; + font-size: 1.2em; +} + +.clock-big { + font-size: 1.4em; + color: white; +} + +.system-stats-big { + padding: 4px 8px; +} + +.system-stats-title { + font-family: sans-serif; + font-size: 1.2em; + font-weight: 500; +} + +.currently-playing-big { + background-color: @bg; + border-radius: 8px; + padding: 4px; +} + +.album-art-big { + border-radius: 8px; + min-width: 72px; + min-height: 72px; +} + +.currently-playing-title { + color: white; + font-size: 1.2em; + font-weight: 700; + text-shadow: 1px 1px 2px rgba(0, 0, 0, 0.2); +} + +.currently-playing-artists { + color: white; + font-weight: 600; + text-shadow: 1px 1px 2px rgba(0, 0, 0, 0.2); +} + +calendar { + background-color: @bg; + border: none; + border-radius: 8px; + color: rgb(220, 220, 220); + font-family: sans-serif; + font-weight: 500; + padding: 1px; +} + +calendar .header, +calendar .highlight { + background-color: @normal; + color: white; +} + +calendar .button { + background-color: transparent; + color: white; +} + +calendar:selected { + background-color: rgba(255, 255, 255, 0.8); + color: black; +} + +calendar:indeterminate { + color: rgb(90, 90, 90); +} diff --git a/hosts/totsugeki/home-manager/desktop/ags/config/widgets/bar-extended/big-clock.js b/hosts/totsugeki/home-manager/desktop/ags/config/widgets/bar-extended/big-clock.js new file mode 100644 index 0000000..c20fa84 --- /dev/null +++ b/hosts/totsugeki/home-manager/desktop/ags/config/widgets/bar-extended/big-clock.js @@ -0,0 +1,41 @@ +import { date } from "../../state.js"; + +function BlinkingDots() { + return Widget.Label({ + class_name: "blinking-dots", + label: ":", + }); +} + +export function Clock() { + return Widget.Box({ + children: [ + Widget.Box({ hexpand: true, hpack: "fill" }), // Separator + Widget.Box({ + children: [ + Widget.Label({ + label: date.bind().as((d) => { + return d.split(" ")[0]; + }), + }), + BlinkingDots(), + Widget.Label({ + label: date.bind().as((d) => { + return d.split(" ")[1]; + }), + }), + BlinkingDots(), + Widget.Label({ + label: date.bind().as((d) => { + return d.split(" ")[2]; + }), + }), + ], + }), + Widget.Box({ hexpand: true, hpack: "fill" }), // Separator + ], + class_names: ["clock", "clock-big"], + hpack: "fill", + spacing: 8, + }); +} diff --git a/hosts/totsugeki/home-manager/desktop/ags/config/widgets/bar-extended/currently-playing.js b/hosts/totsugeki/home-manager/desktop/ags/config/widgets/bar-extended/currently-playing.js new file mode 100644 index 0000000..e7d29d6 --- /dev/null +++ b/hosts/totsugeki/home-manager/desktop/ags/config/widgets/bar-extended/currently-playing.js @@ -0,0 +1,78 @@ +const mpris = await Service.import("mpris"); +const players = mpris.bind("players"); + +// TODO: rework +export const currently_playing_controller = Widget.Box({ + vertical: true, + visible: players.as((p) => p.length > 0), + children: players.as((p) => p.map(CurrentlyPlaying)), +}); + +function CurrentlyPlaying(player) { + return Widget.Box({ + class_name: "currently-playing-big", + css: player.bind("cover_path").as((url) => { + if (url == undefined) { + return ""; + } + console.log(url); + return ` + background-image: url('${url}'); + background-blend-mode: darken; + background-position: center; + background-size: cover; + `; + }), + spacing: 4, + children: [ + Widget.Box({ + class_name: "album-art-big", + css: player.bind("cover_path").as((url) => { + if (url == undefined) { + return ` + background-color: rgba(0, 0, 0, 0.2); + background-image: url('${App.configDir}/assets/music-symbolic.svg'); + background-size: 50% 50%; + background-repeat: no-repeat; + background-position: center; + `; + } + return `background-image: url('${url}'); background-size: cover;`; + }), + }), + Widget.Box({ + children: [ + Widget.Label({ + class_name: "currently-playing-title", + label: player.bind("track-title"), + xalign: 0.5, + hexpand: true, + truncate: "end", + }), + Widget.Label({ + class_name: "currently-playing-artists", + label: player.bind("track-artists").as((artists) => { + return artists.join(", "); + }), + xalign: 0.5, + hexpand: true, + truncate: "end", + }), + ], + vpack: "center", + vertical: true, + }), + // Widget.Icon({ + // class_name: "playback-status", + // icon: player.bind("play-back-status").as((status) => { + // if (status == "Playing") { + // return "media-playback-start"; + // } else if (status == "Paused") { + // return "media-playback-pause"; + // } + // return ""; + // }), + // }), + ], + }); +} diff --git a/hosts/totsugeki/home-manager/desktop/ags/config/widgets/bar-extended/extended-bar.js b/hosts/totsugeki/home-manager/desktop/ags/config/widgets/bar-extended/extended-bar.js new file mode 100644 index 0000000..1cf1e38 --- /dev/null +++ b/hosts/totsugeki/home-manager/desktop/ags/config/widgets/bar-extended/extended-bar.js @@ -0,0 +1,45 @@ +import { Clock } from "./big-clock.js"; +import { currently_playing_controller } from "./currently-playing.js"; +import { ProfileBlock } from "./profile-block.js"; +import { + graphics_card_usage, + memory_usage, + processor_usage, + volume_widget, +} from "./system-stats-big.js"; +import { workspaces } from "./workspaces.js"; +import { on_window_event } from "../../state.js"; + +export const extended_bar = Widget.Window({ + name: "bar_extended", + anchor: ["left", "top", "bottom"], + margins: [4, 0, 4, 4], + child: Widget.Box({ + class_names: ["bar", "extended_bar"], + vpack: "fill", + spacing: 6, + vertical: true, + children: [ + ProfileBlock(), + workspaces, + Widget.Box({ expand: true, vpack: "fill" }), // Separator + currently_playing_controller, + processor_usage, + memory_usage, + graphics_card_usage, + volume_widget, + Widget.Calendar({}), + Clock(), + ], + }), + exclusivity: "ignore", + visible: false, + layer: "overlay", + setup: (self) => { + self.hook(App, (_, window_name, visible) => { + if (window_name == self.name) { + on_window_event(_, window_name, visible); + } + }); + }, +}); diff --git a/hosts/totsugeki/home-manager/desktop/ags/config/widgets/bar-extended/profile-block.js b/hosts/totsugeki/home-manager/desktop/ags/config/widgets/bar-extended/profile-block.js new file mode 100644 index 0000000..46954b6 --- /dev/null +++ b/hosts/totsugeki/home-manager/desktop/ags/config/widgets/bar-extended/profile-block.js @@ -0,0 +1,54 @@ +import { ProfilePicture } from "../bar/profile-picture.js"; +import { CURRENT_USER, HOSTNAME } from "../../state.js"; + +const CURRENT_WALLPAPER_PATH = + "/home/avery/.local/share/wallpapers/.current_path"; + +const wallpaperPath = Variable(Utils.readFile(CURRENT_WALLPAPER_PATH)); + +const wallpaperMonitor = Utils.monitorFile( + CURRENT_WALLPAPER_PATH, + (file, event) => { + if (event === 1) { + wallpaperPath.value = Utils.readFile(file); + } + }, +); + +export function ProfileBlock() { + return Widget.Box({ + class_name: "profile_block", + css: wallpaperPath.bind().as((path) => { + path = + path + .replace("\n", "") + .replace("/", "\\/") + .replace("wallpapers", "wallpaper_thumbnails") + ".jpg"; + return ` + background-image: url('${path}'); + background-size: cover; + background-position: center; + `; + }), + spacing: 8, + children: [ + ProfilePicture(40), + Widget.Box({ + vertical: true, + vpack: "center", + children: [ + Widget.Label({ + class_name: "username", + label: CURRENT_USER.real_name, + xalign: 0, + }), + Widget.Label({ + class_name: "hostname", + label: HOSTNAME, + xalign: 0, + }), + ], + }), + ], + }); +} diff --git a/hosts/totsugeki/home-manager/desktop/ags/config/widgets/bar-extended/system-stats-big.js b/hosts/totsugeki/home-manager/desktop/ags/config/widgets/bar-extended/system-stats-big.js new file mode 100644 index 0000000..1d3dc95 --- /dev/null +++ b/hosts/totsugeki/home-manager/desktop/ags/config/widgets/bar-extended/system-stats-big.js @@ -0,0 +1,155 @@ +import cpu from "../../services/cpu.js"; +import ram from "../../services/ram.js"; +import gpu from "../../services/gpu.js"; +const { speaker } = await Service.import("audio"); + +const PROCESSOR_NAME = Utils.exec( + 'bash -c \'cat /proc/cpuinfo | grep "model name" | head -n 1 | cut -d ":" -f 2 | cut -d " " -f 3,4,5\'', +); +const GRAPHICS_CARD_NAME = "Radeon RX 6700 XT"; + +export const processor_usage = Widget.Box({ + class_names: ["system-stats", "system-stats-big"], + css: cpu.bind("current-usage").as((usage) => { + let level = (usage * 100).toFixed(0); + return ` + background: linear-gradient( + 90deg, rgba(26, 27, 38, 0.8) ${level}%, rgba(26, 27, 38, 0.4) ${level}% + )`; + }), + spacing: 6, + children: [ + Widget.Icon({ + class_names: ["system-stats-icon"], + icon: "microchip-symbolic", + size: 16, + }), + Widget.Label({ + class_name: "system-stats-title", + label: PROCESSOR_NAME, + }), + Widget.Label({ + class_name: "system-stats-text-big", + hexpand: true, + xalign: 1, + label: cpu.bind("current-usage").as((usage) => { + return `${(usage * 100).toFixed(0)}% (${cpu.temperature.toFixed(0)}ºC)`; + }), + }), + ], +}); + +export const memory_usage = Widget.Box({ + class_names: ["system-stats", "system-stats-big"], + css: ram.bind("current-usage-percentage").as((usage) => { + let level = (usage * 100).toFixed(0); + return ` + background: linear-gradient( + 90deg, rgba(26, 27, 38, 0.8) ${level}%, rgba(26, 27, 38, 0.4) ${level}% + )`; + }), + spacing: 6, + children: [ + Widget.Icon({ + class_names: ["system-stats-icon"], + icon: "ram-custom-symbolic", + size: 16, + }), + Widget.Label({ + class_name: "system-stats-title", + label: "Memoria", + }), + Widget.Label({ + class_name: "system-stats-text-big", + hexpand: true, + xalign: 1, + label: ram.bind("current-usage").as((usage) => { + let usageGb = (usage / 1024 ** 2).toFixed(2); + let totalGb = (ram.total_available / 1024 ** 2).toFixed(2); + return `${usageGb}/${totalGb}GiB (${(ram.current_usage_percentage * 100).toFixed(2)}%)`; + }), + }), + ], +}); + +export const graphics_card_usage = Widget.Box({ + class_names: ["system-stats", "system-stats-big"], + css: gpu.bind("current-usage").as((usage) => { + var level = Number(usage).toFixed(0); + return ` + background: linear-gradient( + 90deg, rgba(26, 27, 38, 0.8) ${level}%, rgba(26, 27, 38, 0.4) ${level}% + )`; + }), + spacing: 6, + children: [ + Widget.Icon({ + class_names: ["system-stats-icon"], + icon: "gpu-symbolic", + size: 16, + }), + Widget.Label({ + class_name: "system-stats-title", + label: GRAPHICS_CARD_NAME, + }), + Widget.Label({ + class_name: "system-stats-text-big", + hexpand: true, + xalign: 1, + label: gpu.bind("current-usage").as((usage) => { + return `${Number(usage).toFixed(0)}% (${gpu.temperature.toFixed(0)}ºC)`; + }), + }), + ], +}); + +export const volume_widget = Widget.Box({ + class_names: ["system-stats", "system-stats-big"], + css: speaker.bind("volume").as((l) => { + let level = l.toFixed(2) * 100; + if (level >= 0 && level <= 100) { + return ` + background: linear-gradient( + 90deg, rgba(26, 27, 38, 0.8) ${level}%, rgba(26, 27, 38, 0.4) ${level}% + );`; + } else if (level > 100 && level <= 200) { + return ` + background: linear-gradient( + 90deg, rgba(226, 27, 38, 0.4) ${level - 100}%, rgba(26, 27, 38, 0.8) ${level - 100}% + );`; + } else { + return "background: rgba(226, 27, 38, 0.4)"; + } + }), + spacing: 6, + children: [ + Widget.Icon({ + class_names: ["system-stats-icon"], + icon: speaker.bind("volume").as((l) => { + let level = l.toFixed(2) * 100; + if (level == 0) { + return "audio-volume-muted"; + } else if (level > 0 && level <= 30) { + return "audio-volume-low"; + } else if (level > 30 && level <= 70) { + return "audio-volume-medium"; + } else { + return "audio-volume-high"; + } + }), + size: 16, + }), + Widget.Label({ + class_name: "system-stats-title", + label: "Volumen", + }), + Widget.Label({ + class_name: "system-stats-text-big", + hexpand: true, + xalign: 1, + label: speaker.bind("volume").as((l) => { + return `${(l * 100).toFixed(0)}%`; + }), + }), + ], +}); diff --git a/hosts/totsugeki/home-manager/desktop/ags/config/widgets/bar-extended/workspaces.js b/hosts/totsugeki/home-manager/desktop/ags/config/widgets/bar-extended/workspaces.js new file mode 100644 index 0000000..d4737b6 --- /dev/null +++ b/hosts/totsugeki/home-manager/desktop/ags/config/widgets/bar-extended/workspaces.js @@ -0,0 +1,81 @@ +const sway = await Service.import("sway"); + +function getWorkspaceWindows(node) { + var nodes = []; + node.nodes.concat(node.floating_nodes).forEach((child_node) => { + if ( + child_node.app_id != undefined || + child_node.window_properties != undefined + ) { + nodes.push(child_node); + } else { + nodes = nodes.concat(getWorkspaceWindows(child_node)); + } + }); + return nodes; +} + +export const workspaces = Widget.Box({ + children: Array.from({ length: 10 }, (_, i) => { + i += 1; + return Widget.Box({ + class_names: ["workspace-button", "workspace-button-big"], + spacing: 16, + children: [ + Widget.Label({ + class_name: "workspace-name", + label: `Escritorio ${i.toString()}`, + }), + Widget.Box({ + children: [ + Widget.Label({ + label: "dummy", + hexpand: true, + xalign: 1, + truncate: "end", + setup: (label) => { + label.hook(sway, (label) => { + const ws = sway.getWorkspace(`${i}`); + if (ws === undefined) { + label.visible = false; + return; + } + var nodes = getWorkspaceWindows(ws); + if (nodes.length == 1) { + label.label = nodes[0].name; + label.visible = true; + } else if (nodes.length > 0) { + label.label = `${nodes.length} ventanas abiertas`; + label.visible = true; + } else { + label.label = "No hay ventanas abiertas"; + label.visible = false; + } + }); + }, + }), + ], + }), + ], + setup: (btn) => { + btn.hook( + sway, + (btn) => { + const ws = sway.getWorkspace(`${i}`); + btn.toggleClassName( + "occupied", + ws?.nodes.length + ws?.floating_nodes.length > 0, + ); + }, + "notify::workspaces", + ); + + btn.hook(sway.active.workspace, (btn) => { + btn.toggleClassName("active", sway.active.workspace.name == i); + }); + }, + }); + }), + spacing: 4, + vertical: true, +}); diff --git a/hosts/totsugeki/home-manager/desktop/ags/config/widgets/bar/profile-picture.js b/hosts/totsugeki/home-manager/desktop/ags/config/widgets/bar/profile-picture.js new file mode 100644 index 0000000..04e5b1b --- /dev/null +++ b/hosts/totsugeki/home-manager/desktop/ags/config/widgets/bar/profile-picture.js @@ -0,0 +1,12 @@ +// TODO: user-agnostic, reload if changed +export function ProfilePicture(size = 32) { + return Widget.Box({ + class_name: "profile-picture", + css: ` + background-image: url('/home/avery/.face.icon'); + background-size: cover; + min-width: ${size}px; + min-height: ${size}px; + `, + }); +} diff --git a/hosts/totsugeki/home-manager/desktop/ags/config/widgets/bar/system-stats.js b/hosts/totsugeki/home-manager/desktop/ags/config/widgets/bar/system-stats.js new file mode 100644 index 0000000..d12b389 --- /dev/null +++ b/hosts/totsugeki/home-manager/desktop/ags/config/widgets/bar/system-stats.js @@ -0,0 +1,141 @@ +import cpu from "../../services/cpu.js"; +import gpu from "../../services/gpu.js"; +import ram from "../../services/ram.js"; +const { speaker } = await Service.import("audio"); + +export function ProcessorUsage() { + return Widget.Box({ + class_name: "system-stats", + css: cpu.bind("current-usage").as((usage) => { + let level = (usage * 100).toFixed(0); + return ` + background: linear-gradient( + 90deg, rgba(26, 27, 38, 0.8) ${level}%, rgba(26, 27, 38, 0.4) ${level}% + )`; + }), + spacing: 2, + children: [ + Widget.Icon({ + class_names: ["system-stats-icon"], + icon: "microchip-symbolic", + size: 16, + }), + Widget.Label({ + class_names: ["system-stats-text"], + hexpand: true, + label: cpu.bind("current-usage").as((usage) => { + return `${(usage * 100).toFixed(0)}%`; + }), + }), + ], + vertical: true, + }); +} + +export function MemoryUsage() { + return Widget.Box({ + class_name: "system-stats", + css: ram.bind("current-usage-percentage").as((usage) => { + let level = (usage * 100).toFixed(0); + return ` + background: linear-gradient( + 90deg, rgba(26, 27, 38, 0.8) ${level}%, rgba(26, 27, 38, 0.4) ${level}% + )`; + }), + spacing: 2, + children: [ + Widget.Icon({ + class_names: ["system-stats-icon"], + icon: "ram-custom-symbolic", + size: 16, + }), + Widget.Label({ + class_names: ["system-stats-text"], + hexpand: true, + label: ram.bind("current-usage-percentage").as((usage) => { + return `${(usage * 100).toFixed(0)}%`; + }), + }), + ], + tooltip_text: ram.bind("current-usage").as((usage) => { + let usageGb = (usage / 1024 ** 2).toFixed(2); + let totalGb = (ram.total_available / 1024 ** 2).toFixed(2); + return `${usageGb}/${totalGb}GiB (${(ram.current_usage_percentage * 100).toFixed(2)}%)`; + }), + vertical: true, + }); +} + +export const graphics_card_usage = Widget.Box({ + class_name: "system-stats", + css: gpu.bind("current-usage").as((usage) => { + var level = Number(usage).toFixed(0); + return ` + background: linear-gradient( + 90deg, rgba(26, 27, 38, 0.8) ${level}%, rgba(26, 27, 38, 0.4) ${level}% + )`; + }), + spacing: 2, + children: [ + Widget.Icon({ + class_names: ["system-stats-icon"], + icon: "gpu-symbolic", + size: 16, + }), + Widget.Label({ + class_names: ["system-stats-text"], + hexpand: true, + label: gpu.bind("current-usage").as((usage) => { + return `${Number(usage).toFixed(0)}%`; + }), + }), + ], + vertical: true, +}); + +export const volume_widget = Widget.Box({ + class_name: "system-stats", + css: speaker.bind("volume").as((l) => { + let level = l.toFixed(2) * 100; + if (level >= 0 && level <= 100) { + return ` + background: linear-gradient( + 90deg, rgba(26, 27, 38, 0.8) ${level}%, rgba(26, 27, 38, 0.4) ${level}% + );`; + } else if (level > 100 && level <= 200) { + return ` + background: linear-gradient( + 90deg, rgba(226, 27, 38, 0.4) ${level - 100}%, rgba(26, 27, 38, 0.8) ${level - 100}% + );`; + } else { + return "background: rgba(226, 27, 38, 0.4)"; + } + }), + spacing: 2, + vertical: true, + children: [ + Widget.Icon({ + class_names: ["system-stats-icon"], + icon: speaker.bind("volume").as((l) => { + let level = l.toFixed(2) * 100; + if (level == 0) { + return "audio-volume-muted"; + } else if (level > 0 && level <= 30) { + return "audio-volume-low"; + } else if (level > 30 && level <= 70) { + return "audio-volume-medium"; + } else { + return "audio-volume-high"; + } + }), + size: 16, + }), + Widget.Label({ + class_name: "system-stats-text", + hexpand: true, + label: speaker.bind("volume").as((l) => { + return `${(l * 100).toFixed(0)}%`; + }), + }), + ], +}); diff --git a/hosts/totsugeki/home-manager/desktop/ags/config/widgets/launcher/application.js b/hosts/totsugeki/home-manager/desktop/ags/config/widgets/launcher/application.js index c855ae4..cef3ee3 100644 --- a/hosts/totsugeki/home-manager/desktop/ags/config/widgets/launcher/application.js +++ b/hosts/totsugeki/home-manager/desktop/ags/config/widgets/launcher/application.js @@ -10,7 +10,7 @@ export const Application = (application) => children: [ Widget.Icon({ icon: application.icon_name || "", - size: 32, + size: 24, }), Widget.Label({ class_name: "application_name", @@ -20,6 +20,6 @@ export const Application = (application) => truncate: "end", }), ], - spacing: 8, + spacing: 4, }), }); diff --git a/hosts/totsugeki/home-manager/desktop/ags/config/widgets/launcher/launcher.js b/hosts/totsugeki/home-manager/desktop/ags/config/widgets/launcher/launcher.js index 80052bc..c34adb0 100644 --- a/hosts/totsugeki/home-manager/desktop/ags/config/widgets/launcher/launcher.js +++ b/hosts/totsugeki/home-manager/desktop/ags/config/widgets/launcher/launcher.js @@ -1,13 +1,14 @@ import { Application } from "./application.js"; +import { on_window_event } from "../../state.js"; const { query } = await Service.import("applications"); -const Launcher = ({ width = 500, height = 600, spacing = 4 }) => { +const Launcher = () => { let applications = query("").map(Application); const list = Widget.Box({ css: "background-color: transparent", vertical: true, - spacing, + spacing: 4, children: applications, }); @@ -54,20 +55,20 @@ const Launcher = ({ width = 500, height = 600, spacing = 4 }) => { return Widget.Box({ class_name: "launcher", vertical: true, - spacing, + spacing: 4, children: [ searchBox, Widget.Scrollable({ hscroll: "never", - css: `min-width: ${width}px; min-height: ${height}px;`, child: list, vexpand: true, }), ], setup: (self) => - self.hook(App, (_, windowName, visible) => { - if (windowName == "launcher" && visible) { + self.hook(App, (_, window_name, visible) => { + if (window_name == "launcher" && visible) { repopulate(); + applications[0].grab_focus(); // Scrolls application list to top searchBox.text = ""; searchBox.grab_focus(); } @@ -77,11 +78,20 @@ const Launcher = ({ width = 500, height = 600, spacing = 4 }) => { export const launcher = Widget.Window({ name: "launcher", - setup: (self) => + anchor: ["left", "top", "bottom"], + setup: (self) => { + self.hook(App, (_, window_name, visible) => { + if (window_name == self.name) { + on_window_event(_, window_name, visible); + } + }); self.keybind("Escape", () => { App.closeWindow("launcher"); - }), + }); + }, visible: false, + margins: [4, 0, 4, 4], + exclusivity: "ignore", keymode: "exclusive", child: Launcher({}), }); diff --git a/hosts/totsugeki/home-manager/desktop/ags/config/widgets/popup-clock/popup-clock.js b/hosts/totsugeki/home-manager/desktop/ags/config/widgets/popup-clock/popup-clock.js new file mode 100644 index 0000000..5bb7ec8 --- /dev/null +++ b/hosts/totsugeki/home-manager/desktop/ags/config/widgets/popup-clock/popup-clock.js @@ -0,0 +1,10 @@ +import { Clock } from "../bar-extended/big-clock.js"; + +export const popup_clock = Widget.Window({ + name: "popup_clock", + visible: false, + anchor: ["right", "top"], + margins: [16, 16, 0, 0], + layer: "overlay", + child: Clock(), +}); diff --git a/hosts/totsugeki/home-manager/desktop/ags/default.nix b/hosts/totsugeki/home-manager/desktop/ags/default.nix index 71e5b1d..28a8cfd 100644 --- a/hosts/totsugeki/home-manager/desktop/ags/default.nix +++ b/hosts/totsugeki/home-manager/desktop/ags/default.nix @@ -1,4 +1,5 @@ -{ ... }: { +{ pkgs, ... }: { + home.packages = with pkgs; [ pciutils ]; programs.ags = { enable = true; # configDir = ./widgets;