Compare commits
12 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
4763fc0624 | ||
![]() |
2a8070cc08 | ||
![]() |
a304510df7 | ||
![]() |
460c2589cc | ||
![]() |
0cf8c0d102 | ||
![]() |
a263107565 | ||
![]() |
fd3d5a4844 | ||
![]() |
fbb8e6f357 | ||
![]() |
d584d25194 | ||
![]() |
784e71e643 | ||
![]() |
3f85d85da3 | ||
![]() |
9d44a0ef14 |
2
.gitignore
vendored
2
.gitignore
vendored
@ -1,2 +0,0 @@
|
||||
__pycache__
|
||||
style/style.css
|
19
init.lua
Normal file
19
init.lua
Normal file
@ -0,0 +1,19 @@
|
||||
require("lua.globals")
|
||||
|
||||
local bar = require('lua.widgets.bar')
|
||||
local notifications = require('lua.widgets.notifications')
|
||||
|
||||
local scss = "./style/style.scss"
|
||||
local css = "./style/style.css"
|
||||
|
||||
exec(string.format("sassc %s %s", scss, css))
|
||||
|
||||
exec('pkill -f /usr/bin/mpDris2; mpDris2')
|
||||
|
||||
App:start({
|
||||
css = css,
|
||||
main = function()
|
||||
bar:show_all()
|
||||
notifications:show_all()
|
||||
end,
|
||||
})
|
14
lua/globals.lua
Normal file
14
lua/globals.lua
Normal file
@ -0,0 +1,14 @@
|
||||
lgi = require('lgi')
|
||||
|
||||
astal = require("astal.gtk3")
|
||||
|
||||
Astal = astal.Astal
|
||||
Widget = astal.Widget
|
||||
App = astal.App
|
||||
Gtk = astal.Gtk
|
||||
|
||||
bind = require'astal'.bind
|
||||
read_file = require'astal'.read_file
|
||||
exec = require'astal'.exec
|
||||
|
||||
Variable = require('astal.variable')
|
23
lua/lib/init.lua
Normal file
23
lua/lib/init.lua
Normal file
@ -0,0 +1,23 @@
|
||||
local M = {}
|
||||
|
||||
function M.map(tbl, f)
|
||||
local t = {}
|
||||
for k,v in pairs(tbl) do
|
||||
t[k] = f(v)
|
||||
end
|
||||
return t
|
||||
end
|
||||
|
||||
function M.sequence(start, stop)
|
||||
local t = {}
|
||||
for i=start,stop do
|
||||
table.insert(t, i)
|
||||
end
|
||||
return t
|
||||
end
|
||||
|
||||
function ternary(cond, t, f)
|
||||
if cond then return t else return f end
|
||||
end
|
||||
|
||||
return M
|
33
lua/widgets/bar/battery.lua
Normal file
33
lua/widgets/bar/battery.lua
Normal file
@ -0,0 +1,33 @@
|
||||
local Battery <const> = lgi.require("AstalBattery")
|
||||
|
||||
local battery <const> = Battery.get_default()
|
||||
|
||||
local icons <const> = {
|
||||
{"", "", "", "", "", "", "", "", "", "", ""},
|
||||
{"", "", "", "", "", "", "", "", "", "", ""},
|
||||
};
|
||||
|
||||
return 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 = "CENTER",
|
||||
hexpand = true,
|
||||
justify = 2,
|
||||
setup = function(self)
|
||||
self:hook(battery, 'notify::percentage', function(self, percentage)
|
||||
self.label = icons[battery:get_charging() and 1 or 2][math.floor(percentage*10)]
|
||||
end)
|
||||
self.label = icons[battery:get_charging() and 2 or 1][math.floor(battery:get_percentage()*10)]
|
||||
end
|
||||
})
|
||||
})
|
||||
}
|
||||
})
|
18
lua/widgets/bar/clock.lua
Normal file
18
lua/widgets/bar/clock.lua
Normal file
@ -0,0 +1,18 @@
|
||||
local datetime = Variable(0):poll(1000, "date +'%d %b %H:%M:%S'", function(out, _) return out end)
|
||||
local unix_seconds = Variable(0):poll(1000, "date +'%s'", function(out, _) return out end)
|
||||
|
||||
return Widget.Box({
|
||||
class_name = 'clock',
|
||||
vertical = true,
|
||||
valign = 'CENTER',
|
||||
children = {
|
||||
Widget.Label({
|
||||
halign = 'START',
|
||||
label = bind(datetime, 'value')
|
||||
}),
|
||||
Widget.Label({
|
||||
halign = 'START',
|
||||
label = bind(unix_seconds, 'value')
|
||||
})
|
||||
}
|
||||
})
|
40
lua/widgets/bar/init.lua
Normal file
40
lua/widgets/bar/init.lua
Normal file
@ -0,0 +1,40 @@
|
||||
local workspaces <const> = require(... .. '.workspaces')
|
||||
local clock <const> = require(... .. '.clock')
|
||||
local battery <const> = require(... .. '.battery')
|
||||
local volume <const> = require(... .. '.volume')
|
||||
local mpris <const> = require(... .. '.mpris')
|
||||
-- local brightness <const> = require(... .. '.brightness')
|
||||
|
||||
return Astal.Window({
|
||||
namespace = "bar",
|
||||
name = "bar",
|
||||
anchor = Astal.WindowAnchor.TOP + Astal.WindowAnchor.LEFT + Astal.WindowAnchor.RIGHT,
|
||||
exclusivity = "EXCLUSIVE",
|
||||
child = Widget.CenterBox({
|
||||
start_widget = Widget.Box({
|
||||
class_name = 'left',
|
||||
children = {
|
||||
workspaces,
|
||||
mpris,
|
||||
}
|
||||
}),
|
||||
center_widget = Widget.Box({}),
|
||||
end_widget = Widget.Box({
|
||||
class_name = 'right',
|
||||
halign = 'END',
|
||||
children = {
|
||||
Widget.Box({
|
||||
class_name = 'sliders',
|
||||
vertical = true,
|
||||
children = {
|
||||
volume,
|
||||
brightness
|
||||
}
|
||||
}),
|
||||
battery,
|
||||
Gtk.Separator({}),
|
||||
clock
|
||||
}
|
||||
}),
|
||||
})
|
||||
})
|
108
lua/widgets/bar/mpris.lua
Normal file
108
lua/widgets/bar/mpris.lua
Normal file
@ -0,0 +1,108 @@
|
||||
local Mpris <const> = lgi.require("AstalMpris")
|
||||
local mpris = Mpris.get_default()
|
||||
local map = require('lua.lib').map
|
||||
|
||||
return Widget.Stack({
|
||||
transition_type = 'SLIDE_UP_DOWN',
|
||||
transition_duration = 125,
|
||||
children = {},
|
||||
setup = function(self)
|
||||
self:add_events(Gdk.EventMask.SCROLL_MASK);
|
||||
self:add_events(Gdk.EventMask.SMOOTH_SCROLL_MASK);
|
||||
|
||||
local function add_player(player)
|
||||
self:add_named(
|
||||
Widget.Box({
|
||||
class_name = 'player',
|
||||
vertical = true,
|
||||
hexpand = false,
|
||||
setup = function(self) self['name'] = player:get_bus_name() end,
|
||||
css = bind(player, 'cover-art'):as(
|
||||
function(coverart)
|
||||
if coverart then
|
||||
return
|
||||
'background: linear-gradient(to right, rgba(0, 0, 0, 0), rgba(22, 22, 22, 0.9)), url("' .. coverart .. '");'
|
||||
.. 'background-position: 50% 50%; background-size: cover'
|
||||
else
|
||||
return ''
|
||||
end
|
||||
end),
|
||||
children = {
|
||||
Widget.Box({
|
||||
vertical = true,
|
||||
hexpand = true,
|
||||
valign = 'CENTER',
|
||||
halign = 'END',
|
||||
children = {
|
||||
Widget.Button({
|
||||
on_click = function() player:previous() end,
|
||||
child = Widget.Icon({ icon = 'media-skip-backward-symbolic' })
|
||||
}),
|
||||
Widget.Button({
|
||||
on_click = function() player:play_pause() end,
|
||||
child = Widget.Icon({
|
||||
icon = bind(player, 'playback-status'):as(function(status)
|
||||
return (player:get_playback_status() == 'PLAYING' and 'media-playback-pause-symbolic' or 'media-playback-start-symbolic')
|
||||
end)
|
||||
})
|
||||
}),
|
||||
Widget.Button({
|
||||
on_click = function() player:next() end,
|
||||
child = Widget.Icon({ icon = 'media-skip-forward-symbolic' })
|
||||
})
|
||||
}
|
||||
}),
|
||||
Widget.Slider({
|
||||
hexpand = true,
|
||||
class_name = 'music-progress',
|
||||
valign = 'END',
|
||||
halign = 'START',
|
||||
-- for some reason, this is broken
|
||||
on_change_value = function(self) player:set_position(self:get_value() * player:get_length()) end,
|
||||
setup = function(self)
|
||||
Variable():poll(500, function()
|
||||
if player == nil then return end
|
||||
self:set_value(player:get_position() / player:get_length())
|
||||
end)
|
||||
end,
|
||||
})
|
||||
}
|
||||
}),
|
||||
player:get_bus_name()
|
||||
)
|
||||
end
|
||||
|
||||
map(mpris:get_players(), add_player)
|
||||
|
||||
self:hook(mpris, 'player-added', function(self, player)
|
||||
add_player(player)
|
||||
end)
|
||||
|
||||
self:hook(mpris, 'player-closed', function(self, player)
|
||||
print('remove player', player:get_bus_name())
|
||||
self:get_named(player:get_bus_name()):destroy()
|
||||
end)
|
||||
|
||||
local accumulated_delta_y = 0
|
||||
|
||||
self:hook(self, 'scroll-event', function(_, event)
|
||||
local children = self:get_children()
|
||||
local current_child = self:get_visible_child()
|
||||
local _, delta_y = event:get_scroll_deltas()
|
||||
local index
|
||||
|
||||
accumulated_delta_y = accumulated_delta_y + delta_y
|
||||
|
||||
for i, child in ipairs(children) do
|
||||
if child == current_child then
|
||||
index = i
|
||||
end
|
||||
end
|
||||
|
||||
if math.abs(accumulated_delta_y) > 10 then
|
||||
self:set_visible_child(children[(index + math.floor(accumulated_delta_y / 10)) % #children + 1])
|
||||
accumulated_delta_y = 0
|
||||
end
|
||||
end)
|
||||
end
|
||||
})
|
26
lua/widgets/bar/volume.lua
Normal file
26
lua/widgets/bar/volume.lua
Normal file
@ -0,0 +1,26 @@
|
||||
local wp <const> = lgi.require("AstalWp").get_default()
|
||||
|
||||
local endpoint = wp:get_audio():get_default_speaker()
|
||||
|
||||
return Widget.Box({
|
||||
class_name = 'volume-slider',
|
||||
children = {
|
||||
Widget.Button({
|
||||
child = Widget.Icon({
|
||||
icon = bind(endpoint, "volume-icon"),
|
||||
}),
|
||||
on_clicked = function()
|
||||
endpoint:set_mute(not endpoint:get_mute())
|
||||
end
|
||||
}),
|
||||
Widget.Slider({
|
||||
class_name = 'volume-slider',
|
||||
hexpand = true,
|
||||
draw_value = false,
|
||||
value = bind(endpoint, 'volume'),
|
||||
on_value_changed = function(self)
|
||||
endpoint:set_volume(self.value)
|
||||
end
|
||||
})
|
||||
}
|
||||
})
|
54
lua/widgets/bar/workspaces.lua
Normal file
54
lua/widgets/bar/workspaces.lua
Normal file
@ -0,0 +1,54 @@
|
||||
local Hyprland <const> = lgi.require("AstalHyprland")
|
||||
local map, sequence = require('lua.lib').map, require('lua.lib').sequence
|
||||
|
||||
local hypr = Hyprland.get_default()
|
||||
|
||||
local function workspace_row(start, stop)
|
||||
return Widget.Box({
|
||||
children = map(sequence(start, stop), function(i)
|
||||
return Widget.Button({
|
||||
setup = function(self)
|
||||
self:hook(hypr, 'notify::focused-workspace', function(self, workspace)
|
||||
self:toggle_class_name('focused', workspace:get_id() == i)
|
||||
end)
|
||||
|
||||
local function update()
|
||||
local workspaces = hypr:get_workspaces()
|
||||
for _,workspace in ipairs(workspaces) do
|
||||
if workspace:get_id() == i then
|
||||
local count = 0;
|
||||
|
||||
for _ in pairs(workspace:get_clients()) do
|
||||
count = count + 1
|
||||
end
|
||||
|
||||
self:toggle_class_name('occupied', count > 0)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
self:hook(hypr, 'client-moved', update)
|
||||
self:hook(hypr, 'notify::clients', update)
|
||||
self:hook(hypr, 'notify::workspaces', update)
|
||||
|
||||
update()
|
||||
end,
|
||||
on_click_release = function()
|
||||
hypr:message_async(string.format("dispatch workspace %d", i))
|
||||
end
|
||||
})
|
||||
end)
|
||||
})
|
||||
end
|
||||
|
||||
return Widget.Box({
|
||||
class_name = 'workspaces',
|
||||
vertical = true,
|
||||
hexpand = false,
|
||||
halign = 'START',
|
||||
valign = 'CENTER',
|
||||
children = {
|
||||
workspace_row(1, 5),
|
||||
workspace_row(6, 10),
|
||||
}
|
||||
})
|
129
lua/widgets/notifications/init.lua
Normal file
129
lua/widgets/notifications/init.lua
Normal file
@ -0,0 +1,129 @@
|
||||
local notifd = lgi.require('AstalNotifd').get_default()
|
||||
local pango = lgi.require('Pango')
|
||||
local map = require("lua.lib").map
|
||||
local popup_timeout_seconds = 3
|
||||
|
||||
local notification_icon = function(n)
|
||||
local icon = 'dialog-information-symbolic'
|
||||
|
||||
if n.image then
|
||||
return Widget.Icon({
|
||||
class_name = 'icon image',
|
||||
css = 'background-image: url("' .. n.image .. '");'
|
||||
.. 'background-size: contain;'
|
||||
.. 'background-repeat: no-repeat;'
|
||||
.. 'background-position: center;'
|
||||
})
|
||||
elseif lookup_icon(n.app_icon) then
|
||||
icon = n.app_icon
|
||||
end
|
||||
return Widget.Icon({ icon = icon, class_name = 'icon' })
|
||||
end
|
||||
|
||||
local make_notification = function(n)
|
||||
local layout = Widget.Button({
|
||||
on_clicked = function(self)
|
||||
self:get_parent():set_reveal_child(false)
|
||||
end,
|
||||
child = Widget.Box({
|
||||
children = {
|
||||
Widget.Box({
|
||||
vertical = true,
|
||||
css = 'min-width: 200px; min-height: 50px;',
|
||||
children = {
|
||||
Widget.Box({
|
||||
children = {
|
||||
notification_icon(n),
|
||||
Widget.Box({
|
||||
vertical = true,
|
||||
children = {
|
||||
Widget.Label({
|
||||
class_name = 'title',
|
||||
label = n.summary,
|
||||
xalign = 0,
|
||||
justify = 0,
|
||||
ellipsize = pango.EllipsizeMode.END,
|
||||
}),
|
||||
Widget.Label({
|
||||
class_name = 'body',
|
||||
label = n.body,
|
||||
xalign = 0,
|
||||
justify = 0,
|
||||
wrap = true,
|
||||
wrap_mode = pango.WrapMode.WORD_CHAR,
|
||||
use_markup = true
|
||||
})
|
||||
}
|
||||
}),
|
||||
}
|
||||
}),
|
||||
Widget.ProgressBar({
|
||||
class_name = 'timeout-bar',
|
||||
hexpand = true,
|
||||
valign = 2,
|
||||
fraction = bind(Variable(1):poll(((n.expire_timeout > 0 and n.expire_timeout or popup_timeout_seconds) * 1000 + 250) // 100, function(prev)
|
||||
if prev > .01 then
|
||||
return prev - .01
|
||||
end
|
||||
return 0
|
||||
end)),
|
||||
})
|
||||
}
|
||||
}),
|
||||
Widget.Box({
|
||||
class_name = 'urgency-indicator ' .. n.urgency
|
||||
})
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
return Widget.Revealer({
|
||||
transition_type = 'SLIDE_DOWN',
|
||||
transition_duration = 250,
|
||||
class_name = 'notifications',
|
||||
child = Widget.Revealer({
|
||||
transition_type = 'SLIDE_DOWN',
|
||||
transition_duration = 250,
|
||||
child = layout,
|
||||
setup = function(self)
|
||||
self:hook(self, 'notify::reveal-child', function()
|
||||
if not self.reveal_child then timeout(250, function() self:get_parent():set_reveal_child(false) end) end
|
||||
end)
|
||||
end
|
||||
}),
|
||||
setup = function(self)
|
||||
self:hook(self, 'notify::reveal-child', function()
|
||||
if self.reveal_child then timeout(1, function() self:get_child():set_reveal_child(true) end) end
|
||||
timeout((n.expire_timeout > 0 and n.expire_timeout or popup_timeout_seconds) * 1000, function()
|
||||
self:get_child():set_reveal_child(false)
|
||||
timeout(self:get_child():get_transition_duration(), function()
|
||||
self:set_reveal_child(false)
|
||||
timeout(self:get_transition_duration(), function()
|
||||
self:destroy()
|
||||
end)
|
||||
end)
|
||||
end)
|
||||
end)
|
||||
timeout(0, function() self.reveal_child = true end)
|
||||
end
|
||||
})
|
||||
end
|
||||
|
||||
return Widget.Window({
|
||||
namespace = 'notifications',
|
||||
name = 'notifications',
|
||||
anchor = Astal.WindowAnchor.TOP | Astal.WindowAnchor.RIGHT,
|
||||
exclusivity = 'EXCLUSIVE',
|
||||
margin_top = 5,
|
||||
child = Widget.Box({
|
||||
vertical = true,
|
||||
setup = function(self)
|
||||
self:hook(notifd, 'notified', function(self, n)
|
||||
local children = self:get_children()
|
||||
table.insert(children, make_notification(notifd:get_notification(n)))
|
||||
self:set_children(children)
|
||||
print(children)
|
||||
end)
|
||||
end
|
||||
})
|
||||
})
|
26
main.hy
26
main.hy
@ -1,26 +0,0 @@
|
||||
(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")
|
@ -1,8 +0,0 @@
|
||||
# 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,52 +0,0 @@
|
||||
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
|
65
style/style.css
Normal file
65
style/style.css
Normal file
@ -0,0 +1,65 @@
|
||||
* {
|
||||
all: unset; }
|
||||
|
||||
bar {
|
||||
background: transparent; }
|
||||
|
||||
centerbox > box.left box.workspaces {
|
||||
min-height: 50px;
|
||||
background: #161616;
|
||||
padding: 5px;
|
||||
padding-top: 15px; }
|
||||
centerbox > box.left box.workspaces box > button {
|
||||
min-width: 10px;
|
||||
min-height: 10px;
|
||||
margin: 5px;
|
||||
background-color: #262626; }
|
||||
centerbox > box.left box.workspaces box > button.occupied {
|
||||
background-color: #393939; }
|
||||
centerbox > box.left box.workspaces box > button.focused {
|
||||
background-color: #FF7EB6; }
|
||||
|
||||
centerbox > box.right {
|
||||
min-height: 50px;
|
||||
background: #161616; }
|
||||
centerbox > box.right box.sliders {
|
||||
margin: 5px; }
|
||||
centerbox > box.right box.sliders box.volume-slider > button {
|
||||
font-size: 20px;
|
||||
margin: 5px; }
|
||||
centerbox > box.right box.sliders box.volume-slider > .volume-slider {
|
||||
/* selecting this as `slider` doesn't work, for some reason.*/
|
||||
min-width: 120px;
|
||||
min-height: 10px; }
|
||||
centerbox > box.right box.sliders box.volume-slider > .volume-slider trough {
|
||||
min-height: 10px;
|
||||
min-width: 120px;
|
||||
background: #262626; }
|
||||
centerbox > box.right box.sliders box.volume-slider > .volume-slider highlight {
|
||||
background: #33B1FF; }
|
||||
centerbox > box.right .battery-container {
|
||||
padding-left: 10px;
|
||||
padding-right: 10px; }
|
||||
centerbox > box.right .battery-container .battery-dial {
|
||||
min-width: 40px;
|
||||
min-height: 40px;
|
||||
color: #42be65;
|
||||
background-color: #161616;
|
||||
margin: 0px;
|
||||
padding: 0px;
|
||||
font-size: 5px; }
|
||||
centerbox > box.right .battery-container .battery-dial label, centerbox > box.right .battery-container .battery-dial icon {
|
||||
font-family: 'Symbols Nerd Font Mono';
|
||||
font-size: 16px;
|
||||
color: #f2f4f8; }
|
||||
centerbox > box.right separator {
|
||||
background: #262626;
|
||||
padding: 1px;
|
||||
margin-top: 10px;
|
||||
margin-bottom: 10px; }
|
||||
centerbox > box.right box.clock {
|
||||
margin: 10px;
|
||||
background: #161616; }
|
||||
centerbox > box.right box.clock .datetime {
|
||||
margin: 1px;
|
||||
font-size: 10pt; }
|
@ -42,7 +42,7 @@ centerbox {
|
||||
}
|
||||
}
|
||||
|
||||
> .media-progress {
|
||||
> .music-progress {
|
||||
min-width: 242px;
|
||||
min-height: 2px;
|
||||
background: transparent;
|
||||
@ -68,7 +68,7 @@ centerbox {
|
||||
margin: 5px;
|
||||
box.volume-slider {
|
||||
> button {
|
||||
font-size: 24px;
|
||||
font-size: 20px;
|
||||
margin: 5px;
|
||||
}
|
||||
|
||||
@ -86,29 +86,7 @@ centerbox {
|
||||
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 {
|
||||
@ -139,3 +117,4 @@ centerbox {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -15,7 +15,7 @@
|
||||
min-width: 64px;
|
||||
min-height: 64px;
|
||||
margin: 5px;
|
||||
font-size: 58px;
|
||||
font-size: 58px
|
||||
}
|
||||
|
||||
.timeout-bar {
|
||||
|
@ -1,2 +0,0 @@
|
||||
(import .bar [bar])
|
||||
(import .notifications [notifications])
|
@ -1,34 +0,0 @@
|
||||
(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]))))
|
@ -1,27 +0,0 @@
|
||||
(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)))))))])))
|
@ -1,22 +0,0 @@
|
||||
(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)))))])))
|
@ -1,19 +0,0 @@
|
||||
(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))])))
|
||||
|
@ -1,81 +0,0 @@
|
||||
(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))))))))
|
@ -1,22 +0,0 @@
|
||||
(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))))])))
|
@ -1,48 +0,0 @@
|
||||
(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,107 +0,0 @@
|
||||
(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)))))))))))
|
Loading…
x
Reference in New Issue
Block a user