Compare commits
13 Commits
Author | SHA1 | Date | |
---|---|---|---|
a2baad9d72 | |||
![]() |
676f69c416 | ||
3d32b8197a | |||
dd8fc68c62 | |||
![]() |
4e545f07ef | ||
![]() |
f64f06b35b | ||
![]() |
811b745f51 | ||
![]() |
a41dd49fa3 | ||
![]() |
2ddb8a05cd | ||
![]() |
5a6284d697 | ||
![]() |
40df97aed0 | ||
![]() |
ad3e377963 | ||
![]() |
afc9ff29e7 |
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
__pycache__
|
||||
style/style.css
|
64
config.js
64
config.js
@ -1,64 +0,0 @@
|
||||
const { execAsync } = Utils
|
||||
|
||||
// import { reveal_menu, reveal_launcher, Bar, FakeBar } from './windows/bar.js'
|
||||
import { Bar, FakeBar } from './windows/top-bar.js'
|
||||
import { NotificationPopups } from './windows/notifications.js'
|
||||
import { Lock, show_lock } from './windows/lock.js'
|
||||
|
||||
execAsync('mpDris2')
|
||||
|
||||
Utils.monitorFile(
|
||||
`./style/style.scss`,
|
||||
|
||||
function() {
|
||||
const scss = `./style/style.scss`
|
||||
|
||||
const css = `./style/style.css`
|
||||
|
||||
Utils.exec(`sassc ${scss} ${css}`)
|
||||
App.resetCss()
|
||||
App.applyCss(css)
|
||||
},
|
||||
)
|
||||
|
||||
App.addIcons('/usr/share/icons/Papirus/symbolic/status')
|
||||
|
||||
App.config({
|
||||
windows: [
|
||||
Lock,
|
||||
FakeBar,
|
||||
Bar,
|
||||
NotificationPopups,
|
||||
],
|
||||
style: './style/style.css',
|
||||
icons: './icons'
|
||||
})
|
||||
|
||||
Object.defineProperty(globalThis, "lock", {
|
||||
get() {
|
||||
show_lock.value = true
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
|
||||
/*
|
||||
Object.defineProperty(globalThis, "swipeRight", {
|
||||
get() {
|
||||
if (reveal_menu.value) {
|
||||
reveal_launcher.value = true
|
||||
} else {
|
||||
reveal_menu.value = true
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
Object.defineProperty(globalThis, "swipeLeft", {
|
||||
get() {
|
||||
if (reveal_launcher.value) {
|
||||
reveal_launcher.value = false
|
||||
} else {
|
||||
reveal_menu.value = false
|
||||
}
|
||||
}
|
||||
})*/
|
@ -1 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M2,7V8.5H3V17H4.5V7C3.7,7 2.8,7 2,7M6,7V7L6,16H7V17H14V16H22V7H6M17.5,9A2.5,2.5 0 0,1 20,11.5A2.5,2.5 0 0,1 17.5,14A2.5,2.5 0 0,1 15,11.5A2.5,2.5 0 0,1 17.5,9Z" /></svg>
|
Before Width: | Height: | Size: 238 B |
@ -1 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M12,11A1,1 0 0,0 11,12A1,1 0 0,0 12,13A1,1 0 0,0 13,12A1,1 0 0,0 12,11M12.5,2C17,2 17.11,5.57 14.75,6.75C13.76,7.24 13.32,8.29 13.13,9.22C13.61,9.42 14.03,9.73 14.35,10.13C18.05,8.13 22.03,8.92 22.03,12.5C22.03,17 18.46,17.1 17.28,14.73C16.78,13.74 15.72,13.3 14.79,13.11C14.59,13.59 14.28,14 13.88,14.34C15.87,18.03 15.08,22 11.5,22C7,22 6.91,18.42 9.27,17.24C10.25,16.75 10.69,15.71 10.89,14.79C10.4,14.59 9.97,14.27 9.65,13.87C5.96,15.85 2,15.07 2,11.5C2,7 5.56,6.89 6.74,9.26C7.24,10.25 8.29,10.68 9.22,10.87C9.41,10.39 9.73,9.97 10.14,9.65C8.15,5.96 8.94,2 12.5,2Z" /></svg>
|
Before Width: | Height: | Size: 648 B |
@ -1 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 576 512"><!--!Font Awesome Free 6.5.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2024 Fonticons, Inc.--><path d="M64 64C28.7 64 0 92.7 0 128v7.4c0 6.8 4.4 12.6 10.1 16.3C23.3 160.3 32 175.1 32 192s-8.7 31.7-21.9 40.3C4.4 236 0 241.8 0 248.6V320H576V248.6c0-6.8-4.4-12.6-10.1-16.3C552.7 223.7 544 208.9 544 192s8.7-31.7 21.9-40.3c5.7-3.7 10.1-9.5 10.1-16.3V128c0-35.3-28.7-64-64-64H64zM576 352H0v64c0 17.7 14.3 32 32 32H80V416c0-8.8 7.2-16 16-16s16 7.2 16 16v32h96V416c0-8.8 7.2-16 16-16s16 7.2 16 16v32h96V416c0-8.8 7.2-16 16-16s16 7.2 16 16v32h96V416c0-8.8 7.2-16 16-16s16 7.2 16 16v32h48c17.7 0 32-14.3 32-32V352zM192 160v64c0 17.7-14.3 32-32 32s-32-14.3-32-32V160c0-17.7 14.3-32 32-32s32 14.3 32 32zm128 0v64c0 17.7-14.3 32-32 32s-32-14.3-32-32V160c0-17.7 14.3-32 32-32s32 14.3 32 32zm128 0v64c0 17.7-14.3 32-32 32s-32-14.3-32-32V160c0-17.7 14.3-32 32-32s32 14.3 32 32z"/></svg>
|
Before Width: | Height: | Size: 990 B |
@ -1 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><!--!Font Awesome Free 6.5.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2024 Fonticons, Inc.--><path d="M176 24c0-13.3-10.7-24-24-24s-24 10.7-24 24V64c-35.3 0-64 28.7-64 64H24c-13.3 0-24 10.7-24 24s10.7 24 24 24H64v56H24c-13.3 0-24 10.7-24 24s10.7 24 24 24H64v56H24c-13.3 0-24 10.7-24 24s10.7 24 24 24H64c0 35.3 28.7 64 64 64v40c0 13.3 10.7 24 24 24s24-10.7 24-24V448h56v40c0 13.3 10.7 24 24 24s24-10.7 24-24V448h56v40c0 13.3 10.7 24 24 24s24-10.7 24-24V448c35.3 0 64-28.7 64-64h40c13.3 0 24-10.7 24-24s-10.7-24-24-24H448V280h40c13.3 0 24-10.7 24-24s-10.7-24-24-24H448V176h40c13.3 0 24-10.7 24-24s-10.7-24-24-24H448c0-35.3-28.7-64-64-64V24c0-13.3-10.7-24-24-24s-24 10.7-24 24V64H280V24c0-13.3-10.7-24-24-24s-24 10.7-24 24V64H176V24zM160 128H352c17.7 0 32 14.3 32 32V352c0 17.7-14.3 32-32 32H160c-17.7 0-32-14.3-32-32V160c0-17.7 14.3-32 32-32zm192 32H160V352H352V160z"/></svg>
|
Before Width: | Height: | Size: 993 B |
@ -1 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M15 13V5A3 3 0 0 0 9 5V13A5 5 0 1 0 15 13M12 4A1 1 0 0 1 13 5V8H11V5A1 1 0 0 1 12 4Z" /></svg>
|
Before Width: | Height: | Size: 163 B |
26
main.hy
Normal file
26
main.hy
Normal file
@ -0,0 +1,26 @@
|
||||
(import astal *)
|
||||
(import astal.gtk3 *)
|
||||
|
||||
(import glob [glob])
|
||||
|
||||
(import widgets)
|
||||
|
||||
(exec-async "mpDris2")
|
||||
|
||||
(defn compile-scss []
|
||||
(exec "sass style/style.scss style/style.css"))
|
||||
|
||||
;; (defn watch-style []
|
||||
;; (lfor file (glob "./style/**" :recursive True)
|
||||
;; (when (not (in ".css" file)) (monitor-file file (fn [_, op] (when (= op 1) (print file op) (compile-scss) (.apply-css App "./style/style.css")))))))
|
||||
|
||||
(compile-scss)
|
||||
|
||||
(.start App
|
||||
:main (fn []
|
||||
;; (.show-all widgets.bar)
|
||||
;; (.add-window App widgets.bar)
|
||||
(.show-all widgets.notifications)
|
||||
(.add-window App widgets.notifications))
|
||||
:instance-name "hy-test"
|
||||
:css "./style/style.css")
|
8
readme.md
Normal file
8
readme.md
Normal file
@ -0,0 +1,8 @@
|
||||
# nat/ui
|
||||
the desktop shell for its computer.
|
||||
|
||||
liable to be ported to a different language around 3 more times.
|
||||
|
||||
it wrote python bindings for libastal because it hates javascript and lgi documentation is questionable
|
||||
|
||||
it should probably submit a pull to github:aylur/astal to get its python bindings added
|
@ -1,44 +0,0 @@
|
||||
class Brightness extends Service {
|
||||
static {
|
||||
Service.register(
|
||||
this,
|
||||
{},
|
||||
{
|
||||
screen: ["float", "rw"],
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
_screen = 0;
|
||||
|
||||
get screen() {
|
||||
return this._screen;
|
||||
}
|
||||
|
||||
set screen(percent) {
|
||||
if (percent < 0) percent = 0;
|
||||
|
||||
if (percent > 1) percent = 1;
|
||||
|
||||
Utils.execAsync(`brightnessctl s ${percent * 100}% -q`)
|
||||
.then(() => {
|
||||
this._screen = percent;
|
||||
this.changed("screen");
|
||||
})
|
||||
.catch(console.error);
|
||||
}
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
try {
|
||||
this._screen =
|
||||
Number(Utils.exec("brightnessctl g")) /
|
||||
Number(Utils.exec("brightnessctl m"));
|
||||
} catch (error) {
|
||||
console.error("missing dependancy: brightnessctl");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const service = new Brightness();
|
||||
export default service;
|
52
services/brightness/__init__.py
Normal file
52
services/brightness/__init__.py
Normal file
@ -0,0 +1,52 @@
|
||||
from astal import read_file, write_file, gi
|
||||
|
||||
gi.require_version("GObject", "2.0")
|
||||
gi.require_version("GUdev", "1.0")
|
||||
from gi.repository import GObject, GUdev, GLib
|
||||
|
||||
class Brightness(GObject.Object):
|
||||
_brightness: float = 0
|
||||
|
||||
def __init__(self, device: str):
|
||||
super().__init__()
|
||||
|
||||
self._device_name = device
|
||||
self._udev_client = GUdev.Client.new(["backlight"])
|
||||
self._udev_client.connect('uevent', self.on_uevent)
|
||||
|
||||
def on_uevent(self, client, action, device):
|
||||
if device.get_name() != self._device_name:
|
||||
return
|
||||
|
||||
self._brightness = self._get_brightness()
|
||||
self.notify('brightness')
|
||||
|
||||
def _get_brightness(self):
|
||||
return self._get_current_brightness() / self._get_max_brightness()
|
||||
|
||||
def _get_current_brightness(self):
|
||||
return int(read_file(f"/sys/class/backlight/{self._device_name}/brightness"))
|
||||
|
||||
def _get_max_brightness(self):
|
||||
return int(read_file(f"/sys/class/backlight/{self._device_name}/max_brightness"))
|
||||
|
||||
@GObject.Property(type=float)
|
||||
def brightness(self):
|
||||
return self._brightness
|
||||
|
||||
@brightness.setter
|
||||
def brightness(self, value: float):
|
||||
if value < 0:
|
||||
value = 0
|
||||
|
||||
elif value > 1:
|
||||
value = 1
|
||||
|
||||
self._brightness = value
|
||||
GLib.file_set_contents_full(f"/sys/class/backlight/{self._device_name}/brightness", bytes(str(round(self._brightness * self._get_max_brightness())), 'utf-8'), GLib.FileSetContentsFlags.ONLY_EXISTING, 0o666)
|
||||
|
||||
def get_brightness(self):
|
||||
return self._brightness
|
||||
|
||||
def set_brightness(self, value):
|
||||
self.brightness = value
|
361
services/mpd.js
361
services/mpd.js
@ -1,361 +0,0 @@
|
||||
import Gio from "gi://Gio";
|
||||
|
||||
Gio._promisify(Gio.DataInputStream.prototype, "read_line_async");
|
||||
|
||||
class Mpd extends Service {
|
||||
static {
|
||||
Service.register(
|
||||
this,
|
||||
{},
|
||||
{
|
||||
//TODO: parse some properties like duration into number?
|
||||
partition: ["string", "r"],
|
||||
volume: ["string", "r"],
|
||||
repeat: ["string", "r"],
|
||||
random: ["string", "r"],
|
||||
single: ["string", "r"],
|
||||
consume: ["string", "r"],
|
||||
playlist: ["string", "r"],
|
||||
playlistlength: ["string", "r"],
|
||||
state: ["string", "r"],
|
||||
song: ["string", "r"],
|
||||
songid: ["string", "r"],
|
||||
nextsong: ["string", "r"],
|
||||
nextsongid: ["string", "r"],
|
||||
elapsed: ["string", "r"],
|
||||
duration: ["string", "r"],
|
||||
bitrate: ["string", "r"],
|
||||
mixrampdb: ["string", "r"],
|
||||
audio: ["string", "r"],
|
||||
|
||||
file: ["string", "r"],
|
||||
"Last-Modified": ["string", "r"],
|
||||
Artist: ["string", "r"],
|
||||
Title: ["string", "r"],
|
||||
Album: ["string", "r"],
|
||||
Pos: ["string", "r"],
|
||||
Id: ["string", "r"],
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
#socket;
|
||||
|
||||
#inputStream;
|
||||
#outputStream;
|
||||
|
||||
_decoder = new TextDecoder();
|
||||
_encoder = new TextEncoder();
|
||||
_messageHandlerQueue = [];
|
||||
|
||||
//TODO: more properties?
|
||||
|
||||
// Status
|
||||
_partition;
|
||||
_volume;
|
||||
_repeat;
|
||||
_random;
|
||||
_single;
|
||||
_consume;
|
||||
_playlist;
|
||||
_playlistlength;
|
||||
_state;
|
||||
_song;
|
||||
_songid;
|
||||
_nextsong;
|
||||
_nextsongid;
|
||||
_elapsed;
|
||||
_duration;
|
||||
_bitrate;
|
||||
_mixrampdb;
|
||||
_audio;
|
||||
|
||||
_file;
|
||||
_LastModified;
|
||||
_Artist;
|
||||
_Title;
|
||||
_Album;
|
||||
_Pos;
|
||||
_Id;
|
||||
|
||||
get partition() {
|
||||
return this._partition;
|
||||
}
|
||||
|
||||
get volume() {
|
||||
return this._volume;
|
||||
}
|
||||
|
||||
get repeat() {
|
||||
return this._repeat;
|
||||
}
|
||||
|
||||
get random() {
|
||||
return this._random;
|
||||
}
|
||||
|
||||
get single() {
|
||||
return this._single;
|
||||
}
|
||||
|
||||
get consume() {
|
||||
return this._consume;
|
||||
}
|
||||
|
||||
get playlist() {
|
||||
return this._playlist;
|
||||
}
|
||||
|
||||
get playlistlength() {
|
||||
return this._playlistlength;
|
||||
}
|
||||
|
||||
get state() {
|
||||
return this._state;
|
||||
}
|
||||
|
||||
get song() {
|
||||
return this._song;
|
||||
}
|
||||
|
||||
get songid() {
|
||||
return this._songid;
|
||||
}
|
||||
|
||||
get nextsong() {
|
||||
return this._nextsong;
|
||||
}
|
||||
|
||||
get nextsongid() {
|
||||
return this._nextsongid;
|
||||
}
|
||||
|
||||
get elapsed() {
|
||||
return this._elapsed;
|
||||
}
|
||||
|
||||
get duration() {
|
||||
return this._duration;
|
||||
}
|
||||
|
||||
get bitrate() {
|
||||
return this._bitrate;
|
||||
}
|
||||
|
||||
get mixrampdb() {
|
||||
return this._mixrampdb;
|
||||
}
|
||||
|
||||
get audio() {
|
||||
return this._audio;
|
||||
}
|
||||
|
||||
get file() {
|
||||
return this._file;
|
||||
}
|
||||
|
||||
get Last_Modified() {
|
||||
return this._LastModified;
|
||||
}
|
||||
|
||||
get Artist() {
|
||||
return this._Artist;
|
||||
}
|
||||
|
||||
get Title() {
|
||||
return this._Title;
|
||||
}
|
||||
|
||||
get Album() {
|
||||
return this._Album;
|
||||
}
|
||||
|
||||
get Pos() {
|
||||
return this._Pos;
|
||||
}
|
||||
|
||||
get Id() {
|
||||
return this._Id;
|
||||
}
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this._initSocket();
|
||||
}
|
||||
|
||||
async _initSocket() {
|
||||
try {
|
||||
this.#socket = new Gio.SocketClient().connect_to_host(
|
||||
"localhost",
|
||||
6600,
|
||||
null,
|
||||
);
|
||||
|
||||
this.#inputStream = new Gio.DataInputStream({
|
||||
base_stream: this.#socket.get_input_stream(),
|
||||
});
|
||||
|
||||
this.#outputStream = new Gio.DataOutputStream({
|
||||
base_stream: this.#socket.get_output_stream(),
|
||||
});
|
||||
|
||||
this._watchSocket();
|
||||
|
||||
//init properties
|
||||
//[TODO): init more properties?
|
||||
|
||||
this.send("status")
|
||||
.then(this._updateProperties.bind(this))
|
||||
.catch(logError);
|
||||
this.send("currentsong")
|
||||
.then(this._updateProperties.bind(this))
|
||||
.catch(logError);
|
||||
} catch (e) {
|
||||
logError(e);
|
||||
}
|
||||
}
|
||||
|
||||
async _watchSocket() {
|
||||
let bufferedLines = [];
|
||||
while (true) {
|
||||
const [rawData] = await this.#inputStream.read_line_async(0, null);
|
||||
const data = this._decoder.decode(rawData);
|
||||
if (data == null) continue;
|
||||
bufferedLines.push(data);
|
||||
const { response, remain } = this._parseResponse(bufferedLines);
|
||||
bufferedLines = remain;
|
||||
|
||||
if (!response) continue;
|
||||
switch (response.type) {
|
||||
case "version":
|
||||
console.log(`MPD Server Version ${response.payload}`);
|
||||
break;
|
||||
case "error":
|
||||
this._handleMessage(new Error(response.payload), null);
|
||||
break;
|
||||
case "data":
|
||||
this._handleMessage(null, response.payload);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_parseResponse(lines) {
|
||||
let response;
|
||||
let beginLine = 0;
|
||||
|
||||
for (let i = 0; i < lines.length; i++) {
|
||||
const line = lines[i];
|
||||
|
||||
const version = line.match(/^OK MPD (.+)/);
|
||||
const error = line.match(/^ACK \[.*] {.*} (.+)/);
|
||||
|
||||
if (version) {
|
||||
response = { type: "version", payload: version[1] };
|
||||
beginLine = i + 1;
|
||||
} else if (error) {
|
||||
response = { type: "error", payload: error[1] };
|
||||
beginLine = i + 1;
|
||||
} else if (line === "OK") {
|
||||
response = {
|
||||
type: "data",
|
||||
payload: lines.slice(beginLine, i).join("\n"),
|
||||
};
|
||||
beginLine = i + 1;
|
||||
}
|
||||
}
|
||||
return { response, remain: lines.slice(beginLine) };
|
||||
}
|
||||
|
||||
_handleMessage(err, msg) {
|
||||
const { func } = this._messageHandlerQueue.shift();
|
||||
func(err, msg);
|
||||
if (this._messageHandlerQueue.length === 0) {
|
||||
this._idle();
|
||||
}
|
||||
}
|
||||
|
||||
async send(data) {
|
||||
data = data.trim();
|
||||
const isIdle = data === "idle";
|
||||
|
||||
if (this._messageHandlerQueue[0]?.isIdle) {
|
||||
this.#outputStream.write(this._encoder.encode("noidle\n"), null);
|
||||
}
|
||||
this.#outputStream.write(this._encoder.encode(`${data}\n`), null);
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
this._messageHandlerQueue.push({
|
||||
isIdle,
|
||||
func: (err, msg) => {
|
||||
if (err != null) reject(err);
|
||||
resolve(msg);
|
||||
},
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
_idle() {
|
||||
this.send("idle")
|
||||
.then((msg) => {
|
||||
for (const line of msg.split("\n")) {
|
||||
const subsystem = /changed: (\w+)/.exec(line);
|
||||
if (subsystem == null) continue;
|
||||
|
||||
//TODO: only update those things that could have
|
||||
//changed using a switch over the subsystems
|
||||
this.send("status")
|
||||
.then(this._updateProperties.bind(this))
|
||||
.catch(logError);
|
||||
this.send("currentsong")
|
||||
.then(this._updateProperties.bind(this))
|
||||
.catch(logError);
|
||||
/*
|
||||
switch(subsystem[1]) {
|
||||
case "player":
|
||||
break;
|
||||
}*/
|
||||
}
|
||||
})
|
||||
.catch(logError);
|
||||
}
|
||||
|
||||
_updateProperties(msg) {
|
||||
for (const line of msg.split("\n")) {
|
||||
const keyValue = line.match(/(.*): (.*)/);
|
||||
if (keyValue == null) continue;
|
||||
const deprecatedKeys = [
|
||||
"time",
|
||||
"Time", //deprecated
|
||||
"Format", //same as audio
|
||||
];
|
||||
if (deprecatedKeys.includes(keyValue[1])) continue;
|
||||
if (!this.hasOwnProperty(`_${keyValue[1]}`)) continue;
|
||||
this.updateProperty(keyValue[1], keyValue[2]);
|
||||
this.emit("changed");
|
||||
}
|
||||
}
|
||||
|
||||
setCrossfade = (seconds) => this.send(`crossfade ${seconds}`);
|
||||
setVolume = (volume) => this.send(`setvol ${volume}`);
|
||||
|
||||
toggleShuffle = () => this.send(`random ${+this._random ? "0" : "1"}`);
|
||||
toggleRepeat = () => this.send(`repeat ${+this._repeat ? "0" : "1"}`);
|
||||
|
||||
next = () => this.send("next");
|
||||
playPause = () => this.send(`pause ${this._state === "pause" ? "0" : "1"}`);
|
||||
pause = () => this.send("pause 1");
|
||||
play = () => this.send("pause 0");
|
||||
playSong = (songpos) => this.send(`play ${songpos}`);
|
||||
playSongId = (songid) => this.send(`playid ${songid}`);
|
||||
seekSong = (songpos, time) => this.send(`seek ${songpos} ${time}`);
|
||||
seekSongId = (songid, time) => this.send(`seekid ${songid} ${time}`);
|
||||
seekCur = (time) => this.send(`seekcur ${time}`);
|
||||
previous = () => this.send("previous");
|
||||
stop = () => this.send("stop");
|
||||
|
||||
clearQueue = () => this.send("clear");
|
||||
}
|
||||
|
||||
const service = new Mpd;
|
||||
export default service;
|
@ -1,68 +0,0 @@
|
||||
#status-bar .Menu {
|
||||
background-color: $bg-alt-1;
|
||||
min-width: 640px;
|
||||
|
||||
.mpd-controls {
|
||||
background-color: $bg;
|
||||
border-radius: 10px;
|
||||
margin: 10px;
|
||||
|
||||
.button {
|
||||
font-size: 24px;
|
||||
padding: 10px;
|
||||
margin: 10px;
|
||||
background-color: $bg-alt-1;
|
||||
border-color: $bg-alt-1;
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
.cover {
|
||||
min-width: 250px;
|
||||
min-height: 250px;
|
||||
border-radius: 10px;
|
||||
margin: 10px;
|
||||
background: $bg;
|
||||
background-size: cover;
|
||||
}
|
||||
|
||||
.title {
|
||||
padding-top: 10px;
|
||||
font-size: 20px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.position {
|
||||
margin: 10px;
|
||||
margin-right: 20px;
|
||||
min-height: 10px;
|
||||
border-radius: 5px;
|
||||
|
||||
trough {
|
||||
background: $bg-alt-1;
|
||||
border-radius: 5px;
|
||||
min-height: 10px;
|
||||
}
|
||||
|
||||
slider {
|
||||
min-height: 10px;
|
||||
border-radius: 5px;
|
||||
background: $mpd-progress-primary;
|
||||
}
|
||||
}
|
||||
|
||||
.position-label {
|
||||
margin-top: 75px;
|
||||
}
|
||||
}
|
||||
|
||||
.dial-parent {
|
||||
@include panel-dial($resource-dial-fg-cpu, $bg);
|
||||
border-radius: 10px;
|
||||
|
||||
|
||||
.dial-icon {
|
||||
color: $fg;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,75 +0,0 @@
|
||||
* :not(selection) :not(tooltip) {
|
||||
all: unset;
|
||||
}
|
||||
|
||||
#status-bar {
|
||||
background-color: $bg;
|
||||
|
||||
border-radius: 0 10px 10px 0;
|
||||
|
||||
.dial-container {
|
||||
margin-top: 10px;
|
||||
|
||||
.battery-dial {
|
||||
@include bar-dial($battery-dial-bg, $battery-dial-fg)
|
||||
}
|
||||
|
||||
.volume-dial {
|
||||
@include bar-dial($volume-dial-bg, $volume-dial-fg)
|
||||
}
|
||||
|
||||
.brightness-dial {
|
||||
@include bar-dial($brightness-dial-bg, $brightness-dial-fg)
|
||||
}
|
||||
}
|
||||
|
||||
.workspace-container {
|
||||
background-color: $bg-alt-1;
|
||||
border-radius: 10px;
|
||||
padding: 6px 0px;
|
||||
margin: 0px 12px;
|
||||
border: none;
|
||||
|
||||
.ws-norm {
|
||||
min-width: 15px;
|
||||
border-radius: 10px;
|
||||
margin: 2px 5px;
|
||||
padding: 6px 10px;
|
||||
background-color: $ws-inactive;
|
||||
color: $fg;
|
||||
}
|
||||
|
||||
.ws-active {
|
||||
color: $bg;
|
||||
background-color: $ws-active;
|
||||
}
|
||||
}
|
||||
|
||||
.menu-visibility-button {
|
||||
border-radius: 0px 10px 10px 0px;
|
||||
padding: 10px 0px 10px 2px;
|
||||
background-color: $bg-alt-1
|
||||
}
|
||||
|
||||
.clock {
|
||||
background-color: $bg-alt-1;
|
||||
margin: 8px;
|
||||
margin-bottom: 10px;
|
||||
padding: 6px 2px;
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
.mpd-controls {
|
||||
.button {
|
||||
font-size: 14px;
|
||||
margin: 2px;
|
||||
margin-bottom: 8px;
|
||||
font-family: 'Symbols Nerd Font Mono';
|
||||
|
||||
&:hover {
|
||||
color: $button-hover-color;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,43 +0,0 @@
|
||||
.launcher {
|
||||
min-width: 640px;
|
||||
background: $bg-alt-1;
|
||||
|
||||
.search {
|
||||
> image {
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
> entry {
|
||||
padding: 5px;
|
||||
margin: 10px;
|
||||
background: $bg;
|
||||
border: 5px solid $bg;
|
||||
border-radius: 10px;
|
||||
font-size: 32px;
|
||||
}
|
||||
}
|
||||
|
||||
.entry {
|
||||
margin: 0 10px 0 10px;
|
||||
border: 10px solid $bg;
|
||||
border-radius: 10px;
|
||||
background: $bg;
|
||||
|
||||
> box {
|
||||
> image {
|
||||
padding: 5px;
|
||||
margin: 5px 10px 5px 10px;
|
||||
background: $bg-alt-1;
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
> label {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
> scrolledwindow > * > box:last-child {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
}
|
@ -1,28 +0,0 @@
|
||||
.lock-ready > box{
|
||||
box {
|
||||
background: $bg;
|
||||
padding: 20px;
|
||||
border: 10px solid $hl-alt-1;
|
||||
min-width: 300px;
|
||||
margin: 15px;
|
||||
}
|
||||
|
||||
.img {
|
||||
background: url('/home/catalie/.config/wallpaper');
|
||||
min-width: 300px;
|
||||
min-height: 300px;
|
||||
background-position: 25% 12.5%;
|
||||
background-size: cover;
|
||||
}
|
||||
|
||||
box > entry {
|
||||
background: $bg-alt-1;
|
||||
font-size: 14px;
|
||||
padding: 5px;
|
||||
margin: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
.lock {
|
||||
background: transparent;
|
||||
}
|
@ -1,98 +0,0 @@
|
||||
#notifications {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.notifications {
|
||||
opacity: 1;
|
||||
min-width: 24rem;
|
||||
.revealer {
|
||||
+ .revealer {
|
||||
>*>box {
|
||||
margin-top: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.notification {
|
||||
margin-bottom: 25px;
|
||||
min-width: 24rem;
|
||||
padding: 0px;
|
||||
border: 1px solid $fg;
|
||||
border-right: none;
|
||||
background: $bg;
|
||||
}
|
||||
|
||||
.urgency-indicator {
|
||||
min-width: 15px;
|
||||
margin-bottom: 25px;
|
||||
|
||||
&.normal, &.low {
|
||||
background: $hl-alt-2;
|
||||
border: 1px solid $hl-alt-2;
|
||||
}
|
||||
|
||||
&.urgent {
|
||||
background: $hl;
|
||||
border: 1px solid $hl
|
||||
}
|
||||
}
|
||||
|
||||
.body {
|
||||
margin-right: 1em;
|
||||
}
|
||||
|
||||
.timeout-bar {
|
||||
background: $bg-alt-1;
|
||||
|
||||
> trough > progress {
|
||||
background-image: none;
|
||||
background-color: $hl-alt-1;
|
||||
}
|
||||
}
|
||||
|
||||
.button {
|
||||
font-size: 20px;
|
||||
margin-left: 5px;
|
||||
margin-right: 5px;
|
||||
|
||||
&:hover {
|
||||
color: $hl;
|
||||
}
|
||||
}
|
||||
|
||||
.icon {
|
||||
min-width: 68px;
|
||||
min-height: 68px;
|
||||
margin: 5px;
|
||||
}
|
||||
|
||||
.icon image {
|
||||
font-size: 58px;
|
||||
margin: 5px;
|
||||
color: $fg;
|
||||
}
|
||||
|
||||
.icon box {
|
||||
min-width: 68px;
|
||||
min-height: 68px;
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: 28px;
|
||||
padding-right: 5px;
|
||||
}
|
||||
|
||||
.actions .action-button {
|
||||
margin: 0 .4em;
|
||||
margin-top: .8em;
|
||||
border: 2px solid $fg;
|
||||
}
|
||||
|
||||
.actions .action-button:first-child {
|
||||
margin-left: .5em;
|
||||
}
|
||||
|
||||
.actions .action-button:last-child {
|
||||
margin-right: .5em;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,126 +0,0 @@
|
||||
$height: 50px;
|
||||
|
||||
#status-bar {
|
||||
background: transparent;
|
||||
|
||||
.workspaces {
|
||||
min-height: $height;
|
||||
background: $bg;
|
||||
padding: 5px;
|
||||
padding-top: 15px;
|
||||
|
||||
.workspace {
|
||||
min-width: 10px;
|
||||
min-height: 10px;
|
||||
margin: 5px;
|
||||
background-color: $bg-alt-1;
|
||||
}
|
||||
|
||||
.occupied {
|
||||
background-color: $bg-alt-2;
|
||||
}
|
||||
|
||||
.focused {
|
||||
background-color: $hl;
|
||||
}
|
||||
}
|
||||
|
||||
.media-controls {
|
||||
margin-top: 20px;
|
||||
min-width: 220px;
|
||||
|
||||
.button {
|
||||
font-size: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
.progress-bar {
|
||||
margin-top: 20px;
|
||||
min-width: 200px;
|
||||
min-height: 2px;
|
||||
background: transparent;
|
||||
|
||||
trough {
|
||||
min-height: 2px;
|
||||
background: $bg-alt-1;
|
||||
}
|
||||
|
||||
highlight {
|
||||
min-height: 2px;
|
||||
background: $mpd-progress-primary;
|
||||
}
|
||||
}
|
||||
|
||||
.right {
|
||||
min-height: $height;
|
||||
background: $bg;
|
||||
|
||||
|
||||
.battery-container {
|
||||
padding-left: 10px;
|
||||
padding-right: 10px;
|
||||
.battery-dial {
|
||||
@include bar-dial($battery-dial-bg, $battery-dial-fg);
|
||||
}
|
||||
}
|
||||
|
||||
.sliderbox {
|
||||
margin: 5px;
|
||||
.volume {
|
||||
.slider {
|
||||
trough {
|
||||
min-height: 10px;
|
||||
min-width: 120px;
|
||||
background: $bg-alt-1;
|
||||
}
|
||||
|
||||
highlight {
|
||||
background: $hl-alt-1;
|
||||
}
|
||||
}
|
||||
|
||||
button {
|
||||
font-size: 20px;
|
||||
margin: 5px;
|
||||
}
|
||||
}
|
||||
|
||||
.brightness {
|
||||
.slider {
|
||||
trough {
|
||||
min-height: 10px;
|
||||
min-width: 120px;
|
||||
background: $bg-alt-1;
|
||||
}
|
||||
|
||||
highlight {
|
||||
background: $hl-alt-1;
|
||||
}
|
||||
}
|
||||
|
||||
button {
|
||||
font-size: 20px;
|
||||
margin: 5px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
separator {
|
||||
background: $bg-alt-1;
|
||||
padding: 1px;
|
||||
margin-top: 10px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.clock {
|
||||
background: $bg;
|
||||
|
||||
.datetime {
|
||||
margin: 1px;
|
||||
font-size: 10pt;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
212
style/css
212
style/css
@ -1,212 +0,0 @@
|
||||
* :not(selection) :not(tooltip) {
|
||||
all: unset; }
|
||||
|
||||
#status-bar {
|
||||
background-color: #161616;
|
||||
border-radius: 0 10px 10px 0; }
|
||||
#status-bar .dial-container {
|
||||
margin-top: 10px; }
|
||||
#status-bar .dial-container .battery-dial {
|
||||
color: #33B1FF;
|
||||
background-color: #161616;
|
||||
margin: 0px;
|
||||
padding: 0px;
|
||||
font-size: 5px; }
|
||||
#status-bar .dial-container .battery-dial .dial-icon {
|
||||
font-family: 'Symbols Nerd Font Mono';
|
||||
font-size: 16px;
|
||||
color: #f2f4f8; }
|
||||
#status-bar .dial-container .volume-dial {
|
||||
color: #33B1FF;
|
||||
background-color: #161616;
|
||||
margin: 0px;
|
||||
padding: 0px;
|
||||
font-size: 5px; }
|
||||
#status-bar .dial-container .volume-dial .dial-icon {
|
||||
font-family: 'Symbols Nerd Font Mono';
|
||||
font-size: 16px;
|
||||
color: #f2f4f8; }
|
||||
#status-bar .dial-container .brightness-dial {
|
||||
color: #33B1FF;
|
||||
background-color: #161616;
|
||||
margin: 0px;
|
||||
padding: 0px;
|
||||
font-size: 5px; }
|
||||
#status-bar .dial-container .brightness-dial .dial-icon {
|
||||
font-family: 'Symbols Nerd Font Mono';
|
||||
font-size: 16px;
|
||||
color: #f2f4f8; }
|
||||
#status-bar .workspace-container {
|
||||
background-color: #262626;
|
||||
border-radius: 10px;
|
||||
padding: 6px 0px;
|
||||
margin: 0px 12px;
|
||||
border: none; }
|
||||
#status-bar .workspace-container .ws-norm {
|
||||
min-width: 15px;
|
||||
border-radius: 10px;
|
||||
margin: 2px 5px;
|
||||
padding: 6px 10px;
|
||||
background-color: #161616;
|
||||
color: #f2f4f8; }
|
||||
#status-bar .workspace-container .ws-active {
|
||||
color: #161616;
|
||||
background-color: #FF7EB6; }
|
||||
#status-bar .menu-visibility-button {
|
||||
border-radius: 0px 10px 10px 0px;
|
||||
padding: 10px 0px 10px 2px;
|
||||
background-color: #262626; }
|
||||
#status-bar .clock {
|
||||
background-color: #262626;
|
||||
margin: 8px;
|
||||
margin-bottom: 10px;
|
||||
padding: 6px 2px;
|
||||
border-radius: 10px; }
|
||||
#status-bar .mpd-controls .button {
|
||||
font-size: 14px;
|
||||
margin: 2px;
|
||||
margin-bottom: 8px;
|
||||
font-family: 'Symbols Nerd Font Mono'; }
|
||||
#status-bar .mpd-controls .button:hover {
|
||||
color: #FF7EB6; }
|
||||
|
||||
#status-bar .Menu {
|
||||
background-color: #262626;
|
||||
min-width: 640px; }
|
||||
#status-bar .Menu .mpd-controls {
|
||||
background-color: #161616;
|
||||
border-radius: 10px;
|
||||
margin: 10px; }
|
||||
#status-bar .Menu .mpd-controls .button {
|
||||
font-size: 24px;
|
||||
padding: 10px;
|
||||
margin: 10px;
|
||||
background-color: #262626;
|
||||
border-color: #262626;
|
||||
border-radius: 10px; }
|
||||
#status-bar .Menu .mpd-controls .cover {
|
||||
min-width: 250px;
|
||||
min-height: 250px;
|
||||
border-radius: 10px;
|
||||
margin: 10px;
|
||||
background: #161616;
|
||||
background-size: cover; }
|
||||
#status-bar .Menu .mpd-controls .title {
|
||||
padding-top: 10px;
|
||||
font-size: 20px;
|
||||
font-weight: bold; }
|
||||
#status-bar .Menu .mpd-controls .position {
|
||||
margin: 10px;
|
||||
margin-right: 20px;
|
||||
min-height: 10px;
|
||||
border-radius: 5px; }
|
||||
#status-bar .Menu .mpd-controls .position trough {
|
||||
background: #262626;
|
||||
border-radius: 5px;
|
||||
min-height: 10px; }
|
||||
#status-bar .Menu .mpd-controls .position highlight {
|
||||
min-height: 10px;
|
||||
border-radius: 5px;
|
||||
background: #FF7Eb6; }
|
||||
#status-bar .Menu .mpd-controls .position-label {
|
||||
margin-top: 75px; }
|
||||
#status-bar .Menu .dial-parent {
|
||||
color: #FF7eb6;
|
||||
background-color: #161616;
|
||||
margin: 0px;
|
||||
padding: 10px;
|
||||
font-size: 8px;
|
||||
margin-left: 10px;
|
||||
border-radius: 10px;
|
||||
border-radius: 10px; }
|
||||
#status-bar .Menu .dial-parent .dial-icon {
|
||||
font-family: 'Symbols Nerd Font Mono';
|
||||
font-size: 32px;
|
||||
color: #f2f4f8; }
|
||||
#status-bar .Menu .dial-parent .resource-dial {
|
||||
min-width: 96px;
|
||||
min-height: 96px; }
|
||||
#status-bar .Menu .dial-parent .dial-icon {
|
||||
color: #f2f4f8; }
|
||||
|
||||
#notifications {
|
||||
background: transparent; }
|
||||
|
||||
.notifications {
|
||||
opacity: 1;
|
||||
min-width: 24rem; }
|
||||
.notifications .revealer + .revealer > * > box {
|
||||
margin-top: 0; }
|
||||
.notifications .revealer .notification {
|
||||
margin: 25px;
|
||||
min-width: 24rem;
|
||||
padding: 0px;
|
||||
border: 2px solid #f2f4f8;
|
||||
background: #161616; }
|
||||
.notifications .revealer .notification.critical {
|
||||
border: 2px solid #FF7EB6; }
|
||||
.notifications .revealer .body {
|
||||
margin-right: 1em; }
|
||||
.notifications .revealer .timeout-bar {
|
||||
margin: 5px 0px 0;
|
||||
margin-top: 5px;
|
||||
background: #262626; }
|
||||
.notifications .revealer .timeout-bar > trough > progress {
|
||||
background-image: none;
|
||||
background-color: #33B1FF; }
|
||||
.notifications .revealer .button {
|
||||
font-size: 20px;
|
||||
margin-left: 5px;
|
||||
margin-right: 5px; }
|
||||
.notifications .revealer .button:hover {
|
||||
color: #FF7EB6; }
|
||||
.notifications .revealer .icon {
|
||||
min-width: 68px;
|
||||
min-height: 68px;
|
||||
margin-right: 1em;
|
||||
margin-left: 1em; }
|
||||
.notifications .revealer .icon image {
|
||||
font-size: 58px;
|
||||
margin: 5px;
|
||||
color: #f2f4f8; }
|
||||
.notifications .revealer .icon box {
|
||||
min-width: 68px;
|
||||
min-height: 68px; }
|
||||
.notifications .revealer .title {
|
||||
font-size: 24px; }
|
||||
.notifications .revealer .actions .action-button {
|
||||
margin: 0 .4em;
|
||||
margin-top: .8em;
|
||||
border: 2px solid #f2f4f8; }
|
||||
.notifications .revealer .actions .action-button:first-child {
|
||||
margin-left: .5em; }
|
||||
.notifications .revealer .actions .action-button:last-child {
|
||||
margin-right: .5em; }
|
||||
|
||||
.launcher {
|
||||
min-width: 640px;
|
||||
background: #262626; }
|
||||
.launcher .search > image {
|
||||
margin-left: 10px; }
|
||||
.launcher .search > entry {
|
||||
padding: 5px;
|
||||
margin: 10px;
|
||||
background: #161616;
|
||||
border: 5px solid #161616;
|
||||
border-radius: 10px;
|
||||
font-size: 32px; }
|
||||
.launcher .entry {
|
||||
margin: 0 10px 0 10px;
|
||||
border: 10px solid #161616;
|
||||
border-radius: 10px;
|
||||
background: #161616; }
|
||||
.launcher .entry > box > image {
|
||||
padding: 5px;
|
||||
margin: 5px 10px 5px 10px;
|
||||
background: #262626;
|
||||
border-radius: 5px; }
|
||||
.launcher > scrolledwindow > * > box:last-child {
|
||||
margin-bottom: 10px; }
|
||||
|
||||
* :not(selection) :not(tooltip) {
|
||||
all: unset; }
|
@ -5,7 +5,7 @@
|
||||
padding: $padding;
|
||||
font-size: $font-size;
|
||||
|
||||
.dial-icon {
|
||||
label, icon {
|
||||
font-family: 'Symbols Nerd Font Mono';
|
||||
font-size: $icon-font-size;
|
||||
color: $icon-color;
|
||||
|
320
style/style.css
320
style/style.css
@ -1,320 +0,0 @@
|
||||
.lock-ready > box box {
|
||||
background: #161616;
|
||||
padding: 20px;
|
||||
border: 10px solid #33B1FF;
|
||||
min-width: 300px;
|
||||
margin: 15px; }
|
||||
|
||||
.lock-ready > box .img {
|
||||
background: url("/home/catalie/.config/wallpaper");
|
||||
min-width: 300px;
|
||||
min-height: 300px;
|
||||
background-position: 25% 12.5%;
|
||||
background-size: cover; }
|
||||
|
||||
.lock-ready > box box > entry {
|
||||
background: #262626;
|
||||
font-size: 14px;
|
||||
padding: 5px;
|
||||
margin: 10px; }
|
||||
|
||||
.lock {
|
||||
background: transparent; }
|
||||
|
||||
* :not(selection) :not(tooltip) {
|
||||
all: unset; }
|
||||
|
||||
#status-bar {
|
||||
background-color: #161616;
|
||||
border-radius: 0 10px 10px 0; }
|
||||
#status-bar .dial-container {
|
||||
margin-top: 10px; }
|
||||
#status-bar .dial-container .battery-dial {
|
||||
color: #42be65;
|
||||
background-color: #161616;
|
||||
margin: 0px;
|
||||
padding: 0px;
|
||||
font-size: 5px; }
|
||||
#status-bar .dial-container .battery-dial .dial-icon {
|
||||
font-family: 'Symbols Nerd Font Mono';
|
||||
font-size: 16px;
|
||||
color: #f2f4f8; }
|
||||
#status-bar .dial-container .volume-dial {
|
||||
color: #33B1FF;
|
||||
background-color: #161616;
|
||||
margin: 0px;
|
||||
padding: 0px;
|
||||
font-size: 5px; }
|
||||
#status-bar .dial-container .volume-dial .dial-icon {
|
||||
font-family: 'Symbols Nerd Font Mono';
|
||||
font-size: 16px;
|
||||
color: #f2f4f8; }
|
||||
#status-bar .dial-container .brightness-dial {
|
||||
color: #33B1FF;
|
||||
background-color: #161616;
|
||||
margin: 0px;
|
||||
padding: 0px;
|
||||
font-size: 5px; }
|
||||
#status-bar .dial-container .brightness-dial .dial-icon {
|
||||
font-family: 'Symbols Nerd Font Mono';
|
||||
font-size: 16px;
|
||||
color: #f2f4f8; }
|
||||
#status-bar .workspace-container {
|
||||
background-color: #262626;
|
||||
border-radius: 10px;
|
||||
padding: 6px 0px;
|
||||
margin: 0px 12px;
|
||||
border: none; }
|
||||
#status-bar .workspace-container .ws-norm {
|
||||
min-width: 15px;
|
||||
border-radius: 10px;
|
||||
margin: 2px 5px;
|
||||
padding: 6px 10px;
|
||||
background-color: #161616;
|
||||
color: #f2f4f8; }
|
||||
#status-bar .workspace-container .ws-active {
|
||||
color: #161616;
|
||||
background-color: #FF7EB6; }
|
||||
#status-bar .menu-visibility-button {
|
||||
border-radius: 0px 10px 10px 0px;
|
||||
padding: 10px 0px 10px 2px;
|
||||
background-color: #262626; }
|
||||
#status-bar .clock {
|
||||
background-color: #262626;
|
||||
margin: 8px;
|
||||
margin-bottom: 10px;
|
||||
padding: 6px 2px;
|
||||
border-radius: 10px; }
|
||||
#status-bar .mpd-controls .button {
|
||||
font-size: 14px;
|
||||
margin: 2px;
|
||||
margin-bottom: 8px;
|
||||
font-family: 'Symbols Nerd Font Mono'; }
|
||||
#status-bar .mpd-controls .button:hover {
|
||||
color: #FF7EB6; }
|
||||
|
||||
#status-bar {
|
||||
background: transparent; }
|
||||
#status-bar .workspaces {
|
||||
min-height: 50px;
|
||||
background: #161616;
|
||||
padding: 5px;
|
||||
padding-top: 15px; }
|
||||
#status-bar .workspaces .workspace {
|
||||
min-width: 10px;
|
||||
min-height: 10px;
|
||||
margin: 5px;
|
||||
background-color: #262626; }
|
||||
#status-bar .workspaces .occupied {
|
||||
background-color: #393939; }
|
||||
#status-bar .workspaces .focused {
|
||||
background-color: #FF7EB6; }
|
||||
#status-bar .media-controls {
|
||||
margin-top: 20px;
|
||||
min-width: 220px; }
|
||||
#status-bar .media-controls .button {
|
||||
font-size: 20px; }
|
||||
#status-bar .progress-bar {
|
||||
margin-top: 20px;
|
||||
min-width: 200px;
|
||||
min-height: 2px;
|
||||
background: transparent; }
|
||||
#status-bar .progress-bar trough {
|
||||
min-height: 2px;
|
||||
background: #262626; }
|
||||
#status-bar .progress-bar highlight {
|
||||
min-height: 2px;
|
||||
background: #FF7Eb6; }
|
||||
#status-bar .right {
|
||||
min-height: 50px;
|
||||
background: #161616; }
|
||||
#status-bar .right .battery-container {
|
||||
padding-left: 10px;
|
||||
padding-right: 10px; }
|
||||
#status-bar .right .battery-container .battery-dial {
|
||||
color: #42be65;
|
||||
background-color: #161616;
|
||||
margin: 0px;
|
||||
padding: 0px;
|
||||
font-size: 5px; }
|
||||
#status-bar .right .battery-container .battery-dial .dial-icon {
|
||||
font-family: 'Symbols Nerd Font Mono';
|
||||
font-size: 16px;
|
||||
color: #f2f4f8; }
|
||||
#status-bar .right .sliderbox {
|
||||
margin: 5px; }
|
||||
#status-bar .right .sliderbox .volume .slider trough {
|
||||
min-height: 10px;
|
||||
min-width: 120px;
|
||||
background: #262626; }
|
||||
#status-bar .right .sliderbox .volume .slider highlight {
|
||||
background: #33B1FF; }
|
||||
#status-bar .right .sliderbox .volume button {
|
||||
font-size: 20px;
|
||||
margin: 5px; }
|
||||
#status-bar .right .sliderbox .brightness .slider trough {
|
||||
min-height: 10px;
|
||||
min-width: 120px;
|
||||
background: #262626; }
|
||||
#status-bar .right .sliderbox .brightness .slider highlight {
|
||||
background: #33B1FF; }
|
||||
#status-bar .right .sliderbox .brightness button {
|
||||
font-size: 20px;
|
||||
margin: 5px; }
|
||||
#status-bar .right separator {
|
||||
background: #262626;
|
||||
padding: 1px;
|
||||
margin-top: 10px;
|
||||
margin-bottom: 10px; }
|
||||
#status-bar .right .clock {
|
||||
background: #161616; }
|
||||
#status-bar .right .clock .datetime {
|
||||
margin: 1px;
|
||||
font-size: 10pt; }
|
||||
|
||||
#status-bar .Menu {
|
||||
background-color: #262626;
|
||||
min-width: 640px; }
|
||||
#status-bar .Menu .mpd-controls {
|
||||
background-color: #161616;
|
||||
border-radius: 10px;
|
||||
margin: 10px; }
|
||||
#status-bar .Menu .mpd-controls .button {
|
||||
font-size: 24px;
|
||||
padding: 10px;
|
||||
margin: 10px;
|
||||
background-color: #262626;
|
||||
border-color: #262626;
|
||||
border-radius: 10px; }
|
||||
#status-bar .Menu .mpd-controls .cover {
|
||||
min-width: 250px;
|
||||
min-height: 250px;
|
||||
border-radius: 10px;
|
||||
margin: 10px;
|
||||
background: #161616;
|
||||
background-size: cover; }
|
||||
#status-bar .Menu .mpd-controls .title {
|
||||
padding-top: 10px;
|
||||
font-size: 20px;
|
||||
font-weight: bold; }
|
||||
#status-bar .Menu .mpd-controls .position {
|
||||
margin: 10px;
|
||||
margin-right: 20px;
|
||||
min-height: 10px;
|
||||
border-radius: 5px; }
|
||||
#status-bar .Menu .mpd-controls .position trough {
|
||||
background: #262626;
|
||||
border-radius: 5px;
|
||||
min-height: 10px; }
|
||||
#status-bar .Menu .mpd-controls .position slider {
|
||||
min-height: 10px;
|
||||
border-radius: 5px;
|
||||
background: #FF7Eb6; }
|
||||
#status-bar .Menu .mpd-controls .position-label {
|
||||
margin-top: 75px; }
|
||||
#status-bar .Menu .dial-parent {
|
||||
color: #FF7eb6;
|
||||
background-color: #161616;
|
||||
margin: 0px;
|
||||
padding: 10px;
|
||||
font-size: 8px;
|
||||
margin-left: 10px;
|
||||
border-radius: 10px;
|
||||
border-radius: 10px; }
|
||||
#status-bar .Menu .dial-parent .dial-icon {
|
||||
font-family: 'Symbols Nerd Font Mono';
|
||||
font-size: 32px;
|
||||
color: #f2f4f8; }
|
||||
#status-bar .Menu .dial-parent .resource-dial {
|
||||
min-width: 96px;
|
||||
min-height: 96px; }
|
||||
#status-bar .Menu .dial-parent .dial-icon {
|
||||
color: #f2f4f8; }
|
||||
|
||||
#notifications {
|
||||
background: transparent; }
|
||||
|
||||
.notifications {
|
||||
opacity: 1;
|
||||
min-width: 24rem; }
|
||||
.notifications .revealer + .revealer > * > box {
|
||||
margin-top: 0; }
|
||||
.notifications .revealer .notification {
|
||||
margin-bottom: 25px;
|
||||
min-width: 24rem;
|
||||
padding: 0px;
|
||||
border: 1px solid #f2f4f8;
|
||||
border-right: none;
|
||||
background: #161616; }
|
||||
.notifications .revealer .urgency-indicator {
|
||||
min-width: 15px;
|
||||
margin-bottom: 25px; }
|
||||
.notifications .revealer .urgency-indicator.normal, .notifications .revealer .urgency-indicator.low {
|
||||
background: #42be65;
|
||||
border: 1px solid #42be65; }
|
||||
.notifications .revealer .urgency-indicator.urgent {
|
||||
background: #FF7EB6;
|
||||
border: 1px solid #FF7EB6; }
|
||||
.notifications .revealer .body {
|
||||
margin-right: 1em; }
|
||||
.notifications .revealer .timeout-bar {
|
||||
background: #262626; }
|
||||
.notifications .revealer .timeout-bar > trough > progress {
|
||||
background-image: none;
|
||||
background-color: #33B1FF; }
|
||||
.notifications .revealer .button {
|
||||
font-size: 20px;
|
||||
margin-left: 5px;
|
||||
margin-right: 5px; }
|
||||
.notifications .revealer .button:hover {
|
||||
color: #FF7EB6; }
|
||||
.notifications .revealer .icon {
|
||||
min-width: 68px;
|
||||
min-height: 68px;
|
||||
margin: 5px; }
|
||||
.notifications .revealer .icon image {
|
||||
font-size: 58px;
|
||||
margin: 5px;
|
||||
color: #f2f4f8; }
|
||||
.notifications .revealer .icon box {
|
||||
min-width: 68px;
|
||||
min-height: 68px; }
|
||||
.notifications .revealer .title {
|
||||
font-size: 28px;
|
||||
padding-right: 5px; }
|
||||
.notifications .revealer .actions .action-button {
|
||||
margin: 0 .4em;
|
||||
margin-top: .8em;
|
||||
border: 2px solid #f2f4f8; }
|
||||
.notifications .revealer .actions .action-button:first-child {
|
||||
margin-left: .5em; }
|
||||
.notifications .revealer .actions .action-button:last-child {
|
||||
margin-right: .5em; }
|
||||
|
||||
.launcher {
|
||||
min-width: 640px;
|
||||
background: #262626; }
|
||||
.launcher .search > image {
|
||||
margin-left: 10px; }
|
||||
.launcher .search > entry {
|
||||
padding: 5px;
|
||||
margin: 10px;
|
||||
background: #161616;
|
||||
border: 5px solid #161616;
|
||||
border-radius: 10px;
|
||||
font-size: 32px; }
|
||||
.launcher .entry {
|
||||
margin: 0 10px 0 10px;
|
||||
border: 10px solid #161616;
|
||||
border-radius: 10px;
|
||||
background: #161616; }
|
||||
.launcher .entry > box > image {
|
||||
padding: 5px;
|
||||
margin: 5px 10px 5px 10px;
|
||||
background: #262626;
|
||||
border-radius: 5px; }
|
||||
.launcher > scrolledwindow > * > box:last-child {
|
||||
margin-bottom: 10px; }
|
||||
|
||||
* :not(selection) :not(tooltip) {
|
||||
all: unset; }
|
@ -1,14 +1,9 @@
|
||||
* {
|
||||
all: unset;
|
||||
}
|
||||
|
||||
@import 'colors.scss';
|
||||
@import 'mixins.scss';
|
||||
|
||||
// components
|
||||
@import './components/lock.scss';
|
||||
@import './components/bar.scss';
|
||||
@import './components/top-bar.scss';
|
||||
@import './components/bar-control-center.scss';
|
||||
@import './components/notification-popups.scss';
|
||||
@import './components/launcher.scss';
|
||||
|
||||
* :not(selection) :not(tooltip) {
|
||||
all: unset;
|
||||
}
|
||||
@import './widgets/bar.scss';
|
||||
@import './widgets/notifications.scss';
|
||||
|
141
style/widgets/bar.scss
Normal file
141
style/widgets/bar.scss
Normal file
@ -0,0 +1,141 @@
|
||||
$height: 50px;
|
||||
|
||||
bar {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
centerbox {
|
||||
> box.left {
|
||||
box.workspaces {
|
||||
min-height: $height;
|
||||
background: $bg;
|
||||
padding: 5px;
|
||||
padding-top: 15px;
|
||||
|
||||
box > button {
|
||||
min-width: 10px;
|
||||
min-height: 10px;
|
||||
margin: 5px;
|
||||
background-color: $bg-alt-1;
|
||||
|
||||
&.occupied {
|
||||
background-color: $bg-alt-2;
|
||||
}
|
||||
|
||||
&.focused {
|
||||
background-color: $hl;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
stack > box.player {
|
||||
min-width: 250px;
|
||||
min-height: 60px;
|
||||
background-position: 50% 50%;
|
||||
background-size: cover;
|
||||
border: 4px solid #161616;
|
||||
|
||||
> box {
|
||||
> button {
|
||||
margin: 3px 10px;
|
||||
}
|
||||
}
|
||||
|
||||
> .media-progress {
|
||||
min-width: 242px;
|
||||
min-height: 2px;
|
||||
background: transparent;
|
||||
|
||||
trough {
|
||||
min-height: 2px;
|
||||
background: $bg-alt-1;
|
||||
}
|
||||
|
||||
highlight {
|
||||
min-height: 2px;
|
||||
background: $mpd-progress-primary;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
> box.right {
|
||||
min-height: $height;
|
||||
background: $bg;
|
||||
|
||||
box.sliders {
|
||||
margin: 5px;
|
||||
box.volume-slider {
|
||||
> button {
|
||||
font-size: 24px;
|
||||
margin: 5px;
|
||||
}
|
||||
|
||||
> .volume-slider { /* selecting this as `slider` doesn't work, for some reason.*/
|
||||
min-width: 120px;
|
||||
min-height: 10px;
|
||||
|
||||
& trough {
|
||||
min-height: 10px;
|
||||
min-width: 120px;
|
||||
background: $bg-alt-1;
|
||||
}
|
||||
|
||||
& highlight {
|
||||
background: $hl-alt-1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
box.brightness-slider {
|
||||
> button {
|
||||
font-size: 20px;
|
||||
margin: 5px;
|
||||
}
|
||||
|
||||
> .brightness-slider { /* selecting this as `slider` doesn't work, for some reason.*/
|
||||
min-width: 120px;
|
||||
min-height: 10px;
|
||||
|
||||
& trough {
|
||||
min-height: 10px;
|
||||
min-width: 120px;
|
||||
background: $bg-alt-1;
|
||||
}
|
||||
|
||||
& highlight {
|
||||
background: $hl-alt-1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.battery-container {
|
||||
padding-left: 10px;
|
||||
padding-right: 10px;
|
||||
.battery-dial {
|
||||
min-width: 40px;
|
||||
min-height: 40px;
|
||||
@include bar-dial($battery-dial-bg, $battery-dial-fg);
|
||||
}
|
||||
}
|
||||
|
||||
separator {
|
||||
background: $bg-alt-1;
|
||||
padding: 1px;
|
||||
margin-top: 10px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
box.clock {
|
||||
margin: 10px;
|
||||
background: $bg;
|
||||
|
||||
.datetime {
|
||||
margin: 1px;
|
||||
font-size: 10pt;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
44
style/widgets/notifications.scss
Normal file
44
style/widgets/notifications.scss
Normal file
@ -0,0 +1,44 @@
|
||||
.notifications {
|
||||
> * {
|
||||
> button > box {
|
||||
margin: 10px 15px;
|
||||
border: 1px solid $fg;
|
||||
background: $bg;
|
||||
|
||||
.title {
|
||||
font-family: 'tewi';
|
||||
font-size: 22px;
|
||||
min-width: 16rem;
|
||||
}
|
||||
|
||||
.icon {
|
||||
min-width: 64px;
|
||||
min-height: 64px;
|
||||
margin: 5px;
|
||||
font-size: 58px;
|
||||
}
|
||||
|
||||
.timeout-bar {
|
||||
background: $bg-alt-1;
|
||||
|
||||
> trough > progress {
|
||||
background-image: none;
|
||||
background-color: $hl-alt-1;
|
||||
}
|
||||
}
|
||||
|
||||
.urgency-indicator {
|
||||
min-width: 25px;
|
||||
|
||||
&.NORMAL, &.LOW {
|
||||
background: $hl-alt-2;
|
||||
}
|
||||
|
||||
&.CRITICAL {
|
||||
background: $hl;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,29 +0,0 @@
|
||||
export default ({
|
||||
name,
|
||||
child,
|
||||
transition = "slide_up",
|
||||
transitionDuration = 250,
|
||||
...props
|
||||
}) => {
|
||||
const reveal = Variable(false)
|
||||
const window = Widget.Window({
|
||||
name,
|
||||
visible: false,
|
||||
...props,
|
||||
|
||||
child: Widget.Box({
|
||||
css: `min-height: 2px;
|
||||
min-width: 2px;`,
|
||||
child: Widget.Revealer({
|
||||
transition,
|
||||
transitionDuration,
|
||||
hexpand: true,
|
||||
vexpand: true,
|
||||
child: child,
|
||||
revealChild: reveal.bind()
|
||||
}),
|
||||
}),
|
||||
});
|
||||
|
||||
return window, reveal;
|
||||
}
|
2
widgets/__init__.hy
Normal file
2
widgets/__init__.hy
Normal file
@ -0,0 +1,2 @@
|
||||
(import .bar [bar])
|
||||
(import .notifications [notifications])
|
34
widgets/bar/__init__.hy
Normal file
34
widgets/bar/__init__.hy
Normal file
@ -0,0 +1,34 @@
|
||||
(import astal.gtk3 *)
|
||||
(import astal *)
|
||||
|
||||
(import .workspaces [workspaces])
|
||||
(import .mpris [mpris-controls])
|
||||
(import .clock [clock])
|
||||
(import .battery [battery-dial])
|
||||
(import .volume [volume])
|
||||
(import .brightness [brightness])
|
||||
|
||||
(setv bar (Widget.Window
|
||||
:namespace "bar"
|
||||
:name "bar"
|
||||
:anchor (| Astal.WindowAnchor.TOP Astal.WindowAnchor.LEFT Astal.WindowAnchor.RIGHT)
|
||||
:exclusivity Astal.Exclusivity.EXCLUSIVE
|
||||
:child (Widget.CenterBox
|
||||
:start-widget (Widget.Box
|
||||
:class-name "left"
|
||||
:children [
|
||||
workspaces
|
||||
mpris-controls])
|
||||
:end-widget (Widget.Box
|
||||
:class-name "right"
|
||||
:halign Gtk.Align.END
|
||||
:children [
|
||||
(Widget.Box
|
||||
:class-name "sliders"
|
||||
:vertical True
|
||||
:children [
|
||||
volume
|
||||
brightness])
|
||||
battery-dial
|
||||
((astalify Gtk.Separator))
|
||||
clock]))))
|
27
widgets/bar/battery.hy
Normal file
27
widgets/bar/battery.hy
Normal file
@ -0,0 +1,27 @@
|
||||
(import astal *)
|
||||
(import astal.gtk3 *)
|
||||
|
||||
(.require-version gi "AstalBattery" "0.1")
|
||||
|
||||
(import gi.repository [AstalBattery :as Battery])
|
||||
|
||||
(let [
|
||||
battery (.get-default Battery)
|
||||
icons [
|
||||
["" "" "" "" "" "" "" "" "" "" ""]
|
||||
["" "" "" "" "" "" "" "" "" "" ""]]]
|
||||
(setv battery-dial (Widget.Box
|
||||
:class-name "battery-container"
|
||||
:children [
|
||||
(Widget.CircularProgress
|
||||
:class-name "battery-dial"
|
||||
:rounded False
|
||||
:inverted False
|
||||
:start-at -.25
|
||||
:end-at .75
|
||||
:value (bind battery "percentage")
|
||||
:child (Widget.Label
|
||||
:halign Gtk.Align.CENTER
|
||||
:hexpand True
|
||||
:justify 2
|
||||
:label (.transform (bind battery "percentage") (fn [percentage] (get (get icons (.get-charging battery)) (round (* percentage 10)))))))])))
|
22
widgets/bar/brightness.hy
Normal file
22
widgets/bar/brightness.hy
Normal file
@ -0,0 +1,22 @@
|
||||
(import astal *)
|
||||
(import astal.gtk3 *)
|
||||
(import services.brightness [Brightness])
|
||||
(import math [floor])
|
||||
|
||||
(import gi.repository [AstalWp])
|
||||
|
||||
(let [
|
||||
backlight (Brightness "amdgpu_bl1")]
|
||||
(setv brightness (Widget.Box
|
||||
:class-name "brightness-slider"
|
||||
:children [
|
||||
(Widget.Button
|
||||
:child (Widget.Icon :icon (bind backlight "brightness" (fn [brightness]
|
||||
f"display-brightness-{(get ["off" "low" "medium" "high" "high"] (floor (/ (* brightness 100) 25)))}-symbolic"))))
|
||||
(Widget.Slider
|
||||
:class-name "brightness-slider"
|
||||
:hexpand True
|
||||
:draw-value False
|
||||
:value (bind backlight "brightness")
|
||||
:on-dragged (fn [self]
|
||||
(. backlight (set-brightness (.get-value self)))))])))
|
19
widgets/bar/clock.hy
Normal file
19
widgets/bar/clock.hy
Normal file
@ -0,0 +1,19 @@
|
||||
(import astal *)
|
||||
(import astal.gtk3 *)
|
||||
(import datetime)
|
||||
|
||||
(let [
|
||||
time (.poll (Variable "") 1000 (fn [_] (. datetime datetime (now) (strftime "%d %b %H:%M:%S"))) (fn [out] out))
|
||||
unix-seconds (.poll (Variable "") 1000 (fn [_] (. datetime datetime (now) (strftime "%s"))) (fn [out] out))]
|
||||
(setv clock (Widget.Box
|
||||
:class-name "clock"
|
||||
:vertical True
|
||||
:valign Gtk.Align.CENTER
|
||||
:children [
|
||||
(Widget.Label
|
||||
:halign Gtk.Align.START
|
||||
:label (bind time))
|
||||
(Widget.Label
|
||||
:halign Gtk.Align.START
|
||||
:label (bind unix-seconds))])))
|
||||
|
81
widgets/bar/mpris.hy
Normal file
81
widgets/bar/mpris.hy
Normal file
@ -0,0 +1,81 @@
|
||||
(import astal *)
|
||||
(import astal.gtk3 *)
|
||||
|
||||
(import math)
|
||||
|
||||
(.require-version gi "AstalMpris" "0.1")
|
||||
|
||||
(import gi.repository [AstalMpris :as Mpris])
|
||||
|
||||
(let [mpris (.get-default Mpris)]
|
||||
(setv mpris-controls
|
||||
(Widget.Stack
|
||||
:transition-type Gtk.StackTransitionType.SLIDE_UP_DOWN
|
||||
:transition-duration 125
|
||||
:children []
|
||||
:setup (fn [self]
|
||||
(.add-events self Gdk.EventMask.SCROLL_MASK)
|
||||
(.add-events self Gdk.EventMask.SMOOTH_SCROLL_MASK)
|
||||
|
||||
(defn add-player [player]
|
||||
(.add-named self
|
||||
(Widget.Box
|
||||
:class-name "player"
|
||||
:vertical True
|
||||
:hexpand False
|
||||
:setup (fn [self] (.set-name self (.get-bus-name player)))
|
||||
:css (.transform (bind player "cover-art") (fn [cover-uri]
|
||||
(when cover-uri (return f"
|
||||
background: linear-gradient(to right, rgba(0, 0, 0, 0), rgba(22, 22, 22, 0.925)), url(\"{cover-uri}\");
|
||||
background-position: 50% 50%;
|
||||
background-size: cover;
|
||||
"))
|
||||
(return "")))
|
||||
:children [
|
||||
(Widget.Box
|
||||
:vertical True
|
||||
:vexpand True
|
||||
:valign Gtk.Align.CENTER
|
||||
:halign Gtk.Align.END
|
||||
:children [
|
||||
(Widget.Button
|
||||
:on-clicked (fn [#* _] (.previous player))
|
||||
:child (Widget.Icon :icon "media-skip-backward-symbolic"))
|
||||
(Widget.Button
|
||||
:on-clicked (fn [#* _] (.play-pause player))
|
||||
:child (Widget.Icon :icon (.transform (bind player "playback-status") (fn [status]
|
||||
(when (= status Mpris.PlaybackStatus.PLAYING)
|
||||
(return "media-playback-pause-symbolic"))
|
||||
(return "media-playback-start-symbolic")))))
|
||||
(Widget.Button
|
||||
:on-clicked (fn [#* _] (.next player))
|
||||
:child (Widget.Icon :icon "media-skip-forward-symbolic"))])
|
||||
(Widget.Slider
|
||||
:hexpand True
|
||||
:class-name "media-progress"
|
||||
:valign Gtk.Align.END
|
||||
:halign Gtk.Align.START
|
||||
:on-dragged (fn [self] (.set-position player (* (.get-value self) (.get-length player))))
|
||||
:setup (fn [self]
|
||||
(.poll (Variable) 500 (fn [#* _]
|
||||
(when player
|
||||
(.set-value self (/ (.get-position player) (.get-length player))))))))])
|
||||
(.get-bus-name player)))
|
||||
|
||||
(for [player (.get-players mpris)] (add-player player))
|
||||
|
||||
(.hook self mpris "player-added" (fn [self player]
|
||||
(add-player player)))
|
||||
|
||||
(.hook self mpris "player-closed" (fn [self player]
|
||||
(.destroy (.get-named self (.get-bus-name player)))))
|
||||
|
||||
(setv accumulated-delta-y 0)
|
||||
|
||||
(.hook self self "scroll-event" (fn [_ event]
|
||||
(nonlocal accumulated-delta-y)
|
||||
(setv accumulated-delta-y (+ accumulated-delta-y (get (.get-scroll-deltas event) 2)))
|
||||
|
||||
(when (> (abs accumulated-delta-y) 10)
|
||||
(.set-visible-child self (get (.get-children self) (% (+ (.index (lfor child (.get-children self) (.get-name child)) (.get-shown self)) (* 1 (round (/ accumulated-delta-y 10)))) (len (.get-children self)))))
|
||||
(setv accumulated-delta-y 0))))))))
|
22
widgets/bar/volume.hy
Normal file
22
widgets/bar/volume.hy
Normal file
@ -0,0 +1,22 @@
|
||||
(import astal *)
|
||||
(import astal.gtk3 *)
|
||||
|
||||
(.require-version gi "AstalWp" "0.1")
|
||||
|
||||
(import gi.repository [AstalWp])
|
||||
|
||||
(let [
|
||||
endpoint (. AstalWp (get-default) (get-audio) (get-default-speaker))]
|
||||
(setv volume (Widget.Box
|
||||
:class-name "volume-slider"
|
||||
:children [
|
||||
(Widget.Button
|
||||
:child (Widget.Icon :icon (bind endpoint "volume-icon"))
|
||||
:on-clicked (fn [#* _] (.set-mute endpoint (not (.get-mute endpoint)))))
|
||||
(Widget.Slider
|
||||
:class-name "volume-slider"
|
||||
:hexpand True
|
||||
:draw-value False
|
||||
:value (bind endpoint "volume")
|
||||
:on-dragged (fn [self]
|
||||
(.set-volume endpoint (.get-value self))))])))
|
48
widgets/bar/workspaces.hy
Normal file
48
widgets/bar/workspaces.hy
Normal file
@ -0,0 +1,48 @@
|
||||
(import astal *)
|
||||
(import astal.gtk3 *)
|
||||
|
||||
(.require-version gi "AstalNiri" "0.1")
|
||||
|
||||
(import gi.repository [AstalNiri :as Niri])
|
||||
|
||||
(let [
|
||||
Niri (.get-default Niri)
|
||||
workspace-row (fn [start stop]
|
||||
(Widget.Box
|
||||
:children (lfor i (range start stop)
|
||||
(Widget.Button
|
||||
:class-name "workspace"
|
||||
:attribute (+ i 1)
|
||||
:on-clicked (fn [self] (exec-async f"niri msg action focus-workspace {self.attribute}"))
|
||||
:setup (fn [self]
|
||||
|
||||
(.hook self Niri "workspace-activated" (fn [_ w __]
|
||||
(when w
|
||||
(.toggle-class-name self "focused" (= (.get-id (. Niri (get-workspace w))) self.attribute)))))
|
||||
|
||||
(defn update [#* _]
|
||||
(let [workspace (.get-workspace Niri self.attribute)]
|
||||
(when (!= workspace None)
|
||||
(.toggle-class-name self "occupied" (< 0 (len (lfor window (. Niri (get-windows))
|
||||
:if (= (.get-workspace-id window) (.get-id workspace))
|
||||
window)))))))
|
||||
|
||||
(.hook self Niri "workspaces-changed" update)
|
||||
(.hook self Niri "window-opened" update)
|
||||
(.hook self Niri "window-changed" update)
|
||||
(.hook self Niri "window-closed" update)
|
||||
|
||||
(idle update)
|
||||
|
||||
(idle (fn [] (when (= (.get-id (.get-focused-workspace Niri)) self.attribute)
|
||||
(.toggle-class-name self "focused")))))))))]
|
||||
|
||||
(setv workspaces (Widget.Box
|
||||
:class_name "workspaces"
|
||||
:vertical True
|
||||
:hexpand False
|
||||
:halign Gtk.Align.START
|
||||
:valign Gtk.Align.CENTER
|
||||
:children [
|
||||
(workspace-row 0 5)
|
||||
(workspace-row 5 10)])))
|
@ -1,38 +0,0 @@
|
||||
const battery = await Service.import('battery')
|
||||
|
||||
const battery_dial = Widget.CircularProgress({
|
||||
className: 'battery-dial',
|
||||
rounded: false,
|
||||
inverted: false,
|
||||
startAt: 0.75,
|
||||
value: battery.bind('percent').as(p => p / 100),
|
||||
child: Widget.Label({
|
||||
className: "dial-icon",
|
||||
hexpand: true,
|
||||
setup: (self) => {
|
||||
self.hook(battery, (self) => {
|
||||
console.log(battery)
|
||||
const icons = [
|
||||
["", "", "", "", "", "", "", "", "", "", ""],
|
||||
["", "", "", "", "", "", "", "", "", "", ""],
|
||||
];
|
||||
self.label = icons[Number(battery.charging)][Math.floor(battery.percent / 10)];
|
||||
self.tooltip_text = 'Battery ' + String(battery.percent) + '%';
|
||||
});
|
||||
}
|
||||
}),
|
||||
setup: (self) => {
|
||||
self.hook(battery, (self) => {
|
||||
if (battery.percent <= 30 && battery.charging === false) {
|
||||
self.toggleClassName("battery-low", true);
|
||||
} else {
|
||||
self.toggleClassName("battery-low", false);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
export {
|
||||
battery_dial
|
||||
}
|
||||
|
@ -1,63 +0,0 @@
|
||||
const { exec, execAsync } = Utils;
|
||||
|
||||
import Brightness from '../services/brightness.js'
|
||||
|
||||
const brightness_dial = Widget.EventBox({
|
||||
className: 'eventbox-hide-pointer',
|
||||
'on-primary-click': () => {execAsync('hyprshade toggle blue-light-filter')},
|
||||
'on-scroll-up': () => {Brightness.screen += 0.01},
|
||||
'on-scroll-down': () => {Brightness.screen -= 0.01},
|
||||
child: Widget.CircularProgress({
|
||||
rounded: false,
|
||||
className: 'brightness-dial',
|
||||
inverted: false,
|
||||
startAt: 0.75,
|
||||
value: Brightness.bind('screen'),
|
||||
child: Widget.Label({
|
||||
className: "dial-icon",
|
||||
hexpand: true,
|
||||
hpack: 'center',
|
||||
setup: (self) => {
|
||||
self.hook(Brightness, (self => {
|
||||
const brightness = Brightness.screen * 100;
|
||||
|
||||
self.label = ["", "", "", "", "", "", ""][Math.floor(brightness/15)]
|
||||
self.tooltip_text = `Brightness ${Math.floor(brightness)}%`;
|
||||
}))
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
const brightness_slider = Widget.Box({
|
||||
className: 'brightness',
|
||||
children: [
|
||||
Widget.Button({
|
||||
on_clicked: () => execAsync('hyprshade toggle blue-light-filter'),
|
||||
child: Widget.Icon().hook(Brightness, self => {
|
||||
const brightness = Brightness.screen * 100;
|
||||
const icon = [
|
||||
[80, 'display-brightness-high-symbolic'],
|
||||
[50, 'display-brightness-medium-symbolic'],
|
||||
[20, 'display-brightness-low-symbolic'],
|
||||
[0, 'display-brightness-off-symbolic']
|
||||
].find(([threshold]) => brightness >= threshold)?.[1];
|
||||
|
||||
self.icon = icon;
|
||||
self.tooltip_text = `Brightness ${Math.floor(brightness)}%`;
|
||||
}),
|
||||
}),
|
||||
Widget.Slider({
|
||||
className: 'slider',
|
||||
hexpand: true,
|
||||
drawValue: false,
|
||||
onChange: ({ value }) => Brightness.screen = value,
|
||||
value: Brightness.bind('screen'),
|
||||
})
|
||||
]
|
||||
});
|
||||
|
||||
export {
|
||||
brightness_dial,
|
||||
brightness_slider
|
||||
}
|
@ -1,62 +0,0 @@
|
||||
const { exec, execAsync } = Utils;
|
||||
|
||||
const bar_clock = Widget.Box({
|
||||
className: 'clock',
|
||||
vpack: 'end',
|
||||
vertical: true,
|
||||
setup: (self) => {
|
||||
var month_and_date, hours_and_minutes, seconds;
|
||||
self.poll(1000, self => {
|
||||
execAsync("date +'%m/%d %H:%M %S'").then((time) => {
|
||||
[month_and_date, hours_and_minutes, seconds] = time.split(' ');
|
||||
});
|
||||
self.children = [
|
||||
Widget.Label({
|
||||
className: 'datetime',
|
||||
label: month_and_date
|
||||
}),
|
||||
Widget.Label({
|
||||
className: 'datetime',
|
||||
label: hours_and_minutes
|
||||
}),
|
||||
Widget.Label({
|
||||
className: 'datetime',
|
||||
label: seconds
|
||||
}),
|
||||
]
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
const horizontal_clock = Widget.Box({
|
||||
className: 'clock',
|
||||
vpack: 'center',
|
||||
vertical: true,
|
||||
setup: (self) => {
|
||||
var date_time, unix_seconds;
|
||||
self.poll(1000, self => {
|
||||
execAsync("date +'%d %b %H:%M:%S %s'").then((time) => {
|
||||
let parts = time.split(' ');
|
||||
date_time = `${parts[0]} ${parts[1]} ${parts[2]}`;
|
||||
unix_seconds = parts[3];
|
||||
});
|
||||
self.children = [
|
||||
Widget.Label({
|
||||
className: 'datetime',
|
||||
label: date_time
|
||||
}),
|
||||
Widget.Label({
|
||||
hpack: 'start',
|
||||
className: 'datetime',
|
||||
label: unix_seconds
|
||||
})
|
||||
];
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
export {
|
||||
bar_clock,
|
||||
horizontal_clock
|
||||
}
|
@ -1,10 +0,0 @@
|
||||
const hyprland = await Service.import('hyprland')
|
||||
|
||||
const active_window = Widget.Label({
|
||||
className: 'active-window',
|
||||
label: '',//hyprland.bind('active').as(c => c.client.title),
|
||||
})
|
||||
|
||||
export {
|
||||
active_window
|
||||
}
|
@ -1,59 +0,0 @@
|
||||
const hyprland = await Service.import('hyprland')
|
||||
|
||||
|
||||
const goto_workspace = (ws) => hyprland.messageAsync(`dispatch workspace ${ws}`)
|
||||
|
||||
const hyprworkspaces = Widget.EventBox({
|
||||
onScrollUp: () => goto_workspace('+1'),
|
||||
onScrollDown: () => goto_workspace('-1'),
|
||||
child: Widget.Box({
|
||||
vertical: true,
|
||||
className: 'workspace-container',
|
||||
children: Array.from({ length: 10 }, (_, i) => i + 1).map(i => Widget.Button({
|
||||
className: 'ws-norm',
|
||||
attribute: i,
|
||||
child: Widget.Label(String(i)),
|
||||
onClicked: () => goto_workspace(i),
|
||||
setup: self => self.hook(hyprland, self => self.attribute == hyprland.active.workspace.id ?
|
||||
self.toggleClassName('ws-active', true)
|
||||
: self.toggleClassName('ws-active', false))
|
||||
})),
|
||||
|
||||
setup: self => self.hook(hyprland, () => self.children.forEach(btn => {
|
||||
btn.visible = hyprland.workspaces.some(ws => ws.id === btn.attribute);
|
||||
})),
|
||||
}),
|
||||
})
|
||||
|
||||
function workspace_row(start, length) {
|
||||
return Widget.Box({
|
||||
vpack: 'center',
|
||||
hpack: 'center',
|
||||
className: 'workspace-row',
|
||||
children: Array.from({ length: length }, (_, i) => i + 1 + start).map(i => Widget.Button({
|
||||
className: 'workspace',
|
||||
attribute: i,
|
||||
onClicked: () => goto_workspace(i),
|
||||
setup: self => {
|
||||
self.hook(hyprland, self => {
|
||||
self.attribute == hyprland.active.workspace.id ? self.toggleClassName('focused', true) : self.toggleClassName('focused', false)
|
||||
hyprland.workspaces.map(w => w.id).includes(self.attribute) ? self.toggleClassName('occupied', true) : self.toggleClassName('occupied', false)
|
||||
})
|
||||
}
|
||||
}))
|
||||
})
|
||||
}
|
||||
|
||||
const hyprworkspaces_grid = Widget.Box({
|
||||
className: 'workspaces',
|
||||
vertical: true,
|
||||
children: [
|
||||
workspace_row(0, 5),
|
||||
workspace_row(5, 5)
|
||||
]
|
||||
})
|
||||
|
||||
export {
|
||||
hyprworkspaces,
|
||||
hyprworkspaces_grid
|
||||
}
|
@ -1,87 +0,0 @@
|
||||
const { Gtk, cairo } = imports.gi;
|
||||
const register = Widget.register;
|
||||
|
||||
class MarqueeLabel extends Gtk.DrawingArea {
|
||||
static {
|
||||
register(this, {
|
||||
properties: {
|
||||
label: ["string", "rw"],
|
||||
"scroll-speed": ["int", "rw"],
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
#xOffset;
|
||||
#scrollDirection;
|
||||
|
||||
get label() {
|
||||
return this._label;
|
||||
}
|
||||
|
||||
set label(label) {
|
||||
this._label = label;
|
||||
this.queue_draw();
|
||||
this.notify("label");
|
||||
}
|
||||
|
||||
get scroll_speed() {
|
||||
return this._scrollSpeed;
|
||||
}
|
||||
|
||||
set scroll_speed(speed) {
|
||||
this._scrollSpeed = speed;
|
||||
this.notify("scroll-speed");
|
||||
}
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this._reset();
|
||||
|
||||
this.poll(this._scrollSpeed * 11, () => this.queue_draw());
|
||||
|
||||
this.on("size-allocate", () => this._reset());
|
||||
this.on("notify::label", () => this._reset());
|
||||
}
|
||||
|
||||
_reset() {
|
||||
this.#xOffset = 1;
|
||||
this.#scrollDirection = 0;
|
||||
}
|
||||
|
||||
vfunc_draw(cr) {
|
||||
const allocation = this.get_allocation();
|
||||
const styles = this.get_style_context();
|
||||
const width = allocation.width;
|
||||
const height = allocation.height;
|
||||
const color = styles.get_color(Gtk.StateFlags.NORMAL);
|
||||
const [fontFamily] = styles.get_property(
|
||||
"font-family",
|
||||
Gtk.StateFlags.NORMAL,
|
||||
);
|
||||
const fontSize = Math.floor(
|
||||
styles.get_property("font-size", Gtk.StateFlags.NORMAL),
|
||||
);
|
||||
|
||||
cr.setSourceRGB(color.red, color.green, color.blue);
|
||||
cr.selectFontFace(fontFamily, null, null);
|
||||
cr.setFontSize(fontSize);
|
||||
|
||||
const labelWidth = cr.textExtents(this._label).width;
|
||||
|
||||
if (labelWidth > width) {
|
||||
this.#xOffset += this.#scrollDirection * this._scrollSpeed;
|
||||
|
||||
if (this.#xOffset >= 1 || this.#xOffset <= width - labelWidth) {
|
||||
this.#scrollDirection *= 0;
|
||||
}
|
||||
} else {
|
||||
this.#xOffset = (width - labelWidth) / 3;
|
||||
}
|
||||
|
||||
cr.moveTo(this.#xOffset*1.475, fontSize);
|
||||
cr.showText(this._label);
|
||||
}
|
||||
}
|
||||
|
||||
export default MarqueeLabel;
|
182
widgets/mpd.js
182
widgets/mpd.js
@ -1,182 +0,0 @@
|
||||
const { Gtk } = imports.gi;
|
||||
|
||||
import Mpd from '../services/mpd.js';
|
||||
import MarqueeLabel from './marqueeLabel.js'
|
||||
|
||||
const Mpris = await Service.import("mpris");
|
||||
|
||||
const AspectFrame = Widget.subclass(Gtk.AspectFrame);
|
||||
|
||||
function lengthString(length) {
|
||||
return (
|
||||
`${Math.floor(length / 60)
|
||||
.toString()
|
||||
.padStart(2, "0")}:` +
|
||||
`${Math.floor(length % 60)
|
||||
.toString()
|
||||
.padStart(2, "0")}`
|
||||
);
|
||||
}
|
||||
|
||||
const albumCover = Widget.Box({
|
||||
className: "cover",
|
||||
setup: (self) => self.hook(Mpris, () => {
|
||||
const mpd = Mpris.getPlayer("mpd");
|
||||
self.css = `background-image: url("${mpd?.coverPath}");`;
|
||||
}),
|
||||
});
|
||||
|
||||
const positionLabel = Widget.Label({
|
||||
className: 'position-label',
|
||||
setup: (self) => self.poll(500, () => {
|
||||
Mpd.send("status")
|
||||
.then((msg) => {
|
||||
const elapsed = msg?.match(/elapsed: (\d+\.\d+)/)?.[1];
|
||||
self.label = `${lengthString(elapsed || 0)} / ${lengthString(Mpd.duration || 0)}`;
|
||||
})
|
||||
.catch((error) => logError(error));
|
||||
}),
|
||||
});
|
||||
|
||||
const positionSlider = Widget.Slider({
|
||||
className: 'position',
|
||||
vpack: 'end',
|
||||
drawValue: false,
|
||||
onChange: ({ value }) => {
|
||||
Mpd.seekCur(value * Mpd.duration);
|
||||
},
|
||||
setup: (self) => {
|
||||
self.poll(500, () => {
|
||||
Mpd.send("status")
|
||||
.then((msg) => {
|
||||
const elapsed = msg?.match(/elapsed: (\d+\.\d+)/)?.[1];
|
||||
self.value = elapsed / Mpd.duration || 0;
|
||||
})
|
||||
.catch((error) => logError(error));
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
const songTitle = Widget.Box({
|
||||
className: 'title',
|
||||
children: [
|
||||
new MarqueeLabel({
|
||||
heightRequest: 30,
|
||||
widthRequest: 350,
|
||||
scrollSpeed: 1,
|
||||
label: 'No Title',
|
||||
setup: (self) => {
|
||||
self.hook(Mpd, () => {
|
||||
self.label = `${Mpd.Title || "No Title"}`;
|
||||
});
|
||||
},
|
||||
})
|
||||
]
|
||||
})
|
||||
|
||||
const songArtist = Widget.Box({
|
||||
className: 'artist',
|
||||
children: [
|
||||
new MarqueeLabel({
|
||||
heightRequest: 30,
|
||||
widthRequest: 350,
|
||||
scrollSpeed: 1,
|
||||
label: 'No Artist',
|
||||
setup: (self) => {
|
||||
self.hook(Mpd, () => {
|
||||
self.label = `${Mpd.Artist || "No Artist"}`;
|
||||
});
|
||||
},
|
||||
})
|
||||
]
|
||||
})
|
||||
|
||||
const mediaControls = Widget.Box()
|
||||
|
||||
const mpd_controls = () => Widget.CenterBox({
|
||||
className: 'mpd-controls',
|
||||
hpack: 'center',
|
||||
hexpand: true,
|
||||
startWidget: Widget.Button({
|
||||
hpack: 'start',
|
||||
className: 'button',
|
||||
onClicked: () => Mpd.previous(),
|
||||
child: Widget.Icon('media-skip-backward-symbolic')
|
||||
}),
|
||||
centerWidget: Widget.Button({
|
||||
hpack: 'center',
|
||||
className: 'button',
|
||||
onClicked: () => Mpd.playPause(),
|
||||
child: Widget.Icon({
|
||||
setup: self => self.hook(Mpd, () => (Mpd.state === 'play')
|
||||
? self.icon = 'media-playback-pause-symbolic'
|
||||
: self.icon = 'media-playback-start-symbolic')
|
||||
})
|
||||
}),
|
||||
endWidget: Widget.Button({
|
||||
hpack: 'end',
|
||||
className: 'button',
|
||||
onClicked: () => Mpd.next(),
|
||||
child: Widget.Icon('media-skip-forward-symbolic')
|
||||
})
|
||||
})
|
||||
|
||||
export const mpd_bar_controls = mpd_controls()
|
||||
|
||||
export const mpd_menu_controls = Widget.Box({
|
||||
className: 'mpd-controls',
|
||||
children: [
|
||||
albumCover,
|
||||
Widget.Box({
|
||||
vertical: true,
|
||||
hpack: 'end',
|
||||
children: [
|
||||
Widget.Box({
|
||||
vertical: true,
|
||||
children: [
|
||||
songTitle,
|
||||
songArtist
|
||||
]
|
||||
}),
|
||||
mpd_controls(),
|
||||
positionLabel,
|
||||
positionSlider
|
||||
]
|
||||
})
|
||||
]
|
||||
})
|
||||
|
||||
export const cover_with_controls = Widget.Box({
|
||||
className: 'cover',
|
||||
hexpand: true,
|
||||
vexpand: true,
|
||||
child: Widget.Box({
|
||||
className: "cover",
|
||||
vertical: true,
|
||||
children: [
|
||||
mpd_controls(),
|
||||
Widget.Slider({
|
||||
vpack: 'end',
|
||||
className: 'progress-bar',
|
||||
drawValue: false,
|
||||
hexpand: false,
|
||||
onChange: ({ value }) => Mpd.seekCur(value * Mpd.duration),
|
||||
setup: (self) => {
|
||||
self.poll(500, () => {
|
||||
Mpd.send("status")
|
||||
.then((msg) => {
|
||||
const elapsed = msg?.match(/elapsed: (\d+\.\d+)/)?.[1];
|
||||
self.value = elapsed / Mpd.duration || 0;
|
||||
})
|
||||
.catch((error) => logError(error));
|
||||
});
|
||||
},
|
||||
})
|
||||
],
|
||||
setup: (self) =>
|
||||
self.hook(Mpris, () => {
|
||||
const mpd = Mpris.getPlayer("mpd");
|
||||
self.css = `background: linear-gradient(rgba(0, 0, 0, 0.6), rgba(0, 0, 0, 0.6)), url("${mpd?.coverPath}"); min-height: 60px; min-width: 250px; background-position: 50% 50%; background-size: cover; border: 5px solid #161616`;
|
||||
})
|
||||
})
|
||||
})
|
114
widgets/mpris.js
114
widgets/mpris.js
@ -1,114 +0,0 @@
|
||||
const { Gtk, Gdk } = imports.gi;
|
||||
const Mpris = await Service.import('mpris')
|
||||
|
||||
const media_controls = (player) => Widget.CenterBox({
|
||||
className: 'media-controls',
|
||||
hpack: 'center',
|
||||
hexpand: true,
|
||||
startWidget: Widget.Button({
|
||||
hpack: 'start',
|
||||
className: 'button',
|
||||
onClicked: () => player.previous(),
|
||||
child: Widget.Icon('media-skip-backward-symbolic')
|
||||
}),
|
||||
centerWidget: Widget.Button({
|
||||
hpack: 'center',
|
||||
className: 'button',
|
||||
onClicked: () => player.playPause(),
|
||||
child: Widget.Icon({
|
||||
setup: self => self.hook(Mpris, () => (player.playBackStatus === 'Playing')
|
||||
? self.icon = 'media-playback-pause-symbolic'
|
||||
: self.icon = 'media-playback-start-symbolic')
|
||||
})
|
||||
}),
|
||||
endWidget: Widget.Button({
|
||||
hpack: 'end',
|
||||
className: 'button',
|
||||
onClicked: () => player.next(),
|
||||
child: Widget.Icon('media-skip-forward-symbolic')
|
||||
})
|
||||
})
|
||||
|
||||
const cover_with_controls = (player) => Widget.Box({
|
||||
className: "media",
|
||||
children: [
|
||||
Widget.Box({
|
||||
vertical: true,
|
||||
children: [
|
||||
media_controls(player),
|
||||
Widget.Slider({
|
||||
vpack: 'end',
|
||||
className: 'progress-bar',
|
||||
drawValue: false,
|
||||
hexpand: false,
|
||||
onChange: ({ value }) => {player.position = (value * player.length)},
|
||||
setup: self => self.poll(500, self => {
|
||||
if (!player) return
|
||||
self.value = player.position/player.length
|
||||
})
|
||||
})
|
||||
],
|
||||
}),
|
||||
],
|
||||
setup: (self) => {
|
||||
self.hook(Mpris, () => {
|
||||
self.queue_draw()
|
||||
self.css = `background: linear-gradient(rgba(0, 0, 0, 0.6), rgba(0, 0, 0, 0.6)), url("${player.coverPath}"); min-height: 60px; min-width: 250px; background-position: 50% 50%; background-size: cover; border: 5px solid #161616`;
|
||||
})
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
export const players = Widget.Stack({
|
||||
transition: "slide_up_down",
|
||||
transitionDuration: 125,
|
||||
children: {},
|
||||
setup: (self) => {
|
||||
self.add_events(Gdk.EventMask.SCROLL_MASK);
|
||||
self.add_events(Gdk.EventMask.SMOOTH_SCROLL_MASK);
|
||||
|
||||
let currentDeltaY = 0;
|
||||
|
||||
self.on("scroll-event", (_, event) => {
|
||||
const childNames = Object.keys(self.children);
|
||||
|
||||
const length = Object.keys(self.children).length
|
||||
|
||||
const prevChild = childNames[((n)=>n>=0?n:length-1)((childNames.indexOf(self.get_visible_child_name()) - 1) % length)];
|
||||
const nextChild = childNames[(childNames.indexOf(self.get_visible_child_name()) + 1) % length];
|
||||
|
||||
const deltaY = event.get_scroll_deltas()[2];
|
||||
|
||||
currentDeltaY += deltaY;
|
||||
|
||||
if (currentDeltaY > 10 && prevChild) {
|
||||
self.set_visible_child_name(prevChild);
|
||||
currentDeltaY = 0;
|
||||
}
|
||||
if (currentDeltaY < -10 && nextChild) {
|
||||
self.set_visible_child_name(nextChild);
|
||||
currentDeltaY = 0;
|
||||
}
|
||||
console.log(self.children)
|
||||
});
|
||||
|
||||
self.hook(Mpris, (_, name) => {
|
||||
if (!name) return;
|
||||
self.add_named(cover_with_controls(Mpris.getPlayer(name)), name);
|
||||
}, "player-added");
|
||||
self.hook(Mpris, (_, name) => {
|
||||
if (!name) return;
|
||||
|
||||
self.get_child_by_name(name).destroy();
|
||||
delete children[name]
|
||||
console.log('destroyed')
|
||||
}, "player-closed");
|
||||
},
|
||||
});
|
||||
|
||||
export const media = Widget.Revealer({
|
||||
revealChild: Mpris.bind("players").as((players) => players.length > 0),
|
||||
transition: "slide_up",
|
||||
transitionDuration: 125,
|
||||
child: players,
|
||||
});
|
107
widgets/notifications/__init__.hy
Normal file
107
widgets/notifications/__init__.hy
Normal file
@ -0,0 +1,107 @@
|
||||
(import astal *)
|
||||
(import astal.gtk3 *)
|
||||
|
||||
(.require-version gi "AstalNotifd" "0.1")
|
||||
(.require-version gi "Pango" "1.0")
|
||||
|
||||
(import gi.repository [AstalNotifd Pango])
|
||||
|
||||
(let [
|
||||
ProgressBar (astalify Gtk.ProgressBar)
|
||||
notifd (.get-default AstalNotifd)
|
||||
notification-timeout 3
|
||||
notification-icon (fn [notification]
|
||||
(cond
|
||||
(.get-image notification) (Widget.Box
|
||||
:class-name "icon image"
|
||||
:css f"
|
||||
background-image: url(\"{(.get-image notification)}\");
|
||||
background-size: contain;
|
||||
background-repeat: no-repeat;
|
||||
background-position: center;")
|
||||
(. Astal Icon (lookup-icon (.get-app-icon notification))) (Widget.Icon
|
||||
:icon (.get-app-icon notification)
|
||||
:class-name "icon")
|
||||
True (Widget.Icon
|
||||
:icon "dialog-information-symbolic"
|
||||
:class-name "icon")))
|
||||
|
||||
make-notification (fn [notification]
|
||||
(let [
|
||||
layout (Widget.Button
|
||||
:on-clicked (fn [self] (. self (get-parent) (set-reveal-child False)))
|
||||
:child (Widget.Box
|
||||
:children [
|
||||
(Widget.Box
|
||||
:vertical True
|
||||
:css "min-width: 200px; min-height 50px;;"
|
||||
:children [
|
||||
(Widget.Box
|
||||
:children [
|
||||
(notification-icon notification)
|
||||
(Widget.Box
|
||||
:vertical True
|
||||
:children [
|
||||
(Widget.Label
|
||||
:class-name "title"
|
||||
:label (str (.get-summary notification))
|
||||
:xalign 0
|
||||
:justify 0
|
||||
:ellipsize Pango.EllipsizeMode.END
|
||||
)
|
||||
(Widget.Label
|
||||
:class-name "body"
|
||||
:label (str (.get-body notification))
|
||||
:xalign 0
|
||||
:justify 0
|
||||
:wrap True
|
||||
:wrap-mode Pango.WrapMode.WORD_CHAR
|
||||
:use-markup True
|
||||
)])])
|
||||
(ProgressBar
|
||||
:class-name "timeout-bar"
|
||||
:hexpand True
|
||||
:valign 2
|
||||
:fraction (bind (.poll (Variable 1) (// (+ (* (or (max (.get-expire-timeout notification) 0) notification-timeout) 1000) 250) 100) (fn [prev]
|
||||
(when (> prev .02)
|
||||
(return (- prev .01)))
|
||||
(return 0)))))])
|
||||
(Widget.Box :class-name f"urgency-indicator {(.get-urgency notification)}")]))]
|
||||
|
||||
(Widget.Revealer
|
||||
:transition-type Gtk.RevealerTransitionType.SLIDE_DOWN
|
||||
:transition-duration 250
|
||||
:class-name "notifications"
|
||||
:child (Widget.Revealer
|
||||
:transition-type Gtk.RevealerTransitionType.SLIDE_DOWN
|
||||
:transition-duration 250
|
||||
:child layout
|
||||
:setup (fn [self]
|
||||
(.hook self self "notify::reveal-child" (fn [#* _]
|
||||
(when (not (.get-reveal-child self))
|
||||
(timeout 250 (fn [] (. self (get-parent) (set-reveal-child False)))))))))
|
||||
:setup (fn [self]
|
||||
(.hook self self "notify::reveal-child" (fn [self revealed?]
|
||||
(when revealed?
|
||||
(when (.get-reveal-child self) (timeout 1 (fn [] (. self (get-child) (set-reveal-child True)))))
|
||||
(timeout (* 1000 (or (max (.get-expire-timeout notification) 0) notification-timeout)) (fn []
|
||||
(. self (get-child) (set-reveal-child False))
|
||||
(timeout (. self (get-child) (get-transition-duration)) (fn []
|
||||
(.set-reveal-child self False)
|
||||
(timeout (.get-transition-duration self) (fn []
|
||||
(.destroy self))))))))))
|
||||
|
||||
(.set-reveal-child self True)))))]
|
||||
|
||||
(setv notifications (Widget.Window
|
||||
:namespace "notifications"
|
||||
:name "notifications"
|
||||
:anchor (| Astal.WindowAnchor.TOP Astal.WindowAnchor.RIGHT)
|
||||
:exclusivity Astal.Exclusivity.EXCLUSIVE
|
||||
:margin-top 5
|
||||
:child (Widget.Box
|
||||
:vertical True
|
||||
:setup (fn [self]
|
||||
(.hook self notifd "notified" (fn [self notification _]
|
||||
(let [children (.get-children self)]
|
||||
(.add self (make-notification (.get-notification notifd notification)))))))))))
|
@ -1,24 +0,0 @@
|
||||
const { Gio, GioUnix } = imports.gi;
|
||||
|
||||
export const resource_dial = (label, icon, timeout, command, transformer) => {
|
||||
let poll = Variable(100, {
|
||||
poll: [timeout, command, v => Number(v)]
|
||||
})
|
||||
|
||||
return Widget.Box({
|
||||
className: 'dial-parent',
|
||||
children: [
|
||||
Widget.CircularProgress({
|
||||
startAt: 0.75,
|
||||
className: 'resource-dial',
|
||||
value: poll.bind().as(v => transformer(v)),
|
||||
tooltipText: poll.bind().as(v => label(v)),
|
||||
child: Widget.Icon({
|
||||
className: 'dial-icon',
|
||||
icon: icon
|
||||
}),
|
||||
})
|
||||
]
|
||||
})
|
||||
}
|
||||
|
@ -1,14 +0,0 @@
|
||||
const systemtray = await Service.import('systemtray')
|
||||
|
||||
const SysTrayItem = item => Widget.Button({
|
||||
child: Widget.Icon().bind('icon', item, 'icon'),
|
||||
tooltipMarkup: item.bind('tooltip_markup'),
|
||||
onPrimaryClick: (_, event) => item.activate(event),
|
||||
onSecondaryClick: (_, event) => item.openMenu(event),
|
||||
});
|
||||
|
||||
export const systray = Widget.Box({
|
||||
className: 'systray',
|
||||
vertical: true,
|
||||
children: systemtray.bind('items').as(i => i.map(SysTrayItem))
|
||||
})
|
@ -1,70 +0,0 @@
|
||||
const { exec, execAsync } = Utils
|
||||
|
||||
const audio = await Service.import('audio')
|
||||
|
||||
const volume_dial = Widget.EventBox({
|
||||
className: 'eventbox-hide-pointer',
|
||||
'on-scroll-up': () => {audio.speaker.volume += 0.01},
|
||||
'on-scroll-down': () => {audio.speaker.volume -= 0.01},
|
||||
'on-primary-click': () => {audio.speaker.is_muted = !audio.speaker.is_muted},
|
||||
child: Widget.CircularProgress({
|
||||
className: 'volume-dial',
|
||||
rounded: false,
|
||||
inverted: false,
|
||||
startAt: 0.75,
|
||||
value: audio.speaker.bind('volume'),
|
||||
child: Widget.Icon({
|
||||
className: "dial-icon",
|
||||
hexpand: true,
|
||||
setup: (self) => {
|
||||
self.hook(audio, (self => {
|
||||
const vol = audio.speaker.volume * 100;
|
||||
const icon = [
|
||||
[101, 'overamplified'],
|
||||
[67, 'high'],
|
||||
[34, 'medium'],
|
||||
[1, 'low'],
|
||||
[0, 'muted'],
|
||||
].find(([threshold]) => threshold <= vol)?.[1];
|
||||
|
||||
self.icon = `audio-volume-${icon}-symbolic`;
|
||||
self.tooltip_text = `Volume ${Math.floor(vol)}%`;
|
||||
}))
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
const volume_slider = Widget.Box({
|
||||
className: 'volume',
|
||||
children: [
|
||||
Widget.Button({
|
||||
on_clicked: () => audio.speaker.is_muted = !audio.speaker.is_muted,
|
||||
child: Widget.Icon().hook(audio.speaker, self => {
|
||||
const vol = audio.speaker.volume * 100;
|
||||
const icon = [
|
||||
[101, 'overamplified'],
|
||||
[67, 'high'],
|
||||
[34, 'medium'],
|
||||
[1, 'low'],
|
||||
[0, 'muted'],
|
||||
].find(([threshold]) => threshold <= vol)?.[1];
|
||||
|
||||
self.icon = `audio-volume-${icon}-symbolic`;
|
||||
self.tooltip_text = `Volume ${Math.floor(vol)}%`;
|
||||
}),
|
||||
}),
|
||||
Widget.Slider({
|
||||
className: 'slider',
|
||||
hexpand: true,
|
||||
drawValue: false,
|
||||
onChange: ({ value }) => audio['speaker'].volume = value,
|
||||
value: audio['speaker'].bind('volume'),
|
||||
})
|
||||
]
|
||||
})
|
||||
|
||||
export {
|
||||
volume_dial,
|
||||
volume_slider
|
||||
}
|
141
windows/bar.js
141
windows/bar.js
@ -1,141 +0,0 @@
|
||||
import { battery_dial } from '../widgets/battery.js'
|
||||
import { volume_dial } from '../widgets/volume.js'
|
||||
import { brightness_dial } from '../widgets/brightness.js'
|
||||
import { hyprworkspaces } from '../widgets/hyprworkspaces.js'
|
||||
import { mpd_bar_controls } from '../widgets/mpd.js'
|
||||
// import { systray } from '../widgets/systray.js'
|
||||
|
||||
import { bar_clock} from '../widgets/clock.js'
|
||||
|
||||
import { MenuWidget } from './menu.js'
|
||||
import { reveal_launcher, launcher } from './launcher.js'
|
||||
|
||||
export const FakeBar = Widget.Window({
|
||||
name: 'FakeBar',
|
||||
exclusivity: 'exclusive',
|
||||
anchor: ['left'],
|
||||
margins: [0, 35],
|
||||
child: Widget.Box({css: 'min-width: 1px;'})
|
||||
})
|
||||
|
||||
let reveal_menu = Variable(false)
|
||||
let reveal_menu_button = Variable(false)
|
||||
|
||||
const MenuRevealButton = Widget.Box({
|
||||
children: [
|
||||
Widget.Button({
|
||||
vexpand: false,
|
||||
vpack: 'center',
|
||||
className: 'menu-visibility-button',
|
||||
'on-primary-click': (self) => reveal_menu.value = !reveal_menu.value,
|
||||
child: Widget.Icon({
|
||||
icon: reveal_menu.bind().as(reveal => !reveal ? 'go-next-symbolic' : 'go-previous-symbolic')
|
||||
})
|
||||
})
|
||||
]
|
||||
})
|
||||
|
||||
const BarTopWidget = Widget.Box({
|
||||
vertical: true,
|
||||
vpack: 'start',
|
||||
children: [
|
||||
Widget.Box({
|
||||
className: 'dial-container',
|
||||
vertical: true,
|
||||
spacing: 8,
|
||||
children: [
|
||||
battery_dial,
|
||||
volume_dial,
|
||||
brightness_dial
|
||||
]
|
||||
}),
|
||||
Widget.Box({
|
||||
css: 'margin-left: 35px; margin-right: 35px'
|
||||
})
|
||||
]
|
||||
})
|
||||
|
||||
const BarMiddleWidget = Widget.Box({
|
||||
children: [
|
||||
Widget.EventBox({
|
||||
'on-hover': () => reveal_menu_button.value = true,
|
||||
child: Widget.Box({
|
||||
css: 'min-width: 1px; min-height: 40px;'
|
||||
})
|
||||
}),
|
||||
hyprworkspaces
|
||||
]
|
||||
})
|
||||
|
||||
const BarEndWidget = Widget.Box({
|
||||
vertical: true,
|
||||
vpack: 'end',
|
||||
children: [
|
||||
// systray,
|
||||
mpd_bar_controls,
|
||||
bar_clock
|
||||
]
|
||||
})
|
||||
|
||||
const BarWidget = Widget.CenterBox({
|
||||
homogeneous: false,
|
||||
vertical: true,
|
||||
spacing: 10,
|
||||
startWidget: BarTopWidget,
|
||||
centerWidget: BarMiddleWidget,
|
||||
endWidget: BarEndWidget
|
||||
})
|
||||
|
||||
const MenuButtonRevealer = Widget.Revealer({
|
||||
revealChild: reveal_menu_button.bind(),
|
||||
transition: 'slide_right',
|
||||
child: MenuRevealButton
|
||||
})
|
||||
|
||||
const MenuRevealer = Widget.Revealer({
|
||||
revealChild: reveal_menu.bind(),
|
||||
transition: 'slide_right',
|
||||
transitionDuration: 500,
|
||||
child: MenuWidget,
|
||||
})
|
||||
|
||||
const LauncherRevealer = Widget.Revealer({
|
||||
revealChild: reveal_launcher.bind(),
|
||||
transition: 'slide_right',
|
||||
transitionDuration: 500,
|
||||
child: launcher,
|
||||
})
|
||||
|
||||
export const Bar = Widget.Window({
|
||||
name: 'status-bar',
|
||||
exclusivity: 'ignore',
|
||||
keymode: reveal_launcher.bind().as(v => v ? 'exclusive' : 'on-demand'),
|
||||
anchor: ['top', 'left', 'bottom'],
|
||||
child: Widget.Overlay({
|
||||
'pass-through': true,
|
||||
overlay: Widget.Box({
|
||||
children: [
|
||||
Widget.EventBox({
|
||||
'on-hover': () => reveal_menu_button.value = true,
|
||||
'on-hover-lost': () => reveal_menu_button.value = false,
|
||||
child: Widget.Box({
|
||||
css: 'margin-right: 4px;',
|
||||
children: [MenuButtonRevealer]
|
||||
})
|
||||
}),
|
||||
]
|
||||
}),
|
||||
child: Widget.Box({
|
||||
children: [
|
||||
LauncherRevealer,
|
||||
MenuRevealer,
|
||||
BarWidget
|
||||
]
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
export {
|
||||
reveal_menu,
|
||||
reveal_launcher
|
||||
}
|
@ -1,93 +0,0 @@
|
||||
const Applications = await Service.import("applications")
|
||||
|
||||
const reveal_launcher = Variable(false)
|
||||
|
||||
function appItem(app) {
|
||||
return Widget.Button({
|
||||
className: 'entry',
|
||||
onClicked: () => {
|
||||
reveal_launcher.value = false
|
||||
app.launch()
|
||||
},
|
||||
attribute: { app },
|
||||
child: Widget.Box([
|
||||
Widget.Icon({
|
||||
icon: app.icon_name || '',
|
||||
size: 42,
|
||||
}),
|
||||
Widget.Label({
|
||||
className: 'app-title',
|
||||
label: app.name,
|
||||
xalign: 0,
|
||||
vpack: 'center',
|
||||
truncate: 'end'
|
||||
})
|
||||
])
|
||||
})
|
||||
}
|
||||
|
||||
function _launcher() {
|
||||
let applications = Applications.query('').map(appItem)
|
||||
|
||||
const list = Widget.Box({
|
||||
vertical: true,
|
||||
children: applications,
|
||||
spacing: 12
|
||||
})
|
||||
|
||||
function repopulate() {
|
||||
applications = Applications.query('').map(appItem)
|
||||
list.children = applications
|
||||
}
|
||||
|
||||
const entry = Widget.Box({
|
||||
className: 'search',
|
||||
children: [
|
||||
Widget.Icon({
|
||||
icon: 'edit-find-symbolic',
|
||||
size: 42,
|
||||
}),
|
||||
Widget.Entry({
|
||||
hexpand: true,
|
||||
className: 'search',
|
||||
on_accept: () => {
|
||||
applications.filter((item) => item.visible)[0]?.attribute.app.launch()
|
||||
reveal_launcher.value = false
|
||||
},
|
||||
on_change: ({ text }) => applications.forEach(item => {
|
||||
item.visible = item.attribute.app.match(text ?? '')
|
||||
})
|
||||
})
|
||||
]
|
||||
})
|
||||
|
||||
return Widget.Box({
|
||||
vertical: true,
|
||||
className: 'launcher',
|
||||
children: [
|
||||
entry,
|
||||
Widget.Scrollable({
|
||||
hscroll: 'never',
|
||||
vexpand: true,
|
||||
hexpand: true,
|
||||
child: list
|
||||
})
|
||||
],
|
||||
setup: self => self.hook(reveal_launcher, () => {
|
||||
entry.text = ''
|
||||
if (reveal_launcher.value) {
|
||||
repopulate()
|
||||
console.log('nya')
|
||||
entry.text = ''
|
||||
entry.grab_focus()
|
||||
}
|
||||
}, "changed")
|
||||
})
|
||||
}
|
||||
|
||||
const launcher = _launcher()
|
||||
|
||||
export {
|
||||
reveal_launcher,
|
||||
launcher
|
||||
}
|
@ -1,76 +0,0 @@
|
||||
import { show_notification_popups } from './notifications.js'
|
||||
|
||||
// TODO: const { Gdk, GtkSessionLock } = imports.gi;
|
||||
const { authenticateUser, exec } = Utils
|
||||
|
||||
export const show_lock = Variable(false)
|
||||
const lock_ready = Variable(false)
|
||||
|
||||
const username = Variable()
|
||||
|
||||
const password_entry = Widget.Entry({
|
||||
visibility: false,
|
||||
xalign: 0.5,
|
||||
onAccept: (self) => {
|
||||
authenticateUser(username.value, self.text)
|
||||
.then(() => {
|
||||
show_lock.value = false
|
||||
exec('rm /tmp/lock-pixelated.png')
|
||||
show_notification_popups.value = true
|
||||
})
|
||||
.catch(() => self.text = '')
|
||||
self.text = ''
|
||||
username_entry.text = ''
|
||||
username_entry.grab_focus()
|
||||
},
|
||||
setup: self => self.text = ''
|
||||
})
|
||||
|
||||
const username_entry = Widget.Entry({
|
||||
xalign: 0.5,
|
||||
onAccept: (self) => {
|
||||
username.value = self.text
|
||||
password_entry.grab_focus()
|
||||
},
|
||||
})
|
||||
|
||||
const login_container = Widget.Box({
|
||||
child: Widget.Box({
|
||||
className: lock_ready.bind().as(b => b ? 'lock-ready' : 'lock'),
|
||||
vpack: 'center',
|
||||
hpack: 'center',
|
||||
hexpand: 'true',
|
||||
vertical: true,
|
||||
child: Widget.Box({
|
||||
vertical: true,
|
||||
children: [
|
||||
Widget.Box({className: 'img'}),
|
||||
Widget.Box({
|
||||
vertical: true,
|
||||
children: [
|
||||
username_entry,
|
||||
password_entry
|
||||
]
|
||||
})
|
||||
]
|
||||
})
|
||||
}),
|
||||
setup: self => self.hook(show_lock, () => {
|
||||
self.css = 'min-width: 2560px; min-height: 1600px; background: url("/tmp/lock-pixelated.png");'
|
||||
lock_ready.value = true
|
||||
})
|
||||
})
|
||||
|
||||
export const Lock = Widget.Window({
|
||||
name: 'lock',
|
||||
visible: show_lock.bind().as(b => {
|
||||
if (b) {
|
||||
exec(`bash -c "grim - | convert - -scale 12.5% -scale 800% -filter point /tmp/lock-pixelated.png"`)
|
||||
show_notification_popups.value = false
|
||||
}
|
||||
return b
|
||||
}),
|
||||
exclusivity: 'ignore',
|
||||
keymode: 'exclusive',
|
||||
child: login_container,
|
||||
})
|
@ -1,49 +0,0 @@
|
||||
import { mpd_menu_controls } from '../widgets/mpd.js'
|
||||
import { resource_dial } from '../widgets/resourceDial.js'
|
||||
|
||||
export const MenuWidget = Widget.Box({
|
||||
vertical: true,
|
||||
className: 'Menu',
|
||||
children: [
|
||||
mpd_menu_controls,
|
||||
Widget.Box({
|
||||
children: [
|
||||
resource_dial(
|
||||
(v) => `cpu: ${v}%`,
|
||||
'microchip-solid',
|
||||
1000,
|
||||
"bash -c \"top -bn1 | grep 'Cpu(s)' | awk '{print \$2 + \$4}'\"",
|
||||
v => v/100
|
||||
),
|
||||
resource_dial(
|
||||
(v) => `mem: ${Math.round(100*v/31396)}%`,
|
||||
'memory-solid',
|
||||
1000,
|
||||
"bash -c \"free -m | grep Mem | awk '{print $3}'\"",
|
||||
v => v/31396
|
||||
),
|
||||
resource_dial(
|
||||
(v) => `igpu: ${v}%`,
|
||||
'expansion-card',
|
||||
1000,
|
||||
"bash -c \"cat /sys/class/drm/card1/device/gpu_busy_percent\"",
|
||||
v => v/100
|
||||
),
|
||||
resource_dial(
|
||||
(v) => `fan: ${v}rpm`,
|
||||
'fan',
|
||||
1000,
|
||||
"bash -c \"sudo ectool pwmgetfanrpm | awk '{a+=\$4} END {print a/2}'\"",
|
||||
v => v/5000
|
||||
),
|
||||
resource_dial(
|
||||
(v) => `temp (cpu): ${v}`,
|
||||
'thermometer',
|
||||
1000,
|
||||
"bash -c \"sudo ectool temps all | grep -E 'cpu' | awk '{print \$5}'\"",
|
||||
v => v/100
|
||||
),
|
||||
]
|
||||
})
|
||||
]
|
||||
})
|
@ -1,185 +0,0 @@
|
||||
const Notifications = await Service.import('notifications')
|
||||
const { Pango } = imports.gi;
|
||||
|
||||
Notifications.popupTimeout = 3000;
|
||||
|
||||
function notification_icon({ app_entry, app_icon, image }) {
|
||||
if (image) {
|
||||
return Widget.Box({
|
||||
css: `background-image: url("${image}");`
|
||||
+ 'background-size: contain;'
|
||||
+ 'background-repeat: no-repeat;'
|
||||
+ 'background-position: center;',
|
||||
})
|
||||
}
|
||||
|
||||
let icon = 'dialog-information-symbolic'
|
||||
if (Utils.lookUpIcon(app_icon))
|
||||
icon = app_icon
|
||||
|
||||
if (app_entry && Utils.lookUpIcon(app_entry))
|
||||
icon = app_entry
|
||||
|
||||
return Widget.Box({
|
||||
child: Widget.Icon(icon),
|
||||
})
|
||||
}
|
||||
|
||||
const notification = (n) => {
|
||||
const icon = Widget.Box({
|
||||
vpack: 'center',
|
||||
hpack: 'start',
|
||||
class_name: 'icon',
|
||||
child: notification_icon(n),
|
||||
})
|
||||
|
||||
const title = Widget.Label({
|
||||
className: 'title',
|
||||
label: n.summary,
|
||||
xalign: 0,
|
||||
maxWidthChars: 40,
|
||||
justify: 'left',
|
||||
})
|
||||
|
||||
const body = Widget.Label({
|
||||
className: 'body',
|
||||
label: n.body,
|
||||
xalign: 0,
|
||||
justification: 'left',
|
||||
maxWidthChars: 26,
|
||||
wrap: true,
|
||||
wrapMode: Pango.WrapMode.WORD_CHAR,
|
||||
useMarkup: true,
|
||||
})
|
||||
|
||||
const actions = Widget.Box({
|
||||
class_name: "actions",
|
||||
children: n.actions.map(({ id, label }) => Widget.Button({
|
||||
class_name: "action-button",
|
||||
on_clicked: () => {
|
||||
n.invoke(id)
|
||||
n.dismiss()
|
||||
},
|
||||
hexpand: true,
|
||||
child: Widget.Label(label),
|
||||
})),
|
||||
})
|
||||
|
||||
// const buttons = Widget.Box({
|
||||
// hpack: 'end',
|
||||
// children: [
|
||||
// Widget.Button({
|
||||
// className: 'button',
|
||||
// onClicked: () => n.dismiss(),
|
||||
// child: Widget.Label('')
|
||||
// })
|
||||
// ]
|
||||
// })
|
||||
|
||||
const timeout_progress = Widget.ProgressBar({
|
||||
className: 'timeout-bar',
|
||||
hexpand: true,
|
||||
vpack: 'end',
|
||||
value: 1,
|
||||
setup: (self) => {
|
||||
self.poll(n.timeout/100, () => {
|
||||
if (self.value > 0.01) {
|
||||
self.value = self.value - .01
|
||||
}
|
||||
else { n.dismiss() } // Notifications.forceTimeout doesn't work for notifications that are bugged.
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
const layout = Widget.Button({
|
||||
child: Widget.Box({
|
||||
children: [
|
||||
Widget.Box({
|
||||
className: 'notification',
|
||||
vertical: true,
|
||||
children: [
|
||||
Widget.Box([
|
||||
icon,
|
||||
Widget.Box({
|
||||
vertical: true,
|
||||
children: [
|
||||
title, body
|
||||
]
|
||||
})
|
||||
]),
|
||||
actions,
|
||||
timeout_progress
|
||||
]
|
||||
}),
|
||||
Widget.Box({
|
||||
vexpand: false,
|
||||
classNames: ['urgency-indicator', n.urgency]
|
||||
})
|
||||
]
|
||||
}),
|
||||
onPrimaryClick: n.dismiss,
|
||||
})
|
||||
|
||||
return Widget.Revealer({
|
||||
className: 'revealer',
|
||||
attribute: { id: n.id },
|
||||
vpack: 'start',
|
||||
transition: 'slide_down',
|
||||
transitionDuration: 250,
|
||||
setup: (self) => {
|
||||
Utils.timeout(1, () => self.set_reveal_child(true));
|
||||
self.on('notify::reveal-child', () => {
|
||||
if (self.reveal_child) Utils.timeout(125, () => self.child.set_reveal_child(true))
|
||||
})
|
||||
},
|
||||
child: Widget.Revealer({
|
||||
className: 'revealer',
|
||||
hpack: 'end',
|
||||
transition: 'slide_left',
|
||||
transitionDuration: 250,
|
||||
setup: (self) => self.on('notify::reveal-child', () => {
|
||||
if (!self.reveal_child) Utils.timeout(250, () => self.parent.set_reveal_child(false))
|
||||
}),
|
||||
child: layout
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
export const show_notification_popups = Variable(true)
|
||||
export const NotificationPopups = Widget.Window({
|
||||
visible: show_notification_popups.bind(),
|
||||
name: 'notifications',
|
||||
anchor: ['top', 'right'],
|
||||
layer: 'overlay',
|
||||
margins: [15, 0, 0, 0],
|
||||
child: Widget.Box({
|
||||
className: 'notifications',
|
||||
vertical: true,
|
||||
widthRequest: 2,
|
||||
heightRequest: 2,
|
||||
children: Notifications.popups.map(notification),
|
||||
setup: (self) => self.hook(Notifications, (_, id) => {
|
||||
if (!id || Notifications.dnd) return;
|
||||
|
||||
const n = Notifications.getNotification(id)
|
||||
|
||||
if (!n) return;
|
||||
|
||||
self.children = [...self.children, notification(n)]
|
||||
}, 'notified')
|
||||
.hook(Notifications, (_, id) => {
|
||||
if (!id) return;
|
||||
|
||||
const n = self.children.find(
|
||||
(child) => child.attribute.id === id,
|
||||
)
|
||||
|
||||
if (!n) return;
|
||||
|
||||
n.child.set_reveal_child(false)
|
||||
|
||||
Utils.timeout(n.child.transition_duration, () => n.destroy())
|
||||
}, 'dismissed')
|
||||
})
|
||||
})
|
||||
|
@ -1,70 +0,0 @@
|
||||
import { hyprworkspaces_grid } from '../widgets/hyprworkspaces.js'
|
||||
import { volume_slider } from '../widgets/volume.js'
|
||||
import { brightness_slider } from '../widgets/brightness.js'
|
||||
import { battery_dial } from '../widgets/battery.js'
|
||||
import { horizontal_clock } from '../widgets/clock.js'
|
||||
import { active_window } from '../widgets/hypractive.js'
|
||||
import { media } from '../widgets/mpris.js'
|
||||
|
||||
export const FakeBar = Widget.Window({
|
||||
name: 'FakeBar',
|
||||
exclusivity: 'exclusive',
|
||||
anchor: ['left', 'top', 'right'],
|
||||
margins: [35, 0],
|
||||
child: Widget.Box({css: 'min-height: 1px;'})
|
||||
})
|
||||
|
||||
const left_box = Widget.Box({
|
||||
className: 'left',
|
||||
hpack: 'start',
|
||||
children: [
|
||||
hyprworkspaces_grid,
|
||||
media
|
||||
]
|
||||
})
|
||||
|
||||
const middle_box = Widget.Box({
|
||||
className: 'middle',
|
||||
hpack: 'center',
|
||||
children: [
|
||||
active_window
|
||||
]
|
||||
})
|
||||
|
||||
const right_box = Widget.Box({
|
||||
className: 'right',
|
||||
hpack: 'end',
|
||||
children: [
|
||||
Widget.Box({
|
||||
className: 'sliderbox',
|
||||
vertical: true,
|
||||
children: [
|
||||
volume_slider,
|
||||
brightness_slider
|
||||
]
|
||||
}),
|
||||
Widget.Box({
|
||||
className: 'battery-container',
|
||||
children: [battery_dial]
|
||||
}),
|
||||
Widget.Separator({vertical: true}),
|
||||
horizontal_clock
|
||||
]
|
||||
})
|
||||
|
||||
const bar = Widget.Box({
|
||||
className: 'bar',
|
||||
children: [
|
||||
left_box,
|
||||
middle_box,
|
||||
right_box
|
||||
]
|
||||
})
|
||||
|
||||
export const Bar = Widget.Window({
|
||||
name: 'status-bar',
|
||||
exclusivity: 'ignore',
|
||||
// margins: [5, 5, 5, 5],
|
||||
anchor: ['left', 'top', 'right'],
|
||||
child: bar
|
||||
})
|
Loading…
x
Reference in New Issue
Block a user