commit 6ff50006f22b93e7e67fd862e397040818e251b9 Author: Jas Singh Date: Sun Jun 9 01:25:57 2024 -0700 Added workspaces, window titles, volume, bluetooth, systray and date/time modules to the panel diff --git a/README.md b/README.md new file mode 100644 index 0000000..71563ed --- /dev/null +++ b/README.md @@ -0,0 +1,15 @@ + +# Starter Config + +if suggestions don't work, first make sure +you have TypeScript LSP working in your editor + +if you do not want typechecking only suggestions + +```json +// tsconfig.json +"checkJs": false +``` + +types are symlinked to: +/usr/share/com.github.Aylur.ags/types diff --git a/config.js b/config.js new file mode 100644 index 0000000..e3f1a1a --- /dev/null +++ b/config.js @@ -0,0 +1,43 @@ +import {exec, idle, monitorFile} from "resource:///com/github/Aylur/ags/utils.js"; +import { Volume } from './modules/volume/volume.js'; +import { Bar } from './modules/bar/bar.js'; +import DirectoryMonitorService from "./directoryMonitorService.js"; + +const applyScss = () => { + // Compile scss + exec(`sass ${App.configDir}/scss/main.scss ${App.configDir}/style.css`); + exec(`sass ${App.configDir}/scss/highlight.scss ${App.configDir}/highlight.css`); + console.log("Scss compiled"); + + // Apply compiled css + App.resetCss(); + App.applyCss(`${App.configDir}/style.css`); + console.log("Compiled css applied"); +}; + +DirectoryMonitorService.connect("changed", () => applyScss()); + +applyScss(); + + +export default { + style: `${App.configDir}/style.css`, + closeWindowDelay: { + sideright: 350, + launcher: 350, + bar0: 350, + }, +}; +// const win = Widget.Window({ +// name: "volumeModule", +// child: Volume(), +// }); + +App.config({ + windows: [ + // win, + Bar(0), + Bar(1), + Bar(2), + ], +}); diff --git a/directoryMonitorService.js b/directoryMonitorService.js new file mode 100644 index 0000000..99b1809 --- /dev/null +++ b/directoryMonitorService.js @@ -0,0 +1,38 @@ +import Service from "resource:///com/github/Aylur/ags/service.js"; +import App from "resource:///com/github/Aylur/ags/app.js"; +import {monitorFile} from "resource:///com/github/Aylur/ags/utils.js"; +import Gio from "gi://Gio"; + +class DirectoryMonitorService extends Service { + static { + Service.register(this, {}, {}); + } + + constructor() { + super(); + this.recursiveDirectoryMonitor(`${App.configDir}/scss`); + } + + recursiveDirectoryMonitor(directoryPath) { + + monitorFile(directoryPath, (_, eventType) => { + if (eventType === Gio.FileMonitorEvent.CHANGES_DONE_HINT) { + this.emit("changed"); + } + }, "directory"); + + const directory = Gio.File.new_for_path(directoryPath); + const enumerator = directory.enumerate_children("standard::*", Gio.FileQueryInfoFlags.NONE, null); + + let fileInfo; + while ((fileInfo = enumerator.next_file(null)) !== null) { + const childPath = directoryPath + "/" + fileInfo.get_name(); + if (fileInfo.get_file_type() === Gio.FileType.DIRECTORY) { + this.recursiveDirectoryMonitor(childPath); + } + } + } +} + +const service = new DirectoryMonitorService(); +export default service; diff --git a/modules/bar/bar.js b/modules/bar/bar.js new file mode 100644 index 0000000..de181f1 --- /dev/null +++ b/modules/bar/bar.js @@ -0,0 +1,61 @@ +import { Workspaces } from "./workspaces/index.js"; +import { ClientTitle } from "./window_title/index.js"; +import { Media } from "./media/index.js"; +import { Notification } from "./notification/index.js"; +import { Volume } from "./volume/index.js"; +import { Bluetooth } from "./bluetooth/index.js"; +import { BatteryLabel } from "./battery/index.js"; +import { Clock } from "./clock/index.js"; +import { SysTray } from "./systray/index.js"; + +import { BarItemBox } from "../shared/barItemBox.js"; + +// layout of the bar +const Left = () => { + return Widget.Box({ + class_name: "box-left", + hpack: "start", + spacing: 5, + children: [BarItemBox(Workspaces()), BarItemBox(ClientTitle())], + }); +}; + +const Center = () => { + return Widget.Box({ + class_name: "box-center", + spacing: 5, + children: [BarItemBox(Media()), BarItemBox(Notification())], + }); +}; + +const Right = () => { + return Widget.Box({ + class_name: "box-right", + hpack: "end", + spacing: 5, + children: [ + BarItemBox(Volume()), + BarItemBox(Bluetooth()), + BarItemBox(BatteryLabel()), + BarItemBox(SysTray()), + BarItemBox(Clock()), + ], + }); +}; + +const Bar = (monitor = 0) => { + return Widget.Window({ + name: `bar-${monitor}`, + class_name: "bar", + monitor, + anchor: ["top", "left", "right"], + exclusivity: "exclusive", + child: Widget.CenterBox({ + start_widget: Left(), + center_widget: Center(), + end_widget: Right(), + }), + }); +}; + +export { Bar }; diff --git a/modules/bar/battery/index.js b/modules/bar/battery/index.js new file mode 100644 index 0000000..f8da7f2 --- /dev/null +++ b/modules/bar/battery/index.js @@ -0,0 +1,32 @@ +const battery = await Service.import("battery"); + +const BatteryLabel = () => { + const isVis = Variable(battery.available); + + const value = battery.bind("percent").as((p) => (p > 0 ? p / 100 : 0)); + const icon = battery + .bind("percent") + .as((p) => `battery-level-${Math.floor(p / 10) * 10}-symbolic`); + + battery.connect("changed", ({ available }) => { + isVis.value = available; + }); + + return { + component: Widget.Box({ + class_name: "battery", + visible: battery.bind("available"), + children: [ + Widget.Icon({ icon }), + Widget.LevelBar({ + widthRequest: 20, + vpack: "center", + value, + }), + ], + }), + isVis, + }; +}; + +export { BatteryLabel }; diff --git a/modules/bar/bluetooth/index.js b/modules/bar/bluetooth/index.js new file mode 100644 index 0000000..05d61da --- /dev/null +++ b/modules/bar/bluetooth/index.js @@ -0,0 +1,24 @@ +const bluetooth = await Service.import('bluetooth') + +const Bluetooth = () => { + const btIcon = Widget.Label({ + label: bluetooth.bind("enabled").as((v) => v ? "󰂯 " : "󰂲 "), + class_name: "bar-bt_icon", + }); + + const btText = Widget.Label({ + label: bluetooth.bind("enabled").as((v) => v ? "On" : "Off"), + class_name: "bar-bt_label", + }); + + return { + component: Widget.Box({ + class_name: "volume", + children: [btIcon, btText], + }), + isVisible: true, + }; + +} + +export { Bluetooth } diff --git a/modules/bar/clock/index.js b/modules/bar/clock/index.js new file mode 100644 index 0000000..bc8fe7c --- /dev/null +++ b/modules/bar/clock/index.js @@ -0,0 +1,15 @@ +const date = Variable("", { + poll: [1000, 'date "+󰃭 %a %b%e %I:%M:%S %p"'], +}); + +const Clock = () => { + return { + component: Widget.Label({ + class_name: "clock", + label: date.bind(), + }), + isVisible: true, + }; +}; + +export { Clock }; diff --git a/modules/bar/media/index.js b/modules/bar/media/index.js new file mode 100644 index 0000000..68ff6a3 --- /dev/null +++ b/modules/bar/media/index.js @@ -0,0 +1,25 @@ +const mpris = await Service.import("mpris"); + +const Media = () => { + const label = Utils.watch("", mpris, "player-changed", () => { + if (mpris.players[0]) { + const { track_artists, track_title } = mpris.players[0]; + return `${track_artists.join(", ")} - ${track_title}`; + } else { + return "Nothing is playing"; + } + }); + + return { + component: Widget.Button({ + class_name: "media", + on_primary_click: () => mpris.getPlayer("")?.playPause(), + on_scroll_up: () => mpris.getPlayer("")?.next(), + on_scroll_down: () => mpris.getPlayer("")?.previous(), + child: Widget.Label({ label }), + }), + isVisible: false, + }; +}; + +export { Media }; diff --git a/modules/bar/notification/index.js b/modules/bar/notification/index.js new file mode 100644 index 0000000..42630ec --- /dev/null +++ b/modules/bar/notification/index.js @@ -0,0 +1,24 @@ +const notifications = await Service.import("notifications"); + +// we don't need dunst or any other notification daemon +// because the Notifications module is a notification daemon itself +const Notification = () => { + const popups = notifications.bind("popups"); + return { + component: Widget.Box({ + class_name: "notification", + visible: popups.as((p) => p.length > 0), + children: [ + Widget.Icon({ + icon: "preferences-system-notifications-symbolic", + }), + Widget.Label({ + label: popups.as((p) => p[0]?.summary || ""), + }), + ], + }), + isVisible: false, + }; +}; + +export { Notification }; diff --git a/modules/bar/systray/index.js b/modules/bar/systray/index.js new file mode 100644 index 0000000..4a79be4 --- /dev/null +++ b/modules/bar/systray/index.js @@ -0,0 +1,36 @@ +const systemtray = await Service.import("systemtray"); + +const SysTray = () => { + const isVis = Variable(false); + + const items = systemtray.bind("items").as((items) => { + isVis.value = items.length > 0; + return items.map((item) => { + if (item.menu !== undefined) { + item.menu["class_name"] = "systray-menu"; + } + + return Widget.Button({ + cursor: "pointer", + child: Widget.Icon({ + icon: item.bind("icon"), + size: 18, + }), + on_primary_click: (_, event) => item.activate(event), + on_secondary_click: (_, event) => item.openMenu(event), + tooltip_markup: item.bind("tooltip_markup"), + }); + }); + }); + + return { + component: Widget.Box({ + class_name: "systray", + children: items, + }), + isVisible: true, + isVis, + }; +}; + +export { SysTray }; diff --git a/modules/bar/volume/index.js b/modules/bar/volume/index.js new file mode 100644 index 0000000..3f56779 --- /dev/null +++ b/modules/bar/volume/index.js @@ -0,0 +1,41 @@ +const audio = await Service.import("audio"); + +const Volume = () => { + const icons = { + 101: "󰕾", + 66: "󰕾", + 34: "󰖀", + 1: "󰕿", + 0: "󰝟", + }; + + const getIcon = () => { + const icon = audio.speaker.is_muted + ? 0 + : [101, 66, 34, 1, 0].find( + (threshold) => threshold <= audio.speaker.volume * 100, + ); + + return icons[icon]; + }; + + const volIcn = Widget.Label({ + label: audio.speaker.bind("volume").as(() => getIcon()), + class_name: "bar-volume_icon", + }); + + const volPct = Widget.Label({ + label: audio.speaker.bind("volume").as((v) => ` ${Math.floor(v * 100)}%`), + class_name: "bar-volume_percentage", + }); + + return { + component: Widget.Box({ + class_name: "volume", + children: [volIcn, volPct], + }), + isVisible: true, + }; +}; + +export { Volume }; diff --git a/modules/bar/window_title/index.js b/modules/bar/window_title/index.js new file mode 100644 index 0000000..16579c6 --- /dev/null +++ b/modules/bar/window_title/index.js @@ -0,0 +1,39 @@ +const hyprland = await Service.import("hyprland"); + +const filterTitle = (titleString) => { + const windowTitleMap = [ + ["(.*) - NVIM", " NeoVim"], + ["(.*) - nvim", " NeoVim"], + ["(.*) - VIM", " NeoVim"], + ["(.*)vim (.*)", " NeoVim"], + ["(.*) — Mozilla Firefox", "󰈹 Firefox"], + ["(.*) - Microsoft(.*)Edge", "󰇩 Edge"], + ["(.*) - Discord", " Discord"], + ["(.*) — Dolphin", " Dolphin"], + ["Plex", "󰚺 Plex"], + ["(.*) Steam", " Steam"], + [" ", "󰇄 Desktop"], + ["(.*) Spotify Free", "󰓇 Spotify"], + ["(.*)Spotify Premium", "󰓇 Spotify"], + [" ~", " Terminal"], + ["(.*) - Obsidian(.*)", "󱓧 Obsidian"], + ]; + + const foundMatch = windowTitleMap.find((wt) => + RegExp(wt[0]).test(titleString), + ); + + return foundMatch ? foundMatch[1] : titleString; +}; + +const ClientTitle = () => { + return { + component: Widget.Label({ + class_name: "window_title", + label: hyprland.active.client.bind("title").as((v) => filterTitle(v)), + }), + isVisible: true, + }; +}; + +export { ClientTitle }; diff --git a/modules/bar/workspaces/index.js b/modules/bar/workspaces/index.js new file mode 100644 index 0000000..a09fce1 --- /dev/null +++ b/modules/bar/workspaces/index.js @@ -0,0 +1,44 @@ +const hyprland = await Service.import("hyprland"); + +function range(length, start = 1) { + return Array.from({ length }, (_, i) => i + start); +} + +const Workspaces = (ws) => { + return { + component: Widget.Box({ + class_name: "workspaces", + children: range(ws || 8).map((i) => + Widget.Label({ + attribute: i, + vpack: "center", + label: `${i}`, + setup: (self) => + self.hook(hyprland, () => { + self.toggleClassName( + "active", + hyprland.active.workspace.id === i, + ); + self.toggleClassName( + "occupied", + (hyprland.getWorkspace(i)?.windows || 0) > 0, + ); + }), + }), + ), + setup: (box) => { + if (ws === 0) { + box.hook(hyprland.active.workspace, () => + box.children.map((btn) => { + btn.visible = hyprland.workspaces.some( + (ws) => ws.id === btn.attribute, + ); + }), + ); + } + }, + }), + isVisible: true, + }; +}; +export { Workspaces }; diff --git a/modules/icons/index.js b/modules/icons/index.js new file mode 100644 index 0000000..26c2e88 --- /dev/null +++ b/modules/icons/index.js @@ -0,0 +1,174 @@ +export default { + settings: "emblem-system-symbolic", + tick: "object-select-symbolic", + audio: { + mic: { + muted: "microphone-sensitivity-muted-symbolic", + unmuted: "audio-input-microphone-symbolic", + low: "microphone-sensitivity-low-symbolic", + medium: "microphone-sensitivity-medium-symbolic", + high: "microphone-sensitivity-high-symbolic", + }, + volume: { + muted: "audio-volume-muted-symbolic", + low: "audio-volume-low-symbolic", + medium: "audio-volume-medium-symbolic", + high: "audio-volume-high-symbolic", + overamplified: "audio-volume-overamplified-symbolic", + }, + type: { + headset: "audio-headphones-symbolic", + speaker: "audio-speakers-symbolic", + card: "audio-card-symbolic", + }, + mixer: "view-list-symbolic", + }, + apps: { + apps: "view-app-grid-symbolic", + search: "folder-saved-search-symbolic", + }, + launcher: { + search: "system-search-symbolic", + utility: "applications-utilities-symbolic", + system: "emblem-system-symbolic", + education: "applications-science-symbolic", + development: "applications-engineering-symbolic", + network: "network-wired-symbolic", + office: "x-office-document-symbolic", + game: "applications-games-symbolic", + multimedia: "applications-multimedia-symbolic", + hyprland: "hyprland-symbolic", + firefox: "firefox-symbolic" + }, + quicksettings: { + notifications: "user-available-symbolic", + wifi: "network-wireless-symbolic", + bluetooth: "bluetooth-active-symbolic", + audio: "audio-volume-high-symbolic", + mpris: "audio-x-generic-symbolic", + chatgpt: "chatgpt-symbolic" + }, + bluetooth: { + enabled: "bluetooth-active-symbolic", + disabled: "bluetooth-disabled-symbolic", + }, + brightness: { + indicator: "display-brightness-symbolic", + keyboard: "keyboard-brightness-symbolic", + screen: ["󰛩", "󱩎", "󱩏", "󱩐", "󱩑", "󱩒", "󱩓", "󱩔", "󱩕", "󱩖", "󰛨"], + }, + powermenu: { + sleep: "weather-clear-night-symbolic", + reboot: "system-reboot-symbolic", + logout: "system-log-out-symbolic", + shutdown: "system-shutdown-symbolic", + lock: "system-lock-screen-symbolic", + close: "window-close-symbolic" + }, + recorder: { + recording: "media-record-symbolic", + }, + notifications: { + noisy: "user-available-symbolic", + silent: "notifications-disabled-symbolic", + critical: "messagebox_critical-symbolic", + chat: "user-available-symbolic", + close: "window-close-symbolic" + }, + header: { + refresh: "view-refresh-symbolic", + settings: "emblem-system-symbolic", + power: "system-shutdown-symbolic", + }, + trash: { + full: "user-trash-full-symbolic", + empty: "user-trash-symbolic", + }, + mpris: { + fallback: "audio-x-generic-symbolic", + shuffle: { + enabled: "media-playlist-shuffle-symbolic", + disabled: "media-playlist-no-shuffle-symbolic", + }, + loop: { + none: "media-playlist-no-repeat-symbolic", + track: "media-playlist-repeat-song-symbolic", + playlist: "media-playlist-repeat-symbolic", + }, + playing: "media-playback-pause-symbolic", + paused: "media-playback-start-symbolic", + stopped: "media-playback-stop-symbolic", + prev: "media-skip-backward-symbolic", + next: "media-skip-forward-symbolic", + }, + ai: "chatgpt-symbolic", + ui: { + send: "mail-send-symbolic", + arrow: { + right: "pan-end-symbolic", + left: "pan-start-symbolic", + down: "pan-down-symbolic", + up: "pan-up-symbolic", + }, + }, + weather: { + day: { + "113": "\uf00d", //"Sunny", + "116": "\uf002", //"PartlyCloudy", + "119": "\uf041", //"Cloudy", + "122": "\uf013", //"VeryCloudy", + "143": "\uf003", //"Fog", + "176": "\uf01a", //"LightShowers", + "179": "\uf017", //"LightSleetShowers", + "182": "\uf0b5", //"LightSleet", + "185": "\uf0b5", //"LightSleet", + "200": "\uf01d", //"ThunderyShowers", + "227": "\uf01b", //"LightSnow", + "230": "\uf01b", //"HeavySnow", + "248": "\uf014", //"Fog", + "260": "\uf014", //"Fog", + "263": "\uf01a", //"LightShowers", + "266": "\uf01a", //"LightRain", + "281": "\uf0b5", //"LightSleet", + "284": "\uf0b5", //"LightSleet", + "293": "\uf01a", //"LightRain", + "296": "\uf01a", //"LightRain", + "299": "\uf019", //"HeavyShowers", + "302": "\uf019", //"HeavyRain", + "305": "\uf019", //"HeavyShowers", + "308": "\uf019", //"HeavyRain", + "311": "\uf0b5", //"LightSleet", + "314": "\uf0b5", //"LightSleet", + "317": "\uf0b5", //"LightSleet", + "320": "\uf01b", //"LightSnow", + "323": "\uf017", //"LightSnowShowers", + "326": "\uf017", //"LightSnowShowers", + "329": "\uf01b", //"HeavySnow", + "332": "\uf01b", //"HeavySnow", + "335": "\uf01b", //"HeavySnowShowers", + "338": "\uf01b", //"HeavySnow", + "350": "\uf0b5", //"LightSleet", + "353": "\uf01a", //"LightShowers", + "356": "\uf019", //"HeavyShowers", + "359": "\uf019", //"HeavyRain", + "362": "\uf017", //"LightSleetShowers", + "365": "\uf017", //"LightSleetShowers", + "368": "\uf017", //"LightSnowShowers", + "371": "\uf017", //"HeavySnowShowers", + "374": "\uf0b5", //"LightSleetShowers", + "377": "\uf0b5", //"LightSleet", + "386": "\uf01e", //"ThunderyShowers", + "389": "\uf01e", //"ThunderyHeavyRain", + "392": "\uf01e", //"ThunderySnowShowers", + "395": "\uf01b", //"HeavySnowShowers", + }, + night: { + "113": "\uf02e", // Night + "116": "\uf086", // Partly cloudy, night + "119": "\uf086", // Partly cloudy, night + } + } +}; + + + diff --git a/modules/menu/menu.js b/modules/menu/menu.js new file mode 100644 index 0000000..f37817b --- /dev/null +++ b/modules/menu/menu.js @@ -0,0 +1,36 @@ +import Widget from "resource:///com/github/Aylur/ags/widget.js"; + +/** + * @param {Object} param + * @param {string} param.title + * @param {string} param.icon + * @param {import('gi://Gtk').Gtk.Widget} param.content + * @param {import('gi://Gtk').Gtk.Widget} [param.headerChild] + * @return {import('types/widgets/box').default} + */ +export default ({title, icon, content, headerChild = Widget.Box()}) => Widget.Box({ + children: [ + Widget.Box({ + class_name: "qs-menu", + vertical: true, + children: [ + Widget.Box({ + class_name: "qs-title", + spacing: 5, + children: [ + Widget.Icon(icon), + Widget.Label(title), + Widget.Box({hexpand: true}), + headerChild + ], + }), + Widget.Separator(), + Widget.Box({ + class_name: "qs-content", + children: [content], + }), + ], + }) + ], +}); + diff --git a/modules/shared/barItemBox.js b/modules/shared/barItemBox.js new file mode 100644 index 0000000..2e516d2 --- /dev/null +++ b/modules/shared/barItemBox.js @@ -0,0 +1,15 @@ +export const BarItemBox = (child) => { + const computeVisible = () => { + if (Object.hasOwnProperty.call(child, "isVis")) { + return child.isVis.bind("value"); + } + + return child.isVisible; + } + + return Widget.Box({ + class_name: "bar_item_box_visible", + child: child.component, + visible: computeVisible() + }); +}; diff --git a/modules/volume/volume.js b/modules/volume/volume.js new file mode 100644 index 0000000..417aecb --- /dev/null +++ b/modules/volume/volume.js @@ -0,0 +1,261 @@ +// const audio = await Service.import("audio"); +// +// /** @param {'speaker' | 'microphone'} type */ +// const VolumeSlider = (type = "speaker") => +// Widget.Slider({ +// hexpand: true, +// drawValue: false, +// onChange: ({ value }) => (audio[type].volume = value), +// value: audio[type].bind("volume"), +// }); +// +// const speakerSlider = VolumeSlider("speaker"); +// const micSlider = VolumeSlider("microphone"); +// +// const VolumeCtl = () => { +// const volCtlLabel = Widget.Label({ +// class_name: "volCtlLabel", +// label: "Volume", +// }); +// +// const volSliderBox = Widget.Box( +// { class_name: "volumeSliderBox" }, +// speakerSlider, +// ); +// +// return Widget.Box( +// { +// class_name: "volumeCtlContainer", +// vertical: true, +// css: 'min-width: 100px' +// }, +// volCtlLabel, +// volSliderBox, +// ); +// }; +// +// export const Volume = () => { +// return Widget.Box({ +// child: VolumeCtl(), +// }); +// }; +import icons from "../icons/index.js"; +import Widget from "resource:///com/github/Aylur/ags/widget.js"; +import {execAsync} from "resource:///com/github/Aylur/ags/utils.js"; +import Audio from "resource:///com/github/Aylur/ags/service/audio.js"; +import Hyprland from "resource:///com/github/Aylur/ags/service/hyprland.js"; +import Menu from "../menu/menu.js"; + +/** @param {string} type */ +const sorm = (type) => type === "sink" ? "speaker" : "microphone"; +/** @param {string} type */ +const sorms = (type) => type === "sink" ? "speakers" : "microphones"; +/** @param {string | null} item + * @param {string} type */ +const iconSubstitute = (item, type) => { + const microphoneSubstitutes = { + "audio-headset-analog-usb": "audio-headset-symbolic", + "audio-headset-bluetooth": "audio-headphones-symbolic", + "audio-card-analog-usb": "audio-input-microphone-symbolic", + "audio-card-analog-pci": "audio-input-microphone-symbolic", + "audio-card-analog": "audio-input-microphone-symbolic", + "camera-web-analog-usb": "camera-web-symbolic" + }; + const substitues = { + "audio-headset-bluetooth": "audio-headphones-symbolic", + "audio-card-analog-usb": "audio-speakers-symbolic", + "audio-card-analog-pci": "audio-speakers-symbolic", + "audio-card-analog": "audio-speakers-symbolic", + "audio-headset-analog-usb": "audio-headset-symbolic" + }; + + if (type === "sink") { + return substitues[item] || item; + } + return microphoneSubstitutes[item] || item; +}; + +/** @param {import('types/service/audio').Stream} stream */ +const streamIconSubstiture = stream => { + const subs = { + "spotify": "spotify", + "Firefox": "firefox", + }; + return subs[stream.name] || stream.icon_name; +}; + +/** @param {string} type */ +const TypeIndicator = (type = "sink") => Widget.Button({ + on_clicked: () => execAsync(`pactl set-${type}-mute @DEFAULT_${type.toUpperCase()}@ toggle`), + child: Widget.Icon() + .hook(Audio, icon => { + if (Audio[sorm(type)]) + // @ts-ignore + icon.icon = iconSubstitute(Audio[sorm(type)].icon_name, type); + }, sorm(type) + "-changed") +}); + +/** @param {string} type */ +const PercentLabel = (type = "sink") => Widget.Label({ + class_name: "audio-volume-label", +}) + .hook(Audio, label => { + if (Audio[sorm(type)]) + // @ts-ignore + label.label = `${Math.floor(Audio[sorm(type)].volume * 100)}%`; + }, sorm(type) + "-changed"); + +/** @param {string} type */ +const VolumeSlider = (type = "sink") => Widget.Slider({ + hexpand: true, + draw_value: false, + // @ts-ignore + on_change: ({value}) => Audio[sorm(type)].volume = value, +}) + .hook(Audio, slider => { + if (!Audio[sorm(type)]) + return; + + // @ts-ignore + slider.sensitive = !Audio[sorm(type)].is_muted; + // @ts-ignore + slider.value = Audio[sorm(type)].volume; + }, sorm(type) + "-changed"); + +/** @param {string} type */ +export const Volume = (type = "sink") => Widget.Box({ + class_name: "audio-volume-box", + children: [ + TypeIndicator(type), + VolumeSlider(type), + PercentLabel(type) + ], +}); + +/** @param {import('types/service/audio').Stream} stream */ +const MixerItem = stream => Widget.EventBox({ + on_primary_click: () => stream.is_muted = !stream.is_muted, + on_scroll_up: () => stream.volume += 0.03, + on_scroll_down: () => stream.volume -= 0.03, + child: Widget.Box({ + hexpand: true, + class_name: "mixer-item", + children: [ + Widget.Icon({ + icon: stream.bind("icon_name").transform(() => streamIconSubstiture(stream)), + tooltip_text: stream.bind("name").transform(name => name || "") + }), + Widget.Box({ + vertical: true, + vpack: "center", + children: [ + Widget.Box({ + children: [ + Widget.Label({ + xalign: 0, + hexpand: true, + class_name: "mixer-item-title", + truncate: "end", + label: stream.bind("description").transform(desc => desc || ""), + }), + Widget.Label({ + xalign: 0, + class_name: "mixer-item-volume", + label: stream.bind("volume").transform(volume => `${Math.floor(volume * 100)}%`) + }), + ] + }), + Widget.Slider({ + hexpand: true, + class_name: "mixer-item-slider", + draw_value: false, + value: stream.bind("volume"), + on_change: ({value}) => { + stream.volume = value; + }, + }), + ], + }), + ], + }) +}); + +/** + * @param {string} type + * @returns {function(import('types/service/audio').Stream): import('types/widgets/button').default} + */ +const SinkItem = (type) => stream => Widget.Button({ + on_clicked: () => Audio[sorm(type)] = stream, + child: Widget.Box({ + spacing: 5, + children: [ + Widget.Icon({ + icon: iconSubstitute(stream.icon_name, type), + tooltip_text: stream.icon_name, + }), + Widget.Label(stream.description?.split(" ").slice(0, 4).join(" ")), + Widget.Icon({ + icon: icons.tick, + hexpand: true, + hpack: "end", + }).hook(Audio, icon => { + icon.visible = Audio[sorm(type)] === stream; + }), + ], + }), +}); + +/** @param {number} tab */ +const SettingsButton = (tab = 0) => Widget.Button({ + on_clicked: () => Hyprland.sendMessage("dispatch exec pavucontrol -t " + tab), + child: Widget.Icon(icons.settings), +}); + +export const AppMixer = () => Menu({ + title: "App Mixer", + icon: icons.audio.mixer, + content: Widget.Box({ + class_name: "app-mixer", + vertical: true, + children: [ + Widget.Box({vertical: true}) + .hook(Audio, box => { + box.children = Audio.apps.map(MixerItem); + }, "notify::apps") + ], + }), + headerChild: SettingsButton(1), +}); + +export const SinkSelector = (type = "sink") => Menu({ + title: type + " Selector", + icon: type === "sink" ? icons.audio.type.headset : icons.audio.mic.unmuted, + content: Widget.Box({ + class_name: "sink-selector", + vertical: true, + children: [ + Widget.Box({vertical: true}) + .hook(Audio, box => { + box.children = Array.from(Audio[sorms(type)].values()).map(SinkItem(type)); + }, "stream-added") + .hook(Audio, box => { + box.children = Array.from(Audio[sorms(type)].values()).map(SinkItem(type)); + }, "stream-removed") + ], + }), + headerChild: SettingsButton(type === "sink" ? 3 : 4), +}); + +const AudioContent = () => Widget.Box({ + vertical: true, + class_name: "qs-page", + children: [ + Volume("sink"), + Volume("source"), + SinkSelector("sink"), + SinkSelector("source"), + AppMixer(), + ] +}); + +export default AudioContent; diff --git a/nohup.out b/nohup.out new file mode 100644 index 0000000..3047745 --- /dev/null +++ b/nohup.out @@ -0,0 +1,37 @@ +[2024-06-08 14:18:53.312] [info] Using configuration file /home/jaskir/.config/waybar/config +[2024-06-08 14:18:53.313] [info] Unable to receive desktop appearance: GDBus.Error:org.freedesktop.DBus.Error.UnknownMethod: No such interface “org.freedesktop.portal.Settings” on object at path /org/freedesktop/portal/desktop +[2024-06-08 14:18:53.313] [info] Using CSS file /home/jaskir/.config/waybar/style.css +[2024-06-08 14:18:53.316] [warning] persistent_workspaces is deprecated. Please change config to use persistent-workspaces. +[2024-06-08 14:18:53.316] [info] Hyprland IPC starting +[2024-06-08 14:18:53.317] [info] Loading persistent workspaces from Waybar config +[2024-06-08 14:18:53.317] [info] Loading persistent workspaces from Hyprland workspace rules +[2024-06-08 14:18:53.320] [warning] module backlight: Disabling module "backlight", No backlight found +[2024-06-08 14:18:53.321] [warning] No batteries. +[2024-06-08 14:18:53.354] [warning] persistent_workspaces is deprecated. Please change config to use persistent-workspaces. +[2024-06-08 14:18:53.355] [info] Loading persistent workspaces from Waybar config +[2024-06-08 14:18:53.355] [info] Loading persistent workspaces from Hyprland workspace rules +[2024-06-08 14:18:53.357] [warning] module backlight: Disabling module "backlight", No backlight found +[2024-06-08 14:18:53.358] [warning] No batteries. +[2024-06-08 14:18:53.361] [warning] persistent_workspaces is deprecated. Please change config to use persistent-workspaces. +[2024-06-08 14:18:53.362] [info] Loading persistent workspaces from Waybar config +[2024-06-08 14:18:53.362] [info] Loading persistent workspaces from Hyprland workspace rules +[2024-06-08 14:18:53.363] [warning] module backlight: Disabling module "backlight", No backlight found +[2024-06-08 14:18:53.365] [warning] No batteries. +[2024-06-08 14:18:53.495] [error] Host: GDBus.Error:org.freedesktop.DBus.Error.UnknownMethod: Method RegisterStatusNotifierHost is not implemented +[2024-06-08 14:18:53.495] [error] Host: GDBus.Error:org.freedesktop.DBus.Error.UnknownMethod: Method RegisterStatusNotifierHost is not implemented +[2024-06-08 14:18:53.495] [error] Host: GDBus.Error:org.freedesktop.DBus.Error.UnknownMethod: Method RegisterStatusNotifierHost is not implemented +[2024-06-08 14:18:53.503] [info] Bar configured (width: 1440, height: 38) for output: DP-1 +[2024-06-08 14:18:53.503] [info] Bar configured (width: 1440, height: 38) for output: DP-2 +[2024-06-08 14:18:53.503] [info] Bar configured (width: 3840, height: 38) for output: DP-3 + +(waybar:4162481): Gtk-CRITICAL **: 14:19:32.496: gtk_widget_set_accel_path: assertion 'GTK_IS_ACCEL_GROUP (accel_group)' failed + +(waybar:4162481): Gtk-CRITICAL **: 14:19:32.496: gtk_widget_add_accelerator: assertion 'GTK_IS_ACCEL_GROUP (accel_group)' failed + +(waybar:4162481): Gtk-CRITICAL **: 14:19:32.497: gtk_widget_set_accel_path: assertion 'GTK_IS_ACCEL_GROUP (accel_group)' failed + +(waybar:4162481): Gtk-CRITICAL **: 14:19:32.497: gtk_widget_add_accelerator: assertion 'GTK_IS_ACCEL_GROUP (accel_group)' failed + +(waybar:4162481): Gtk-CRITICAL **: 14:19:32.497: gtk_widget_set_accel_path: assertion 'GTK_IS_ACCEL_GROUP (accel_group)' failed + +(waybar:4162481): Gtk-CRITICAL **: 14:19:32.497: gtk_widget_add_accelerator: assertion 'GTK_IS_ACCEL_GROUP (accel_group)' failed diff --git a/scss/bar/audio.scss b/scss/bar/audio.scss new file mode 100644 index 0000000..29f8e3c --- /dev/null +++ b/scss/bar/audio.scss @@ -0,0 +1,36 @@ +@import '../colors'; + +.audio-volume-box { + .audio-volume-label { + min-width: 3rem; + } +} + +.mixer-item { + margin: 5px 0px; + + .mixer-item-volume { + margin: 0px 8px; + } + + .mixer-item-title { + margin: 0px 8px; + } + + .mixer-item-slider { + margin: 8px; + } + + image { + font-size: 40px; + } +} + +.bar-volume_icon { + font-size: 17px; + color: $peach; +} + +.bar-volume_percentage { + color: $peach; +} diff --git a/scss/bar/bar.scss b/scss/bar/bar.scss new file mode 100644 index 0000000..373dd7c --- /dev/null +++ b/scss/bar/bar.scss @@ -0,0 +1,31 @@ +@import "../colors"; + +.bar { + background: $mantle; +} + +.bar * { + font-weight: bold; +} + +.bar_item_box_visible { + background: $surface0; + border-radius: 8px; + padding: 1px 12px; + margin: 6px 3px; +} + +.bar_item_box_hidden { + background: none; + border-radius: 0px; + padding: 0px 0px 0px 0px; + margin: 0px 0px 0px 0px; +} + +.box-left { + margin-left: 25px; +} + +.box-right { + margin-right: 25px; +} diff --git a/scss/bar/bluetooth.scss b/scss/bar/bluetooth.scss new file mode 100644 index 0000000..473b894 --- /dev/null +++ b/scss/bar/bluetooth.scss @@ -0,0 +1,10 @@ +@import '../colors'; + +.bar-bt_icon { + font-size: 17px; + color: $sky; +} + +.bar-bt_label { + color: $sky; +} diff --git a/scss/bar/clock.scss b/scss/bar/clock.scss new file mode 100644 index 0000000..e7988c5 --- /dev/null +++ b/scss/bar/clock.scss @@ -0,0 +1,5 @@ +@import "../colors"; + +.clock { + color: $pink; +} diff --git a/scss/bar/systray.scss b/scss/bar/systray.scss new file mode 100644 index 0000000..221f60c --- /dev/null +++ b/scss/bar/systray.scss @@ -0,0 +1,26 @@ +@import "../colors"; + +.systray button:not(:first-child) { + margin-left: 10px; +} + +.systray-menu { + background: $mantle; +} +.systray-menu label { + font-weight: bold; + color: $lavender; +} + +.systray-menu separator { + background-color: $surface1; +} + +.systray-menu check:not(:checked) { + background-color: $surface0; + border: 1px solid $lavender; +} + +.systray-menu check:checked { + background-color: $lavender; +} diff --git a/scss/bar/window_title.scss b/scss/bar/window_title.scss new file mode 100644 index 0000000..461fc84 --- /dev/null +++ b/scss/bar/window_title.scss @@ -0,0 +1,5 @@ +@import "../colors"; + +.window_title { + color: $yellow; +} diff --git a/scss/bar/workspace.scss b/scss/bar/workspace.scss new file mode 100644 index 0000000..65af538 --- /dev/null +++ b/scss/bar/workspace.scss @@ -0,0 +1,30 @@ +@import "../colors"; + +.workspaces { + label { + font-size: 0px; + min-width: 12px; + min-height: 12px; + border-radius: 11px * .6; + margin: 0px 7px * .5; + transition: 300ms * .5; + background-color: $lavender; + + &.occupied { + background-color: $maroon; + min-width: 12px; + min-height: 12px; + } + + &.active { + background-color: $sky; + min-width: 30px; + min-height: 12px; + } + } +} + + +.workspaces label:not(:first-child) { + margin-left: 12px; +} diff --git a/scss/colors.scss b/scss/colors.scss new file mode 100644 index 0000000..66679d6 --- /dev/null +++ b/scss/colors.scss @@ -0,0 +1,49 @@ +$primary-color: #CDD6F4; +$dark-background: #0e0e1e; +$light-background: #1e1e2e; +$mauve: #cba6f7; +$red: #f38ba8; +$yellow: #f9e2af; +$orange: #fab387; +$teal: #94e2d5; +$lightteal: #bac2de; +$pink: #f5c2e7; +$green: #a6e3a1; +$grey: #585b70; +$blue: #89b4fa; +$lightgrey: #a6adc8; +$lightblue: #74c7ec; + +$rosewater: #f5e0dc; +$flamingo: #f2cdcd; +$pink: #f5c2e7; +$mauve: #cba6f7; +$red: #f38ba8; +$maroon: #eba0ac; +$peach: #fab387; +$yellow: #f9e2af; +$green: #a6e3a1; +$teal: #94e2d5; +$sky: #89dceb; +$sapphire: #74c7ec; +$blue: #89b4fa; +$lavender: #b4befe; +$text: #cdd6f4; +$subtext1: #bac2de; +$subtext2: #a6adc8; +$overlay2: #9399b2; +$overlay1: #7f849c; +$overlay0: #6c7086; +$surface2: #585b70; +$surface1: #45475a; +$surface0: #313244; +$base: #1e1e2e; +$mantle: #181825; +$crust: #11111b; + +$default_fg: $primary-color; +$default_bg: #000000; +$primary_bg: $dark-background; +$primary_fg: mix($mauve, $primary_bg, 70%); +$secondary_fg: $green; +$secondary_bg: $light-background; diff --git a/scss/common.scss b/scss/common.scss new file mode 100644 index 0000000..35ab4d0 --- /dev/null +++ b/scss/common.scss @@ -0,0 +1,230 @@ +menu { + margin: 6px; + padding: 6px; + background-color: $primary_bg; + background-clip: border-box; + border-radius: 12px; + border: 1px solid $secondary_bg; + + menuitem { + transition: background-color 75ms cubic-bezier(0, 0, 0.2, 1); + min-height: 20px; + min-width: 40px; + padding: 4px 8px; + color: #FFFFFF; + font: initial; + text-shadow: none; + border-radius: 6px; + &:hover, &:active{ + background-color: $secondary_bg; + } + &:disabled { + color: $secondary_bg; + } + arrow { + min-height: 16px; + min-width: 16px; + -gtk-icon-source: -gtk-icontheme("pan-end-symbolic"); + margin-left: 8px; + } + } + separator { + min-height: 1px; + margin: 4px 0; + background-color: $secondary_bg + } +} + +menu > arrow { + min-height: 16px; + min-width: 16px; + padding: 4px; + color: $secondary_bg; +} + +menu > arrow.top { + margin-top: 0; + border-radius: 6px; + -gtk-icon-source: -gtk-icontheme("pan-up-symbolic"); +} + +menu > arrow.bottom { + margin-top: 8px; + margin-bottom: -12px; + border-radius: 6px; + -gtk-icon-source: -gtk-icontheme("pan-down-symbolic"); +} + +check, +radio { + min-height: 15px; + min-width: 15px; + margin: 4px; + padding: 0; + color: transparent; + background-color: $secondary_bg; + transition: all 75ms cubic-bezier(0, 0, 0.2, 1), box-shadow 150ms cubic-bezier(0, 0, 0.2, 1); +} + +radio { + border-radius: 9999px; +} + +check { + border-radius: 4px; +} + +check:hover, +radio:hover { + box-shadow: 0 0 0 4px transparentize($primary_bg, 0.8); + background-color: $primary_bg; +} + +check:active, +radio:active { + box-shadow: 0 0 0 4px transparentize($primary_bg, 0.8); + background-color: $primary_bg; +} + +check:checked, check:indeterminate, +radio:checked, +radio:indeterminate { + color: $primary_bg; + background-color: $primary_fg; +} + +check:checked:hover, check:indeterminate:hover, +radio:checked:hover, +radio:indeterminate:hover { + box-shadow: 0 0 0 4px transparentize($primary_fg, 0.8); + background-color: $primary_fg; +} + +check:checked:active, check:indeterminate:active, +radio:checked:active, +radio:indeterminate:active { + box-shadow: 0 0 0 4px transparentize($primary_fg, 0.8); + background-color: $primary_fg; +} + +switch { + transition: all 75ms cubic-bezier(0, 0, 0.2, 1); + margin: 4px 0; + border: none; + border-radius: 9999px; + background-color: $secondary_bg; + background-clip: padding-box; + font-size: 0; + color: transparent; +} + +switch:checked { + background-color: $primary_fg; +} + +switch:disabled { + opacity: 0.5; +} + +switch slider { + transition: all 75ms cubic-bezier(0, 0, 0.2, 1); + min-width: 18px; + min-height: 18px; + margin: 3px; + border-radius: 9999px; + outline: none; + background-color: $default_fg; + border: none; + color: transparent; +} + +scale { + min-height: 2px; + min-width: 2px; +} + +scale.horizontal { + padding: 17px 12px; +} + +scale.vertical { + padding: 12px 17px; +} + +scale slider { + min-height: 18px; + min-width: 18px; + margin: -8px; +} + +scale trough { + transition: background-color 75ms cubic-bezier(0, 0, 0.2, 1); + outline: none; + background-color: $secondary_bg; +} + +scale highlight { + transition: background-color 75ms cubic-bezier(0, 0, 0.2, 1); + background-color: $primary_fg; +} + +scale highlight:disabled { + background-color: #1e1e2e; +} + +scale fill { + transition: background-color 75ms cubic-bezier(0, 0, 0.2, 1); +} + +scale fill:disabled { + background-color: transparent; +} + +scale slider { + transition: all 75ms cubic-bezier(0, 0, 0.2, 1); + border-radius: 9999px; + color: $primary_fg; + background-color: $primary_bg; + box-shadow: inset 0 0 0 2px $primary_fg; +} + +scale slider:hover { + box-shadow: inset 0 0 0 2px $primary_fg, 0 0 0 8px transparentize($primary_fg, 0.9); +} + +scale slider:active { + box-shadow: inset 0 0 0 4px $primary_fg, 0 0 0 8px transparentize($primary_fg, 0.9); +} + +tooltip { + box-shadow: none; +} + +tooltip.background { + background-color: $primary_bg; + color: $default_fg; + border-radius: 6px; +} + +separator { + min-width: 1px; + min-height: 1px; + background-color: $secondary_bg; +} + +@keyframes spin { + to { + -gtk-icon-transform: rotate(1turn); + } +} + +spinner { + background: none; + opacity: 0; + -gtk-icon-source: -gtk-icontheme("process-working-symbolic"); +} + +spinner:checked { + opacity: 1; + animation: spin 1s linear infinite; +} diff --git a/scss/highlights.scss b/scss/highlights.scss new file mode 100644 index 0000000..88837b9 --- /dev/null +++ b/scss/highlights.scss @@ -0,0 +1,221 @@ +@import "colors"; + +* { + color: $default_fg; + font-family: "JetBrainsMono NF"; + font-size: 0.9rem; +} + +html, body { + padding: 0; + margin: 0; + background-color: $primary_bg; +} + +::-webkit-scrollbar { + display: none; +} + +.code { + background: $light-background; + border-radius: 0.5rem; + + .code-header { + background: $light-background; + border-top-left-radius: 0.5rem; + border-top-right-radius: 0.5rem; + border-bottom: 1px solid $primary_fg; + padding: 5px; + + > button { + color: $default_fg; + background: transparent; + float: right; + border: none; + + &:before { + content: '󰆏'; + display: inline-block; + padding-right: 0.5rem; + } + } + } +} + +$languages-map: ( + arduino: "", + armasm: "", + avrasm: "", + bash: "", + c: "", + clojure: "", + coffeescript: "", + cpp: "", + csharp: "󰌛", + css: "", + dockerfile: "󰡨", + go: "", + gradle: "", + haskell: "", + html: "", + java: "", + javascript: "󰌞", + json: "", + latex: "", + lua: "󰢱", + makefile: "", + markdown: "", + mipsasm: "", + nginx: "", + nix: "󱄅", + php: "", + prolog: "", + python: "", + r: "󰟔", + ruby: "", + rust: "", + scss: "", + shell: "", + typescript: "󰛦", + wasm: "", + x86asm: "", + xml: "󰗀", +); + +@each $lang, $content in $languages-map { + [data-language="#{$lang}"]:before { + content: $content; + font-size: 1.1rem; + color: $primary_fg; + padding-right: 0.5rem; + } +} + +pre { + padding: 5px; + overflow-x: scroll; + + code.hljs { + color: $default_fg; + background: transparent; + } +} + +code { + & .hljs-keyword { + color: $mauve; + } + + & .hljs-built_in { + color: $red; + } + + & .hljs-type { + color: $yellow; + } + + & .hljs-literal, + & .hljs-number { + color: $orange; + } + + & .hljs-operator { + color: $teal; + } + + & .hljs-punctuation { + color: $lightteal; + } + + & .hljs-property, + & .hljs-variable.language_, + & .hljs-symbol { + color: $teal; + } + + & .hljs-regexp { + color: $pink; + } + + & .hljs-string, + & .hljs-char.escape_, + & .hljs-subst { + color: $green; + } + + & .hljs-comment { + color: $grey; + } + + & .hljs-doctag { + color: $red; + } + + & .hljs-meta, + & .hljs-title.function_, + & .hljs-section { + color: $orange; + } + + & .hljs-tag, + & .hljs-attribute { + color: $lightgrey; + } + + & .hljs-name, + & .hljs-selector-attr { + color: $mauve; + } + + & .hljs-params, + & .hljs-selector-class, + & .hljs-template-variable { + color: $default_fg; + } + + & .hljs-selector-tag { + color: $yellow; + } + + & .hljs-selector-id { + color: $blue; + } + + & .hljs-bullet, + & .hljs-code, + & .hljs-formula { + color: $teal; + } + + & .hljs-emphasis { + color: $red; + font-style: italic; + } + + & .hljs-strong { + color: $red; + font-weight: bold; + } + + & .hljs-link { + color: $lightblue; + font-style: italic; + } + + & .hljs-quote { + color: $green; + font-style: italic; + } + + & .hljs-addition { + color: $green; + background: rgba(166, 227, 161, 0.15); + } + + & .hljs-deletion { + color: $red; + background: rgba(243, 139, 168, 0.15); + } +} + + diff --git a/scss/main.scss b/scss/main.scss new file mode 100644 index 0000000..dec01af --- /dev/null +++ b/scss/main.scss @@ -0,0 +1,17 @@ +* { + all: unset; + font-family: "JetBrains Mono Nerd Font"; +} + +//general +@import "colors"; +@import "common"; + +//modules - bar +@import "bar/audio"; +@import "bar/bluetooth"; +@import "bar/clock"; +@import "bar/workspace"; +@import "bar/window_title"; +@import "bar/systray"; +@import "bar/bar"; diff --git a/style.css b/style.css new file mode 100644 index 0000000..0613880 --- /dev/null +++ b/style.css @@ -0,0 +1,361 @@ +* { + all: unset; + font-family: "JetBrains Mono Nerd Font"; +} + +menu { + margin: 6px; + padding: 6px; + background-color: #0e0e1e; + background-clip: border-box; + border-radius: 12px; + border: 1px solid #1e1e2e; +} +menu menuitem { + transition: background-color 75ms cubic-bezier(0, 0, 0.2, 1); + min-height: 20px; + min-width: 40px; + padding: 4px 8px; + color: #FFFFFF; + font: initial; + text-shadow: none; + border-radius: 6px; +} +menu menuitem:hover, menu menuitem:active { + background-color: #1e1e2e; +} +menu menuitem:disabled { + color: #1e1e2e; +} +menu menuitem arrow { + min-height: 16px; + min-width: 16px; + -gtk-icon-source: -gtk-icontheme("pan-end-symbolic"); + margin-left: 8px; +} +menu separator { + min-height: 1px; + margin: 4px 0; + background-color: #1e1e2e; +} + +menu > arrow { + min-height: 16px; + min-width: 16px; + padding: 4px; + color: #1e1e2e; +} + +menu > arrow.top { + margin-top: 0; + border-radius: 6px; + -gtk-icon-source: -gtk-icontheme("pan-up-symbolic"); +} + +menu > arrow.bottom { + margin-top: 8px; + margin-bottom: -12px; + border-radius: 6px; + -gtk-icon-source: -gtk-icontheme("pan-down-symbolic"); +} + +check, +radio { + min-height: 15px; + min-width: 15px; + margin: 4px; + padding: 0; + color: transparent; + background-color: #1e1e2e; + transition: all 75ms cubic-bezier(0, 0, 0.2, 1), box-shadow 150ms cubic-bezier(0, 0, 0.2, 1); +} + +radio { + border-radius: 9999px; +} + +check { + border-radius: 4px; +} + +check:hover, +radio:hover { + box-shadow: 0 0 0 4px rgba(14, 14, 30, 0.2); + background-color: #0e0e1e; +} + +check:active, +radio:active { + box-shadow: 0 0 0 4px rgba(14, 14, 30, 0.2); + background-color: #0e0e1e; +} + +check:checked, check:indeterminate, +radio:checked, +radio:indeterminate { + color: #0e0e1e; + background-color: #9278b6; +} + +check:checked:hover, check:indeterminate:hover, +radio:checked:hover, +radio:indeterminate:hover { + box-shadow: 0 0 0 4px rgba(146, 120, 182, 0.2); + background-color: #9278b6; +} + +check:checked:active, check:indeterminate:active, +radio:checked:active, +radio:indeterminate:active { + box-shadow: 0 0 0 4px rgba(146, 120, 182, 0.2); + background-color: #9278b6; +} + +switch { + transition: all 75ms cubic-bezier(0, 0, 0.2, 1); + margin: 4px 0; + border: none; + border-radius: 9999px; + background-color: #1e1e2e; + background-clip: padding-box; + font-size: 0; + color: transparent; +} + +switch:checked { + background-color: #9278b6; +} + +switch:disabled { + opacity: 0.5; +} + +switch slider { + transition: all 75ms cubic-bezier(0, 0, 0.2, 1); + min-width: 18px; + min-height: 18px; + margin: 3px; + border-radius: 9999px; + outline: none; + background-color: #CDD6F4; + border: none; + color: transparent; +} + +scale { + min-height: 2px; + min-width: 2px; +} + +scale.horizontal { + padding: 17px 12px; +} + +scale.vertical { + padding: 12px 17px; +} + +scale slider { + min-height: 18px; + min-width: 18px; + margin: -8px; +} + +scale trough { + transition: background-color 75ms cubic-bezier(0, 0, 0.2, 1); + outline: none; + background-color: #1e1e2e; +} + +scale highlight { + transition: background-color 75ms cubic-bezier(0, 0, 0.2, 1); + background-color: #9278b6; +} + +scale highlight:disabled { + background-color: #1e1e2e; +} + +scale fill { + transition: background-color 75ms cubic-bezier(0, 0, 0.2, 1); +} + +scale fill:disabled { + background-color: transparent; +} + +scale slider { + transition: all 75ms cubic-bezier(0, 0, 0.2, 1); + border-radius: 9999px; + color: #9278b6; + background-color: #0e0e1e; + box-shadow: inset 0 0 0 2px #9278b6; +} + +scale slider:hover { + box-shadow: inset 0 0 0 2px #9278b6, 0 0 0 8px rgba(146, 120, 182, 0.1); +} + +scale slider:active { + box-shadow: inset 0 0 0 4px #9278b6, 0 0 0 8px rgba(146, 120, 182, 0.1); +} + +tooltip { + box-shadow: none; +} + +tooltip.background { + background-color: #0e0e1e; + color: #CDD6F4; + border-radius: 6px; +} + +separator { + min-width: 1px; + min-height: 1px; + background-color: #1e1e2e; +} + +@keyframes spin { + to { + -gtk-icon-transform: rotate(1turn); + } +} +spinner { + background: none; + opacity: 0; + -gtk-icon-source: -gtk-icontheme("process-working-symbolic"); +} + +spinner:checked { + opacity: 1; + animation: spin 1s linear infinite; +} + +.audio-volume-box .audio-volume-label { + min-width: 3rem; +} + +.mixer-item { + margin: 5px 0px; +} +.mixer-item .mixer-item-volume { + margin: 0px 8px; +} +.mixer-item .mixer-item-title { + margin: 0px 8px; +} +.mixer-item .mixer-item-slider { + margin: 8px; +} +.mixer-item image { + font-size: 40px; +} + +.bar-volume_icon { + font-size: 17px; + color: #fab387; +} + +.bar-volume_percentage { + color: #fab387; +} + +.bar-bt_icon { + font-size: 17px; + color: #89dceb; +} + +.bar-bt_label { + color: #89dceb; +} + +.clock { + color: #f5c2e7; +} + +.workspaces label { + font-size: 0px; + min-width: 12px; + min-height: 12px; + border-radius: 6.6px; + margin: 0px 3.5px; + transition: 150ms; + background-color: #b4befe; +} +.workspaces label.occupied { + background-color: #eba0ac; + min-width: 12px; + min-height: 12px; +} +.workspaces label.active { + background-color: #89dceb; + min-width: 30px; + min-height: 12px; +} + +.workspaces label:not(:first-child) { + margin-left: 12px; +} + +.window_title { + color: #f9e2af; +} + +.systray button:not(:first-child) { + margin-left: 10px; +} + +.systray-menu { + background: #181825; +} + +.systray-menu label { + font-weight: bold; + color: #b4befe; +} + +.systray-menu separator { + background-color: #45475a; +} + +.systray-menu check:not(:checked) { + background-color: #313244; + border: 1px solid #b4befe; +} + +.systray-menu check:checked { + background-color: #b4befe; +} + +.bar { + background: #181825; +} + +.bar * { + font-weight: bold; +} + +.bar_item_box_visible { + background: #313244; + border-radius: 8px; + padding: 1px 12px; + margin: 6px 3px; +} + +.bar_item_box_hidden { + background: none; + border-radius: 0px; + padding: 0px 0px 0px 0px; + margin: 0px 0px 0px 0px; +} + +.box-left { + margin-left: 25px; +} + +.box-right { + margin-right: 25px; +} + +/*# sourceMappingURL=style.css.map */ diff --git a/style.css.map b/style.css.map new file mode 100644 index 0000000..dfe386c --- /dev/null +++ b/style.css.map @@ -0,0 +1 @@ +{"version":3,"sourceRoot":"","sources":["scss/main.scss","scss/common.scss","scss/colors.scss","scss/bar/audio.scss","scss/bar/bluetooth.scss","scss/bar/clock.scss","scss/bar/workspace.scss","scss/bar/window_title.scss","scss/bar/systray.scss","scss/bar/bar.scss"],"names":[],"mappings":"AAAA;EACE;EACA;;;ACFF;EACE;EACA;EACA,kBCFgB;EDGhB;EACA;EACA;;AAEA;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AACA;EACI,kBChBW;;ADkBf;EACE,OCnBa;;ADqBf;EACE;EACA;EACA;EACA;;AAGJ;EACE;EACA;EACA,kBC/Be;;;ADmCnB;EACE;EACA;EACA;EACA,OCvCiB;;;AD0CnB;EACE;EACA;EACA;;;AAGF;EACE;EACA;EACA;EACA;;;AAGF;AAAA;EAEE;EACA;EACA;EACA;EACA;EACA,kBC9DiB;ED+DjB;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;AAAA;EAEE;EACA,kBC9EgB;;;ADiFlB;AAAA;EAEE;EACA,kBCpFgB;;;ADuFlB;AAAA;AAAA;EAGE,OC1FgB;ED2FhB,kBC9CW;;;ADiDb;AAAA;AAAA;EAGE;EACA,kBCrDW;;;ADwDb;AAAA;AAAA;EAGE;EACA,kBC5DW;;;AD+Db;EACE;EACA;EACA;EACA;EACA,kBChHiB;EDiHjB;EACA;EACA;;;AAGF;EACE,kBC3EW;;;AD8Eb;EACE;;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA,kBCvIc;EDwId;EACA;;;AAGF;EACE;EACA;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;EACA;EACA;;;AAGF;EACE;EACA;EACA,kBChKiB;;;ADmKnB;EACE;EACA,kBCzHW;;;AD4Hb;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;EACA;EACA,OC3IW;ED4IX,kBCzLgB;ED0LhB;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE,kBC1MgB;ED2MhB,OC5Mc;ED6Md;;;AAGF;EACE;EACA;EACA,kBCjNiB;;;ADoNnB;EACE;IACE;;;AAIJ;EACE;EACA;EACA;;;AAGF;EACE;EACA;;;AEjOA;EACE;;;AAIJ;EACE;;AAEA;EACE;;AAGF;EACE;;AAGF;EACE;;AAGF;EACE;;;AAIJ;EACE;EACA,ODRM;;;ACWR;EACE,ODZM;;;AEpBR;EACE;EACA,OFsBI;;;AEnBN;EACE,OFkBI;;;AGxBN;EACE,OHeK;;;AIfL;EACE;EACA;EACA;EACA;EACA;EACA;EACA,kBJmBO;;AIjBP;EACE,kBJQG;EIPH;EACA;;AAGF;EACE,kBJOA;EINA;EACA;;;AAMN;EACE;;;AC1BF;EACE,OLoBO;;;AMrBT;EACE;;;AAGF;EACE,YNiCO;;;AM/BT;EACE;EACA,ONkBS;;;AMfX;EACE,kBNsBS;;;AMnBX;EACE,kBNmBS;EMlBT;;;AAGF;EACE,kBNKS;;;AO3BX;EACE,YPqCO;;;AOlCT;EACE;;;AAGF;EACE,YP2BS;EO1BT;EACA;EACA;;;AAGF;EACE;EACA;EACA;EACA;;;AAGF;EACE;;;AAGF;EACE","file":"style.css"} \ No newline at end of file diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..f03f2d1 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,18 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "ES2022", + "lib": [ + "ES2022" + ], + "allowJs": true, + "checkJs": true, + "strict": true, + "noImplicitAny": false, + "baseUrl": ".", + "typeRoots": [ + "./types" + ], + "skipLibCheck": true + } +} \ No newline at end of file diff --git a/types b/types new file mode 120000 index 0000000..a8e3f04 --- /dev/null +++ b/types @@ -0,0 +1 @@ +/usr/share/com.github.Aylur.ags/types \ No newline at end of file