Added ags
TODO copy ags config
This commit is contained in:
57
roles/ags/files/widget/bar/Bar.ts
Normal file
57
roles/ags/files/widget/bar/Bar.ts
Normal file
@@ -0,0 +1,57 @@
|
||||
import BatteryBar from "./buttons/BatteryBar"
|
||||
import ColorPicker from "./buttons/ColorPicker"
|
||||
import Date from "./buttons/Date"
|
||||
import Launcher from "./buttons/Launcher"
|
||||
import Media from "./buttons/Media"
|
||||
import PowerMenu from "./buttons/PowerMenu"
|
||||
import SysTray from "./buttons/SysTray"
|
||||
import SystemIndicators from "./buttons/SystemIndicators"
|
||||
import Taskbar from "./buttons/Taskbar"
|
||||
import Workspaces from "./buttons/Workspaces"
|
||||
import ScreenRecord from "./buttons/ScreenRecord"
|
||||
import Messages from "./buttons/Messages"
|
||||
import options from "options"
|
||||
|
||||
const { start, center, end } = options.bar.layout
|
||||
const pos = options.bar.position.bind()
|
||||
|
||||
export type BarWidget = keyof typeof widget
|
||||
|
||||
const widget = {
|
||||
battery: BatteryBar,
|
||||
colorpicker: ColorPicker,
|
||||
date: Date,
|
||||
launcher: Launcher,
|
||||
media: Media,
|
||||
powermenu: PowerMenu,
|
||||
systray: SysTray,
|
||||
system: SystemIndicators,
|
||||
taskbar: Taskbar,
|
||||
workspaces: Workspaces,
|
||||
screenrecord: ScreenRecord,
|
||||
messages: Messages,
|
||||
expander: () => Widget.Box({ expand: true }),
|
||||
}
|
||||
|
||||
export default (monitor: number) => Widget.Window({
|
||||
monitor,
|
||||
class_name: "bar",
|
||||
name: `bar${monitor}`,
|
||||
exclusivity: "exclusive",
|
||||
anchor: pos.as(pos => [pos, "right", "left"]),
|
||||
child: Widget.CenterBox({
|
||||
css: "min-width: 2px; min-height: 2px;",
|
||||
startWidget: Widget.Box({
|
||||
hexpand: true,
|
||||
children: start.bind().as(s => s.map(w => widget[w]())),
|
||||
}),
|
||||
centerWidget: Widget.Box({
|
||||
hpack: "center",
|
||||
children: center.bind().as(c => c.map(w => widget[w]())),
|
||||
}),
|
||||
endWidget: Widget.Box({
|
||||
hexpand: true,
|
||||
children: end.bind().as(e => e.map(w => widget[w]())),
|
||||
}),
|
||||
}),
|
||||
})
|
||||
46
roles/ags/files/widget/bar/PanelButton.ts
Normal file
46
roles/ags/files/widget/bar/PanelButton.ts
Normal file
@@ -0,0 +1,46 @@
|
||||
import options from "options"
|
||||
import { ButtonProps } from "types/widgets/button"
|
||||
|
||||
type PanelButtonProps = ButtonProps & {
|
||||
window?: string,
|
||||
flat?: boolean
|
||||
}
|
||||
|
||||
export default ({
|
||||
window = "",
|
||||
flat,
|
||||
child,
|
||||
setup,
|
||||
...rest
|
||||
}: PanelButtonProps) => Widget.Button({
|
||||
child: Widget.Box({ child }),
|
||||
setup: self => {
|
||||
let open = false
|
||||
|
||||
self.toggleClassName("panel-button")
|
||||
self.toggleClassName(window)
|
||||
|
||||
self.hook(options.bar.flatButtons, () => {
|
||||
self.toggleClassName("flat", flat ?? options.bar.flatButtons.value)
|
||||
})
|
||||
|
||||
self.hook(App, (_, win, visible) => {
|
||||
if (win !== window)
|
||||
return
|
||||
|
||||
if (open && !visible) {
|
||||
open = false
|
||||
self.toggleClassName("active", false)
|
||||
}
|
||||
|
||||
if (visible) {
|
||||
open = true
|
||||
self.toggleClassName("active")
|
||||
}
|
||||
})
|
||||
|
||||
if (setup)
|
||||
setup(self)
|
||||
},
|
||||
...rest,
|
||||
})
|
||||
25
roles/ags/files/widget/bar/ScreenCorners.ts
Normal file
25
roles/ags/files/widget/bar/ScreenCorners.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
import options from "options"
|
||||
|
||||
const { corners } = options.bar
|
||||
|
||||
export default (monitor: number) => Widget.Window({
|
||||
monitor,
|
||||
name: `corner${monitor}`,
|
||||
class_name: "screen-corner",
|
||||
anchor: ["top", "bottom", "right", "left"],
|
||||
click_through: true,
|
||||
child: Widget.Box({
|
||||
class_name: "shadow",
|
||||
child: Widget.Box({
|
||||
class_name: "border",
|
||||
expand: true,
|
||||
child: Widget.Box({
|
||||
class_name: "corner",
|
||||
expand: true,
|
||||
}),
|
||||
}),
|
||||
}),
|
||||
setup: self => self.hook(corners, () => {
|
||||
self.toggleClassName("corners", corners.value)
|
||||
}),
|
||||
})
|
||||
234
roles/ags/files/widget/bar/bar.scss
Normal file
234
roles/ags/files/widget/bar/bar.scss
Normal file
@@ -0,0 +1,234 @@
|
||||
@use 'sass:color';
|
||||
|
||||
$bar-spacing: $spacing * .3;
|
||||
$button-radius: $radius;
|
||||
|
||||
@mixin panel-button($flat: true, $reactive: true) {
|
||||
@include accs-button($flat, $reactive);
|
||||
|
||||
>* {
|
||||
border-radius: $button-radius;
|
||||
margin: $bar-spacing;
|
||||
}
|
||||
|
||||
label,
|
||||
image {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
>* {
|
||||
padding: $padding * 0.4 $padding * 0.8;
|
||||
}
|
||||
}
|
||||
|
||||
.bar {
|
||||
background-color: $bg;
|
||||
|
||||
.panel-button {
|
||||
@include panel-button;
|
||||
|
||||
&:not(.flat) {
|
||||
|
||||
@include accs-button($flat: false);
|
||||
}
|
||||
}
|
||||
|
||||
.launcher {
|
||||
.colored {
|
||||
color: transparentize($primary-bg, 0.2);
|
||||
}
|
||||
|
||||
&:hover .colored {
|
||||
color: $primary-bg;
|
||||
}
|
||||
|
||||
&:active .colored,
|
||||
&.active .colored {
|
||||
color: $primary-fg;
|
||||
}
|
||||
}
|
||||
|
||||
.workspaces {
|
||||
label {
|
||||
font-size: 0;
|
||||
min-width: 5pt;
|
||||
min-height: 5pt;
|
||||
border-radius: $radius*.6;
|
||||
box-shadow: inset 0 0 0 $border-width $border-color;
|
||||
margin: 0 $padding * .5;
|
||||
transition: $transition* .5;
|
||||
background-color: transparentize($fg, .8);
|
||||
|
||||
&.occupied {
|
||||
background-color: transparentize($fg, .2);
|
||||
min-width: 7pt;
|
||||
min-height: 7pt;
|
||||
}
|
||||
|
||||
&.active {
|
||||
// background-color: $primary-bg;
|
||||
background-image: $active-gradient;
|
||||
min-width: 20pt;
|
||||
min-height: 12pt;
|
||||
}
|
||||
}
|
||||
|
||||
&.active,
|
||||
&:active {
|
||||
label {
|
||||
background-color: transparentize($primary-fg, .3);
|
||||
|
||||
&.occupied {
|
||||
background-color: transparentize($primary-fg, .15);
|
||||
}
|
||||
|
||||
&.active {
|
||||
background-color: $primary-fg;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.media label {
|
||||
margin: 0 ($spacing * .5)
|
||||
}
|
||||
|
||||
.taskbar .indicator.active {
|
||||
background-color: $primary-bg;
|
||||
border-radius: $radius;
|
||||
min-height: 4pt;
|
||||
min-width: 6pt;
|
||||
margin: 2pt;
|
||||
}
|
||||
|
||||
.powermenu.colored,
|
||||
.recorder {
|
||||
image {
|
||||
color: transparentize($error-bg, 0.3);
|
||||
}
|
||||
|
||||
&:hover image {
|
||||
color: transparentize($error-bg, 0.15);
|
||||
}
|
||||
|
||||
&:active image {
|
||||
color: $primary-fg;
|
||||
}
|
||||
}
|
||||
|
||||
.quicksettings>box>box {
|
||||
@include spacing($spacing: if($bar-spacing==0, $padding / 2, $bar-spacing));
|
||||
}
|
||||
|
||||
.quicksettings:not(.active):not(:active) {
|
||||
.bluetooth {
|
||||
color: $primary-bg;
|
||||
|
||||
label {
|
||||
font-size: $font-size * .7;
|
||||
color: $fg;
|
||||
text-shadow: $text-shadow;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.battery-bar {
|
||||
>* {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
&.bar-hidden>box {
|
||||
padding: 0 $spacing * .5;
|
||||
|
||||
image {
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
|
||||
levelbar * {
|
||||
all: unset;
|
||||
transition: $transition;
|
||||
}
|
||||
|
||||
.whole {
|
||||
@if $shadows {
|
||||
image {
|
||||
-gtk-icon-shadow: $text-shadow;
|
||||
}
|
||||
|
||||
label {
|
||||
text-shadow: $text-shadow;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.regular image {
|
||||
margin-left: $spacing * .5;
|
||||
}
|
||||
|
||||
trough {
|
||||
@include widget;
|
||||
min-height: 12pt;
|
||||
min-width: 12pt;
|
||||
}
|
||||
|
||||
.regular trough {
|
||||
margin-right: $spacing * .5;
|
||||
}
|
||||
|
||||
block {
|
||||
margin: 0;
|
||||
|
||||
&:last-child {
|
||||
border-radius: 0 $button-radius $button-radius 0;
|
||||
}
|
||||
|
||||
&:first-child {
|
||||
border-radius: $button-radius 0 0 $button-radius;
|
||||
}
|
||||
}
|
||||
|
||||
.vertical {
|
||||
block {
|
||||
&:last-child {
|
||||
border-radius: 0 0 $button-radius $button-radius;
|
||||
}
|
||||
|
||||
&:first-child {
|
||||
border-radius: $button-radius $button-radius 0 0;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@for $i from 1 through $bar-battery-blocks {
|
||||
block:nth-child(#{$i}).filled {
|
||||
background-color: color.mix($bg, $primary-bg, $i*3)
|
||||
}
|
||||
|
||||
&.low block:nth-child(#{$i}).filled {
|
||||
background-color: color.mix($bg, $error-bg, $i*3)
|
||||
}
|
||||
|
||||
&.charging block:nth-child(#{$i}).filled {
|
||||
background-color: color.mix($bg, $charging-bg, $i*3)
|
||||
}
|
||||
|
||||
&:active .regular block:nth-child(#{$i}).filled {
|
||||
background-color: color.mix($bg, $primary-fg, $i*3)
|
||||
}
|
||||
}
|
||||
|
||||
&.low image {
|
||||
color: $error-bg
|
||||
}
|
||||
|
||||
&.charging image {
|
||||
color: $charging-bg
|
||||
}
|
||||
|
||||
&:active image {
|
||||
color: $primary-fg
|
||||
}
|
||||
}
|
||||
}
|
||||
94
roles/ags/files/widget/bar/buttons/BatteryBar.ts
Normal file
94
roles/ags/files/widget/bar/buttons/BatteryBar.ts
Normal file
@@ -0,0 +1,94 @@
|
||||
import icons from "lib/icons"
|
||||
import options from "options"
|
||||
import PanelButton from "../PanelButton"
|
||||
|
||||
const battery = await Service.import("battery")
|
||||
const { bar, percentage, blocks, width, low } = options.bar.battery
|
||||
|
||||
const Indicator = () => Widget.Icon({
|
||||
setup: self => self.hook(battery, () => {
|
||||
self.icon = battery.charging || battery.charged
|
||||
? icons.battery.charging
|
||||
: battery.icon_name
|
||||
}),
|
||||
})
|
||||
|
||||
const PercentLabel = () => Widget.Revealer({
|
||||
transition: "slide_right",
|
||||
click_through: true,
|
||||
reveal_child: percentage.bind(),
|
||||
child: Widget.Label({
|
||||
label: battery.bind("percent").as(p => `${p}%`),
|
||||
}),
|
||||
})
|
||||
|
||||
const LevelBar = () => {
|
||||
const level = Widget.LevelBar({
|
||||
bar_mode: "discrete",
|
||||
max_value: blocks.bind(),
|
||||
visible: bar.bind().as(b => b !== "hidden"),
|
||||
value: battery.bind("percent").as(p => (p / 100) * blocks.value),
|
||||
})
|
||||
const update = () => {
|
||||
level.value = (battery.percent / 100) * blocks.value
|
||||
level.css = `block { min-width: ${width.value / blocks.value}pt; }`
|
||||
}
|
||||
return level
|
||||
.hook(width, update)
|
||||
.hook(blocks, update)
|
||||
.hook(bar, () => {
|
||||
level.vpack = bar.value === "whole" ? "fill" : "center"
|
||||
level.hpack = bar.value === "whole" ? "fill" : "center"
|
||||
})
|
||||
}
|
||||
|
||||
const WholeButton = () => Widget.Overlay({
|
||||
vexpand: true,
|
||||
child: LevelBar(),
|
||||
class_name: "whole",
|
||||
pass_through: true,
|
||||
overlay: Widget.Box({
|
||||
hpack: "center",
|
||||
children: [
|
||||
Widget.Icon({
|
||||
icon: icons.battery.charging,
|
||||
visible: Utils.merge([
|
||||
battery.bind("charging"),
|
||||
battery.bind("charged"),
|
||||
], (ing, ed) => ing || ed),
|
||||
}),
|
||||
Widget.Box({
|
||||
hpack: "center",
|
||||
vpack: "center",
|
||||
child: PercentLabel(),
|
||||
}),
|
||||
],
|
||||
}),
|
||||
})
|
||||
|
||||
const Regular = () => Widget.Box({
|
||||
class_name: "regular",
|
||||
children: [
|
||||
Indicator(),
|
||||
PercentLabel(),
|
||||
LevelBar(),
|
||||
],
|
||||
})
|
||||
|
||||
export default () => PanelButton({
|
||||
class_name: "battery-bar",
|
||||
hexpand: false,
|
||||
on_clicked: () => { percentage.value = !percentage.value },
|
||||
visible: battery.bind("available"),
|
||||
child: Widget.Box({
|
||||
expand: true,
|
||||
visible: battery.bind("available"),
|
||||
child: bar.bind().as(b => b === "whole" ? WholeButton() : Regular()),
|
||||
}),
|
||||
setup: self => self
|
||||
.hook(bar, w => w.toggleClassName("bar-hidden", bar.value === "hidden"))
|
||||
.hook(battery, w => {
|
||||
w.toggleClassName("charging", battery.charging || battery.charged)
|
||||
w.toggleClassName("low", battery.percent < low.value)
|
||||
}),
|
||||
})
|
||||
37
roles/ags/files/widget/bar/buttons/ColorPicker.ts
Normal file
37
roles/ags/files/widget/bar/buttons/ColorPicker.ts
Normal file
@@ -0,0 +1,37 @@
|
||||
import PanelButton from "../PanelButton"
|
||||
import colorpicker from "service/colorpicker"
|
||||
import Gdk from "gi://Gdk"
|
||||
|
||||
const css = (color: string) => `
|
||||
* {
|
||||
background-color: ${color};
|
||||
color: transparent;
|
||||
}
|
||||
*:hover {
|
||||
color: white;
|
||||
text-shadow: 2px 2px 3px rgba(0,0,0,.8);
|
||||
}`
|
||||
|
||||
export default () => {
|
||||
const menu = Widget.Menu({
|
||||
class_name: "colorpicker",
|
||||
children: colorpicker.bind("colors").as(c => c.map(color => Widget.MenuItem({
|
||||
child: Widget.Label(color),
|
||||
css: css(color),
|
||||
on_activate: () => colorpicker.wlCopy(color),
|
||||
}))),
|
||||
})
|
||||
|
||||
return PanelButton({
|
||||
class_name: "color-picker",
|
||||
child: Widget.Icon("color-select-symbolic"),
|
||||
tooltip_text: colorpicker.bind("colors").as(v => `${v.length} colors`),
|
||||
on_clicked: colorpicker.pick,
|
||||
on_secondary_click: self => {
|
||||
if (colorpicker.colors.length === 0)
|
||||
return
|
||||
|
||||
menu.popup_at_widget(self, Gdk.Gravity.SOUTH, Gdk.Gravity.NORTH, null)
|
||||
},
|
||||
})
|
||||
}
|
||||
15
roles/ags/files/widget/bar/buttons/Date.ts
Normal file
15
roles/ags/files/widget/bar/buttons/Date.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
import { clock } from "lib/variables"
|
||||
import PanelButton from "../PanelButton"
|
||||
import options from "options"
|
||||
|
||||
const { format, action } = options.bar.date
|
||||
const time = Utils.derive([clock, format], (c, f) => c.format(f) || "")
|
||||
|
||||
export default () => PanelButton({
|
||||
window: "datemenu",
|
||||
on_clicked: action.bind(),
|
||||
child: Widget.Label({
|
||||
justification: "center",
|
||||
label: time.bind(),
|
||||
}),
|
||||
})
|
||||
49
roles/ags/files/widget/bar/buttons/Launcher.ts
Normal file
49
roles/ags/files/widget/bar/buttons/Launcher.ts
Normal file
@@ -0,0 +1,49 @@
|
||||
import PanelButton from "../PanelButton"
|
||||
import options from "options"
|
||||
import nix from "service/nix"
|
||||
|
||||
const { icon, label, action } = options.bar.launcher
|
||||
|
||||
function Spinner() {
|
||||
const child = Widget.Icon({
|
||||
icon: icon.icon.bind(),
|
||||
class_name: Utils.merge([
|
||||
icon.colored.bind(),
|
||||
nix.bind("ready"),
|
||||
], (c, r) => `${c ? "colored" : ""} ${r ? "" : "spinning"}`),
|
||||
css: `
|
||||
@keyframes spin {
|
||||
to { -gtk-icon-transform: rotate(1turn); }
|
||||
}
|
||||
|
||||
image.spinning {
|
||||
animation-name: spin;
|
||||
animation-duration: 1s;
|
||||
animation-timing-function: linear;
|
||||
animation-iteration-count: infinite;
|
||||
}
|
||||
`,
|
||||
})
|
||||
|
||||
return Widget.Revealer({
|
||||
transition: "slide_left",
|
||||
child,
|
||||
reveal_child: Utils.merge([
|
||||
icon.icon.bind(),
|
||||
nix.bind("ready"),
|
||||
], (i, r) => Boolean(i || r)),
|
||||
})
|
||||
}
|
||||
|
||||
export default () => PanelButton({
|
||||
window: "launcher",
|
||||
on_clicked: action.bind(),
|
||||
child: Widget.Box([
|
||||
Spinner(),
|
||||
Widget.Label({
|
||||
class_name: label.colored.bind().as(c => c ? "colored" : ""),
|
||||
visible: label.label.bind().as(v => !!v),
|
||||
label: label.label.bind(),
|
||||
}),
|
||||
]),
|
||||
})
|
||||
92
roles/ags/files/widget/bar/buttons/Media.ts
Normal file
92
roles/ags/files/widget/bar/buttons/Media.ts
Normal file
@@ -0,0 +1,92 @@
|
||||
import { type MprisPlayer } from "types/service/mpris"
|
||||
import PanelButton from "../PanelButton"
|
||||
import options from "options"
|
||||
import icons from "lib/icons"
|
||||
import { icon } from "lib/utils"
|
||||
|
||||
const mpris = await Service.import("mpris")
|
||||
const { length, direction, preferred, monochrome, format } = options.bar.media
|
||||
|
||||
const getPlayer = (name = preferred.value) =>
|
||||
mpris.getPlayer(name) || mpris.players[0] || null
|
||||
|
||||
const Content = (player: MprisPlayer) => {
|
||||
const revealer = Widget.Revealer({
|
||||
click_through: true,
|
||||
visible: length.bind().as(l => l > 0),
|
||||
transition: direction.bind().as(d => `slide_${d}` as const),
|
||||
setup: self => {
|
||||
let current = ""
|
||||
self.hook(player, () => {
|
||||
if (current === player.track_title)
|
||||
return
|
||||
|
||||
current = player.track_title
|
||||
self.reveal_child = true
|
||||
Utils.timeout(3000, () => {
|
||||
!self.is_destroyed && (self.reveal_child = false)
|
||||
})
|
||||
})
|
||||
},
|
||||
child: Widget.Label({
|
||||
truncate: "end",
|
||||
max_width_chars: length.bind().as(n => n > 0 ? n : -1),
|
||||
label: Utils.merge([
|
||||
player.bind("track_title"),
|
||||
player.bind("track_artists"),
|
||||
format.bind(),
|
||||
], () => `${format}`
|
||||
.replace("{title}", player.track_title)
|
||||
.replace("{artists}", player.track_artists.join(", "))
|
||||
.replace("{artist}", player.track_artists[0] || "")
|
||||
.replace("{album}", player.track_album)
|
||||
.replace("{name}", player.name)
|
||||
.replace("{identity}", player.identity),
|
||||
),
|
||||
}),
|
||||
})
|
||||
|
||||
const playericon = Widget.Icon({
|
||||
icon: Utils.merge([player.bind("entry"), monochrome.bind()], (entry => {
|
||||
const name = `${entry}${monochrome.value ? "-symbolic" : ""}`
|
||||
return icon(name, icons.fallback.audio)
|
||||
})),
|
||||
})
|
||||
|
||||
return Widget.Box({
|
||||
attribute: { revealer },
|
||||
children: direction.bind().as(d => d === "right"
|
||||
? [playericon, revealer] : [revealer, playericon]),
|
||||
})
|
||||
}
|
||||
|
||||
export default () => {
|
||||
let player = getPlayer()
|
||||
|
||||
const btn = PanelButton({
|
||||
class_name: "media",
|
||||
child: Widget.Icon(icons.fallback.audio),
|
||||
})
|
||||
|
||||
const update = () => {
|
||||
player = getPlayer()
|
||||
btn.visible = !!player
|
||||
|
||||
if (!player)
|
||||
return
|
||||
|
||||
const content = Content(player)
|
||||
const { revealer } = content.attribute
|
||||
btn.child = content
|
||||
btn.on_primary_click = () => { player.playPause() }
|
||||
btn.on_secondary_click = () => { player.playPause() }
|
||||
btn.on_scroll_up = () => { player.next() }
|
||||
btn.on_scroll_down = () => { player.previous() }
|
||||
btn.on_hover = () => { revealer.reveal_child = true }
|
||||
btn.on_hover_lost = () => { revealer.reveal_child = false }
|
||||
}
|
||||
|
||||
return btn
|
||||
.hook(preferred, update)
|
||||
.hook(mpris, update, "notify::players")
|
||||
}
|
||||
16
roles/ags/files/widget/bar/buttons/Messages.ts
Normal file
16
roles/ags/files/widget/bar/buttons/Messages.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
import icons from "lib/icons"
|
||||
import PanelButton from "../PanelButton"
|
||||
import options from "options"
|
||||
|
||||
const n = await Service.import("notifications")
|
||||
const notifs = n.bind("notifications")
|
||||
const action = options.bar.messages.action.bind()
|
||||
|
||||
export default () => PanelButton({
|
||||
class_name: "messages",
|
||||
on_clicked: action,
|
||||
visible: notifs.as(n => n.length > 0),
|
||||
child: Widget.Box([
|
||||
Widget.Icon(icons.notifications.message),
|
||||
]),
|
||||
})
|
||||
15
roles/ags/files/widget/bar/buttons/PowerMenu.ts
Normal file
15
roles/ags/files/widget/bar/buttons/PowerMenu.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
import icons from "lib/icons"
|
||||
import PanelButton from "../PanelButton"
|
||||
import options from "options"
|
||||
|
||||
const { monochrome, action } = options.bar.powermenu
|
||||
|
||||
export default () => PanelButton({
|
||||
window: "powermenu",
|
||||
on_clicked: action.bind(),
|
||||
child: Widget.Icon(icons.powermenu.shutdown),
|
||||
setup: self => self.hook(monochrome, () => {
|
||||
self.toggleClassName("colored", !monochrome.value)
|
||||
self.toggleClassName("box")
|
||||
}),
|
||||
})
|
||||
21
roles/ags/files/widget/bar/buttons/ScreenRecord.ts
Normal file
21
roles/ags/files/widget/bar/buttons/ScreenRecord.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
import PanelButton from "../PanelButton"
|
||||
import screenrecord from "service/screenrecord"
|
||||
import icons from "lib/icons"
|
||||
|
||||
export default () => PanelButton({
|
||||
class_name: "recorder",
|
||||
on_clicked: () => screenrecord.stop(),
|
||||
visible: screenrecord.bind("recording"),
|
||||
child: Widget.Box({
|
||||
children: [
|
||||
Widget.Icon(icons.recorder.recording),
|
||||
Widget.Label({
|
||||
label: screenrecord.bind("timer").as(time => {
|
||||
const sec = time % 60
|
||||
const min = Math.floor(time / 60)
|
||||
return `${min}:${sec < 10 ? "0" + sec : sec}`
|
||||
}),
|
||||
}),
|
||||
],
|
||||
}),
|
||||
})
|
||||
39
roles/ags/files/widget/bar/buttons/SysTray.ts
Normal file
39
roles/ags/files/widget/bar/buttons/SysTray.ts
Normal file
@@ -0,0 +1,39 @@
|
||||
import { type TrayItem } from "types/service/systemtray"
|
||||
import PanelButton from "../PanelButton"
|
||||
import Gdk from "gi://Gdk"
|
||||
import options from "options"
|
||||
|
||||
const systemtray = await Service.import("systemtray")
|
||||
const { ignore } = options.bar.systray
|
||||
|
||||
const SysTrayItem = (item: TrayItem) => PanelButton({
|
||||
class_name: "tray-item",
|
||||
child: Widget.Icon({ icon: item.bind("icon") }),
|
||||
tooltip_markup: item.bind("tooltip_markup"),
|
||||
setup: self => {
|
||||
const { menu } = item
|
||||
if (!menu)
|
||||
return
|
||||
|
||||
const id = menu.connect("popped-up", () => {
|
||||
self.toggleClassName("active")
|
||||
menu.connect("notify::visible", () => {
|
||||
self.toggleClassName("active", menu.visible)
|
||||
})
|
||||
menu.disconnect(id!)
|
||||
})
|
||||
|
||||
self.connect("destroy", () => menu.disconnect(id))
|
||||
},
|
||||
|
||||
on_primary_click: btn => item.menu?.popup_at_widget(
|
||||
btn, Gdk.Gravity.SOUTH, Gdk.Gravity.NORTH, null),
|
||||
|
||||
on_secondary_click: btn => item.menu?.popup_at_widget(
|
||||
btn, Gdk.Gravity.SOUTH, Gdk.Gravity.NORTH, null),
|
||||
})
|
||||
|
||||
export default () => Widget.Box()
|
||||
.bind("children", systemtray, "items", i => i
|
||||
.filter(({ id }) => !ignore.value.includes(id))
|
||||
.map(SysTrayItem))
|
||||
98
roles/ags/files/widget/bar/buttons/SystemIndicators.ts
Normal file
98
roles/ags/files/widget/bar/buttons/SystemIndicators.ts
Normal file
@@ -0,0 +1,98 @@
|
||||
import PanelButton from "../PanelButton"
|
||||
import icons from "lib/icons"
|
||||
import asusctl from "service/asusctl"
|
||||
|
||||
const notifications = await Service.import("notifications")
|
||||
const bluetooth = await Service.import("bluetooth")
|
||||
const audio = await Service.import("audio")
|
||||
const network = await Service.import("network")
|
||||
const powerprof = await Service.import("powerprofiles")
|
||||
|
||||
const ProfileIndicator = () => {
|
||||
const visible = asusctl.available
|
||||
? asusctl.bind("profile").as(p => p !== "Balanced")
|
||||
: powerprof.bind("active_profile").as(p => p !== "balanced")
|
||||
|
||||
const icon = asusctl.available
|
||||
? asusctl.bind("profile").as(p => icons.asusctl.profile[p])
|
||||
: powerprof.bind("active_profile").as(p => icons.powerprofile[p])
|
||||
|
||||
return Widget.Icon({ visible, icon })
|
||||
}
|
||||
|
||||
const ModeIndicator = () => {
|
||||
if (!asusctl.available) {
|
||||
return Widget.Icon({
|
||||
setup(self) {
|
||||
Utils.idle(() => self.visible = false)
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
return Widget.Icon({
|
||||
visible: asusctl.bind("mode").as(m => m !== "Hybrid"),
|
||||
icon: asusctl.bind("mode").as(m => icons.asusctl.mode[m]),
|
||||
})
|
||||
}
|
||||
|
||||
const MicrophoneIndicator = () => Widget.Icon()
|
||||
.hook(audio, self => self.visible =
|
||||
audio.recorders.length > 0
|
||||
|| audio.microphone.is_muted
|
||||
|| false)
|
||||
.hook(audio.microphone, self => {
|
||||
const vol = audio.microphone.is_muted ? 0 : audio.microphone.volume
|
||||
const { muted, low, medium, high } = icons.audio.mic
|
||||
const cons = [[67, high], [34, medium], [1, low], [0, muted]] as const
|
||||
self.icon = cons.find(([n]) => n <= vol * 100)?.[1] || ""
|
||||
})
|
||||
|
||||
const DNDIndicator = () => Widget.Icon({
|
||||
visible: notifications.bind("dnd"),
|
||||
icon: icons.notifications.silent,
|
||||
})
|
||||
|
||||
const BluetoothIndicator = () => Widget.Overlay({
|
||||
class_name: "bluetooth",
|
||||
passThrough: true,
|
||||
child: Widget.Icon({
|
||||
icon: icons.bluetooth.enabled,
|
||||
visible: bluetooth.bind("enabled"),
|
||||
}),
|
||||
overlay: Widget.Label({
|
||||
hpack: "end",
|
||||
vpack: "start",
|
||||
label: bluetooth.bind("connected_devices").as(c => `${c.length}`),
|
||||
visible: bluetooth.bind("connected_devices").as(c => c.length > 0),
|
||||
}),
|
||||
})
|
||||
|
||||
const NetworkIndicator = () => Widget.Icon().hook(network, self => {
|
||||
const icon = network[network.primary || "wifi"]?.icon_name
|
||||
self.icon = icon || ""
|
||||
self.visible = !!icon
|
||||
})
|
||||
|
||||
const AudioIndicator = () => Widget.Icon()
|
||||
.hook(audio.speaker, self => {
|
||||
const vol = audio.speaker.is_muted ? 0 : audio.speaker.volume
|
||||
const { muted, low, medium, high, overamplified } = icons.audio.volume
|
||||
const cons = [[101, overamplified], [67, high], [34, medium], [1, low], [0, muted]] as const
|
||||
self.icon = cons.find(([n]) => n <= vol * 100)?.[1] || ""
|
||||
})
|
||||
|
||||
export default () => PanelButton({
|
||||
window: "quicksettings",
|
||||
on_clicked: () => App.toggleWindow("quicksettings"),
|
||||
on_scroll_up: () => audio.speaker.volume += 0.02,
|
||||
on_scroll_down: () => audio.speaker.volume -= 0.02,
|
||||
child: Widget.Box([
|
||||
ProfileIndicator(),
|
||||
ModeIndicator(),
|
||||
DNDIndicator(),
|
||||
BluetoothIndicator(),
|
||||
NetworkIndicator(),
|
||||
AudioIndicator(),
|
||||
MicrophoneIndicator(),
|
||||
]),
|
||||
})
|
||||
90
roles/ags/files/widget/bar/buttons/Taskbar.ts
Normal file
90
roles/ags/files/widget/bar/buttons/Taskbar.ts
Normal file
@@ -0,0 +1,90 @@
|
||||
import { launchApp, icon } from "lib/utils"
|
||||
import icons from "lib/icons"
|
||||
import options from "options"
|
||||
import PanelButton from "../PanelButton"
|
||||
|
||||
const hyprland = await Service.import("hyprland")
|
||||
const apps = await Service.import("applications")
|
||||
const { monochrome, exclusive, iconSize } = options.bar.taskbar
|
||||
const { position } = options.bar
|
||||
|
||||
const focus = (address: string) => hyprland.messageAsync(
|
||||
`dispatch focuswindow address:${address}`)
|
||||
|
||||
const DummyItem = (address: string) => Widget.Box({
|
||||
attribute: { address },
|
||||
visible: false,
|
||||
})
|
||||
|
||||
const AppItem = (address: string) => {
|
||||
const client = hyprland.getClient(address)
|
||||
if (!client || client.class === "")
|
||||
return DummyItem(address)
|
||||
|
||||
const app = apps.list.find(app => app.match(client.class))
|
||||
|
||||
const btn = PanelButton({
|
||||
class_name: "panel-button",
|
||||
tooltip_text: Utils.watch(client.title, hyprland, () =>
|
||||
hyprland.getClient(address)?.title || "",
|
||||
),
|
||||
on_primary_click: () => focus(address),
|
||||
on_middle_click: () => app && launchApp(app),
|
||||
child: Widget.Icon({
|
||||
size: iconSize.bind(),
|
||||
icon: monochrome.bind().as(m => icon(
|
||||
(app?.icon_name || client.class) + (m ? "-symbolic" : ""),
|
||||
icons.fallback.executable + (m ? "-symbolic" : ""),
|
||||
)),
|
||||
}),
|
||||
})
|
||||
|
||||
return Widget.Box(
|
||||
{
|
||||
attribute: { address },
|
||||
visible: Utils.watch(true, [exclusive, hyprland], () => {
|
||||
return exclusive.value
|
||||
? hyprland.active.workspace.id === client.workspace.id
|
||||
: true
|
||||
}),
|
||||
},
|
||||
Widget.Overlay({
|
||||
child: btn,
|
||||
pass_through: true,
|
||||
overlay: Widget.Box({
|
||||
className: "indicator",
|
||||
hpack: "center",
|
||||
vpack: position.bind().as(p => p === "top" ? "start" : "end"),
|
||||
setup: w => w.hook(hyprland, () => {
|
||||
w.toggleClassName("active", hyprland.active.client.address === address)
|
||||
}),
|
||||
}),
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
function sortItems<T extends { attribute: { address: string } }>(arr: T[]) {
|
||||
return arr.sort(({ attribute: a }, { attribute: b }) => {
|
||||
const aclient = hyprland.getClient(a.address)!
|
||||
const bclient = hyprland.getClient(b.address)!
|
||||
return aclient.workspace.id - bclient.workspace.id
|
||||
})
|
||||
}
|
||||
|
||||
export default () => Widget.Box({
|
||||
class_name: "taskbar",
|
||||
children: sortItems(hyprland.clients.map(c => AppItem(c.address))),
|
||||
setup: w => w
|
||||
.hook(hyprland, (w, address?: string) => {
|
||||
if (typeof address === "string")
|
||||
w.children = w.children.filter(ch => ch.attribute.address !== address)
|
||||
}, "client-removed")
|
||||
.hook(hyprland, (w, address?: string) => {
|
||||
if (typeof address === "string")
|
||||
w.children = sortItems([...w.children, AppItem(address)])
|
||||
}, "client-added")
|
||||
.hook(hyprland, (w, event?: string) => {
|
||||
if (event === "movewindow")
|
||||
w.children = sortItems(w.children)
|
||||
}, "event"),
|
||||
})
|
||||
38
roles/ags/files/widget/bar/buttons/Workspaces.ts
Normal file
38
roles/ags/files/widget/bar/buttons/Workspaces.ts
Normal file
@@ -0,0 +1,38 @@
|
||||
import PanelButton from "../PanelButton"
|
||||
import options from "options"
|
||||
import { sh, range } from "lib/utils"
|
||||
|
||||
const hyprland = await Service.import("hyprland")
|
||||
const { workspaces } = options.bar.workspaces
|
||||
|
||||
const dispatch = (arg: string | number) => {
|
||||
sh(`hyprctl dispatch workspace ${arg}`)
|
||||
}
|
||||
|
||||
const Workspaces = (ws: number) => Widget.Box({
|
||||
children: range(ws || 20).map(i => Widget.Label({
|
||||
attribute: i,
|
||||
vpack: "center",
|
||||
label: `${i}`,
|
||||
setup: self => self.hook(hyprland, () => {
|
||||
self.toggleClassName("active", hyprland.active.workspace.id === i)
|
||||
self.toggleClassName("occupied", (hyprland.getWorkspace(i)?.windows || 0) > 0)
|
||||
}),
|
||||
})),
|
||||
setup: box => {
|
||||
if (ws === 0) {
|
||||
box.hook(hyprland.active.workspace, () => box.children.map(btn => {
|
||||
btn.visible = hyprland.workspaces.some(ws => ws.id === btn.attribute)
|
||||
}))
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
export default () => PanelButton({
|
||||
window: "overview",
|
||||
class_name: "workspaces",
|
||||
on_scroll_up: () => dispatch("m+1"),
|
||||
on_scroll_down: () => dispatch("m-1"),
|
||||
on_clicked: () => App.toggleWindow("overview"),
|
||||
child: workspaces.bind().as(Workspaces),
|
||||
})
|
||||
50
roles/ags/files/widget/bar/screencorner.scss
Normal file
50
roles/ags/files/widget/bar/screencorner.scss
Normal file
@@ -0,0 +1,50 @@
|
||||
$_shadow-size: $padding;
|
||||
$_radius: $radius * $hyprland-gaps-multiplier;
|
||||
$_margin: 99px;
|
||||
|
||||
window.screen-corner {
|
||||
box.shadow {
|
||||
margin-right: $_margin * -1;
|
||||
margin-left: $_margin * -1;
|
||||
|
||||
@if $shadows {
|
||||
box-shadow: inset 0 0 $_shadow-size 0 $shadow-color;
|
||||
}
|
||||
|
||||
@if $bar-position =="top" {
|
||||
margin-bottom: $_margin * -1;
|
||||
}
|
||||
|
||||
@if $bar-position =="bottom" {
|
||||
margin-top: $_margin * -1;
|
||||
}
|
||||
}
|
||||
|
||||
box.border {
|
||||
@if $bar-position =="top" {
|
||||
border-top: $border-width solid $bg;
|
||||
}
|
||||
|
||||
@if $bar-position =="bottom" {
|
||||
border-bottom: $border-width solid $bg;
|
||||
}
|
||||
|
||||
margin-right: $_margin;
|
||||
margin-left: $_margin;
|
||||
}
|
||||
|
||||
box.corner {
|
||||
box-shadow: 0 0 0 $border-width $border-color;
|
||||
}
|
||||
|
||||
&.corners {
|
||||
box.border {
|
||||
border-radius: if($radius>0, $radius * $hyprland-gaps-multiplier, 0);
|
||||
box-shadow: 0 0 0 $_radius $bg;
|
||||
}
|
||||
|
||||
box.corner {
|
||||
border-radius: if($radius>0, $radius * $hyprland-gaps-multiplier, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user