[AGS] Bar: BT, Audio, SysInfo, Brightness
This commit is contained in:
parent
69484fc302
commit
e93e051094
@ -1,14 +1,14 @@
|
|||||||
import { App } from "astal/gtk4"
|
import { App } from "astal/gtk4"
|
||||||
import style from "./style.scss"
|
import style from "./style.scss"
|
||||||
|
|
||||||
import notifications from "./components/notifications/handler";
|
// import notifications from "./components/notifications/handler";
|
||||||
import Bar from "./components/bar/ui/Bar";
|
import Bar from "./components/bar/ui/Bar";
|
||||||
|
|
||||||
App.start({
|
App.start({
|
||||||
instanceName: "runner",
|
instanceName: "runner",
|
||||||
css: style,
|
css: style,
|
||||||
main() {
|
main() {
|
||||||
notifications.startNotificationHandler( App.get_monitors()[0] );
|
// notifications.startNotificationHandler( App.get_monitors()[0] );
|
||||||
// TODO: Monitor handling
|
// TODO: Monitor handling
|
||||||
Bar.Bar( App.get_monitors()[0] );
|
Bar.Bar( App.get_monitors()[0] );
|
||||||
},
|
},
|
||||||
@ -17,7 +17,8 @@ App.start({
|
|||||||
|
|
||||||
// Notifications (TODO: Handle the arguments in the components themselves)
|
// Notifications (TODO: Handle the arguments in the components themselves)
|
||||||
if ( args[ 0 ] === 'notifier' ) {
|
if ( args[ 0 ] === 'notifier' ) {
|
||||||
res( notifications.cliHandler( args ) );
|
res( 'Not available here yet, run astal -i notifier ' + args[ 1 ] );
|
||||||
|
// res( notifications.cliHandler( args ) );
|
||||||
} else if ( args[ 0 ] === 'bar' ) {
|
} else if ( args[ 0 ] === 'bar' ) {
|
||||||
res( Bar.cliHandler( args ) );
|
res( Bar.cliHandler( args ) );
|
||||||
}
|
}
|
||||||
|
@ -3,40 +3,55 @@ import Hyprland from "./modules/Hyprland";
|
|||||||
import Calendar from "./modules/Calendar";
|
import Calendar from "./modules/Calendar";
|
||||||
import QuickView from "./modules/QuickView";
|
import QuickView from "./modules/QuickView";
|
||||||
import SystemInfo from "./modules/SystemInfo";
|
import SystemInfo from "./modules/SystemInfo";
|
||||||
|
import { CenterBox } from "astal/gtk4/widget";
|
||||||
|
|
||||||
const Bar = (gdkmonitor: Gdk.Monitor) => {
|
const Bar = (gdkmonitor: Gdk.Monitor) => {
|
||||||
const { TOP, LEFT, RIGHT } = Astal.WindowAnchor;
|
const { TOP, LEFT, RIGHT } = Astal.WindowAnchor;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<window gdkmonitor={gdkmonitor}
|
<window
|
||||||
|
gdkmonitor={gdkmonitor}
|
||||||
cssClasses={["Bar"]}
|
cssClasses={["Bar"]}
|
||||||
exclusivity={Astal.Exclusivity.EXCLUSIVE}
|
exclusivity={Astal.Exclusivity.EXCLUSIVE}
|
||||||
anchor={TOP | LEFT | RIGHT}
|
anchor={TOP | LEFT | RIGHT}
|
||||||
visible
|
visible
|
||||||
application={App}
|
application={App}
|
||||||
child={
|
child={
|
||||||
<box orientation={Gtk.Orientation.HORIZONTAL}>
|
<CenterBox
|
||||||
<box hexpand halign={Gtk.Align.START} cssClasses={["BarLeft"]}>
|
orientation={Gtk.Orientation.HORIZONTAL}
|
||||||
<Calendar.Time />
|
start_widget={
|
||||||
<SystemInfo.SystemInfo />
|
<box
|
||||||
<Hyprland.Workspace />
|
hexpand
|
||||||
</box>
|
halign={Gtk.Align.START}
|
||||||
<Hyprland.ActiveWindow />
|
cssClasses={["BarLeft"]}
|
||||||
<box hexpand halign={Gtk.Align.END} cssClasses={["BarRight"]}>
|
>
|
||||||
<Hyprland.SysTray />
|
<Calendar.Time />
|
||||||
<QuickView.QuickView />
|
<SystemInfo.SystemInfo />
|
||||||
</box>
|
<Hyprland.Workspace />
|
||||||
</box>
|
</box>
|
||||||
}>
|
}
|
||||||
</window>
|
centerWidget={<Hyprland.ActiveWindow />}
|
||||||
|
endWidget={
|
||||||
|
<box
|
||||||
|
hexpand
|
||||||
|
halign={Gtk.Align.END}
|
||||||
|
cssClasses={["BarRight"]}
|
||||||
|
>
|
||||||
|
<Hyprland.SysTray />
|
||||||
|
<QuickView.QuickView />
|
||||||
|
</box>
|
||||||
|
}
|
||||||
|
></CenterBox>
|
||||||
|
}
|
||||||
|
></window>
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
|
|
||||||
const cliHandler = ( args: string[] ): string => {
|
const cliHandler = (args: string[]): string => {
|
||||||
return 'Not implemented';
|
return "Not implemented";
|
||||||
}
|
};
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
Bar,
|
Bar,
|
||||||
cliHandler
|
cliHandler,
|
||||||
};
|
};
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import { Gtk } from "astal/gtk4"
|
import { Gtk } from "astal/gtk4"
|
||||||
import Network from "./modules/Networking/Network";
|
|
||||||
import Power from "./modules/Power";
|
import Power from "./modules/Power";
|
||||||
|
import Audio from "./modules/Audio/Audio";
|
||||||
|
import Bluetooth from "./modules/Bluetooth/Bluetooth";
|
||||||
|
|
||||||
const QuickActions = () => {
|
const QuickActions = () => {
|
||||||
const popover = new Gtk.Popover( { cssClasses: [ 'quick-actions-popover' ] } );
|
const popover = new Gtk.Popover( { cssClasses: [ 'quick-actions-popover' ] } );
|
||||||
@ -12,9 +13,11 @@ const QuickActions = () => {
|
|||||||
|
|
||||||
|
|
||||||
const createQuickActionMenu = () => {
|
const createQuickActionMenu = () => {
|
||||||
return <box visible cssClasses={[ 'quick-actions' ]}>
|
// TODO: For the future add WiFi / Networking back, for the time being remove, as unnecessary effort
|
||||||
|
return <box visible cssClasses={[ 'quick-actions' ]} vertical>
|
||||||
<Power></Power>
|
<Power></Power>
|
||||||
<Network></Network>
|
<Bluetooth.BluetoothModule></Bluetooth.BluetoothModule>
|
||||||
|
<Audio.AudioModule></Audio.AudioModule>
|
||||||
</box>
|
</box>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -0,0 +1,178 @@
|
|||||||
|
import { bind, Binding } from "astal";
|
||||||
|
import { Gtk } from "astal/gtk4";
|
||||||
|
import AstalWp from "gi://AstalWp";
|
||||||
|
|
||||||
|
const wp = AstalWp.get_default()!;
|
||||||
|
|
||||||
|
const AudioModule = () => {
|
||||||
|
const setVolumeSpeaker = (volume: number) => {
|
||||||
|
wp.defaultSpeaker.set_volume(volume / 100);
|
||||||
|
};
|
||||||
|
|
||||||
|
const setVolumeMicrophone = (volume: number) => {
|
||||||
|
wp.defaultMicrophone.set_volume(volume / 100);
|
||||||
|
};
|
||||||
|
|
||||||
|
const speakerSelector = SinkSelectPopover(AstalWp.MediaClass.AUDIO_SPEAKER);
|
||||||
|
const micSelector = SinkSelectPopover(AstalWp.MediaClass.AUDIO_MICROPHONE);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<box cssClasses={["audio-box"]} vertical>
|
||||||
|
<box>
|
||||||
|
<button
|
||||||
|
onClicked={() =>
|
||||||
|
wp.defaultSpeaker.set_mute(
|
||||||
|
!wp.defaultSpeaker.get_mute(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
child={
|
||||||
|
<image
|
||||||
|
iconName={bind(wp.defaultSpeaker, "volumeIcon")}
|
||||||
|
marginEnd={3}
|
||||||
|
></image>
|
||||||
|
}
|
||||||
|
></button>
|
||||||
|
<label
|
||||||
|
label={bind(wp.defaultMicrophone, "volume").as(
|
||||||
|
v => Math.round(100 * v) + "%",
|
||||||
|
)}
|
||||||
|
></label>
|
||||||
|
<slider
|
||||||
|
value={bind(wp.defaultSpeaker, "volume").as(v => 100 * v)}
|
||||||
|
max={100}
|
||||||
|
min={0}
|
||||||
|
step={1}
|
||||||
|
widthRequest={100}
|
||||||
|
onChangeValue={self => setVolumeSpeaker(self.value)}
|
||||||
|
></slider>
|
||||||
|
<button
|
||||||
|
cssClasses={["sink-select-button"]}
|
||||||
|
child={
|
||||||
|
<box>
|
||||||
|
<image iconName={"speaker-symbolic"}></image>
|
||||||
|
{speakerSelector}
|
||||||
|
</box>
|
||||||
|
}
|
||||||
|
onClicked={() => speakerSelector.popup()}
|
||||||
|
></button>
|
||||||
|
</box>
|
||||||
|
<box>
|
||||||
|
<button
|
||||||
|
onClicked={() =>
|
||||||
|
wp.defaultMicrophone.set_mute(
|
||||||
|
!wp.defaultMicrophone.get_mute(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
child={
|
||||||
|
<image
|
||||||
|
iconName={bind(wp.defaultMicrophone, "volumeIcon")}
|
||||||
|
marginEnd={3}
|
||||||
|
></image>
|
||||||
|
}
|
||||||
|
></button>
|
||||||
|
<label
|
||||||
|
label={bind(wp.defaultMicrophone, "volume").as(
|
||||||
|
v => Math.round(100 * v) + "%",
|
||||||
|
)}
|
||||||
|
></label>
|
||||||
|
<slider
|
||||||
|
value={bind(wp.defaultMicrophone, "volume").as(
|
||||||
|
v => 100 * v,
|
||||||
|
)}
|
||||||
|
max={100}
|
||||||
|
min={0}
|
||||||
|
step={1}
|
||||||
|
widthRequest={100}
|
||||||
|
onChangeValue={self => setVolumeMicrophone(self.value)}
|
||||||
|
></slider>
|
||||||
|
<button
|
||||||
|
cssClasses={["sink-select-button"]}
|
||||||
|
child={
|
||||||
|
<box>
|
||||||
|
<image iconName={"microphone"}></image>
|
||||||
|
{micSelector}
|
||||||
|
</box>
|
||||||
|
}
|
||||||
|
onClicked={() => micSelector.popup()}
|
||||||
|
></button>
|
||||||
|
</box>
|
||||||
|
</box>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const SinkPicker = (type: AstalWp.MediaClass) => {
|
||||||
|
const devices = bind(wp, "endpoints");
|
||||||
|
|
||||||
|
return (
|
||||||
|
<box vertical>
|
||||||
|
<label
|
||||||
|
label={`Available Audio ${type === AstalWp.MediaClass.AUDIO_SPEAKER ? "Output" : type === AstalWp.MediaClass.AUDIO_MICROPHONE ? "Input" : ""} Devices`}
|
||||||
|
></label>
|
||||||
|
<Gtk.Separator marginBottom={5} marginTop={3}></Gtk.Separator>
|
||||||
|
<box vertical cssClasses={["sink-picker"]}>
|
||||||
|
{devices.as(d => {
|
||||||
|
return d.map(device => {
|
||||||
|
if (device.get_media_class() !== type) {
|
||||||
|
return <box cssClasses={[ 'empty' ]}></box>;
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
cssClasses={bind(device, "id").as(id => {
|
||||||
|
if (
|
||||||
|
id ===
|
||||||
|
(type ===
|
||||||
|
AstalWp.MediaClass.AUDIO_SPEAKER
|
||||||
|
? wp.defaultSpeaker.id
|
||||||
|
: type ===
|
||||||
|
AstalWp.MediaClass
|
||||||
|
.AUDIO_MICROPHONE
|
||||||
|
? wp.defaultMicrophone.id
|
||||||
|
: "")
|
||||||
|
) {
|
||||||
|
return [
|
||||||
|
"sink-option",
|
||||||
|
"currently-selected-sink-option",
|
||||||
|
];
|
||||||
|
} else {
|
||||||
|
return ["sink-option"];
|
||||||
|
}
|
||||||
|
})}
|
||||||
|
child={
|
||||||
|
<box halign={Gtk.Align.START}>
|
||||||
|
<image
|
||||||
|
iconName={bind(device, "icon").as(
|
||||||
|
icon => icon,
|
||||||
|
)}
|
||||||
|
marginEnd={3}
|
||||||
|
></image>
|
||||||
|
<label
|
||||||
|
label={bind(
|
||||||
|
device,
|
||||||
|
"description",
|
||||||
|
).as(t => t ?? "")}
|
||||||
|
></label>
|
||||||
|
</box>
|
||||||
|
}
|
||||||
|
onClicked={() => {
|
||||||
|
device.set_is_default(true);
|
||||||
|
}}
|
||||||
|
></button>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
})}
|
||||||
|
</box>
|
||||||
|
</box>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const SinkSelectPopover = (type: AstalWp.MediaClass) => {
|
||||||
|
const popover = new Gtk.Popover();
|
||||||
|
|
||||||
|
popover.set_child(SinkPicker(type));
|
||||||
|
|
||||||
|
return popover;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default {
|
||||||
|
AudioModule,
|
||||||
|
};
|
@ -0,0 +1,153 @@
|
|||||||
|
import { bind } from "astal";
|
||||||
|
import { Gtk } from "astal/gtk4";
|
||||||
|
import AstalBluetooth from "gi://AstalBluetooth";
|
||||||
|
import BTDevice from "./Device";
|
||||||
|
|
||||||
|
const bt = AstalBluetooth.get_default();
|
||||||
|
|
||||||
|
const BluetoothModule = () => {
|
||||||
|
return (
|
||||||
|
<box>
|
||||||
|
<button
|
||||||
|
cssClasses={bind(bt.adapter, "powered").as(powered =>
|
||||||
|
powered
|
||||||
|
? ["bt-toggle-button", "bt-on"]
|
||||||
|
: ["bt-toggle-button"],
|
||||||
|
)}
|
||||||
|
onClicked={() =>
|
||||||
|
bt.adapter.set_powered(!bt.adapter.get_powered())
|
||||||
|
}
|
||||||
|
child={
|
||||||
|
<box vertical>
|
||||||
|
<label
|
||||||
|
cssClasses={["button-title"]}
|
||||||
|
label={"Bluetooth"}
|
||||||
|
></label>
|
||||||
|
<box>
|
||||||
|
<label
|
||||||
|
visible={bind(bt.adapter, "powered").as(
|
||||||
|
p => !p,
|
||||||
|
)}
|
||||||
|
label="Disabled"
|
||||||
|
></label>
|
||||||
|
<label
|
||||||
|
visible={bind(bt.adapter, "powered")}
|
||||||
|
label={bind(bt, "devices").as(devices => {
|
||||||
|
let count = 0;
|
||||||
|
devices.forEach(device => {
|
||||||
|
if (device.connected) {
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return `On (${count} ${count === 1 ? "client" : "clients"} connected)`;
|
||||||
|
})}
|
||||||
|
></label>
|
||||||
|
</box>
|
||||||
|
<label></label>
|
||||||
|
</box>
|
||||||
|
}
|
||||||
|
></button>
|
||||||
|
<button
|
||||||
|
cssClasses={["bt-devices-button"]}
|
||||||
|
visible={bind(bt.adapter, "powered")}
|
||||||
|
child={
|
||||||
|
<box>
|
||||||
|
<image iconName={"arrow-right-symbolic"}></image>
|
||||||
|
{picker}
|
||||||
|
</box>
|
||||||
|
}
|
||||||
|
onClicked={() => openBTPicker()}
|
||||||
|
></button>
|
||||||
|
</box>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const openBTPicker = () => {
|
||||||
|
picker.popup();
|
||||||
|
try {
|
||||||
|
bt.adapter.start_discovery();
|
||||||
|
} catch (_) {}
|
||||||
|
};
|
||||||
|
|
||||||
|
const BluetoothPickerList = () => {
|
||||||
|
return (
|
||||||
|
<box vertical onDestroy={() => bt.adapter.stop_discovery()}>
|
||||||
|
<label label={"Connected devices"} cssClasses={["title-2"]}></label>
|
||||||
|
<Gtk.Separator marginTop={3} marginBottom={5}></Gtk.Separator>
|
||||||
|
<box vertical cssClasses={["bt-conn-list"]}>
|
||||||
|
{bind(bt, "devices").as(devices => {
|
||||||
|
return devices
|
||||||
|
.filter(device => {
|
||||||
|
if (device.get_connected()) {
|
||||||
|
return device;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.map(device => {
|
||||||
|
return <BTDevice device={device}></BTDevice>;
|
||||||
|
});
|
||||||
|
})}
|
||||||
|
</box>
|
||||||
|
<label
|
||||||
|
visible={bind(bt, "devices").as(devices => {
|
||||||
|
return (
|
||||||
|
devices.filter(device => {
|
||||||
|
if (device.get_connected()) {
|
||||||
|
return device;
|
||||||
|
}
|
||||||
|
}).length === 0
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
label={"No connected devices"}
|
||||||
|
cssClasses={["bt-no-found", "bt-conn-list"]}
|
||||||
|
></label>
|
||||||
|
<label
|
||||||
|
label={"Discovered bluetooth devices"}
|
||||||
|
cssClasses={["title-2"]}
|
||||||
|
></label>
|
||||||
|
<Gtk.Separator marginBottom={5} marginTop={3}></Gtk.Separator>
|
||||||
|
<box vertical>
|
||||||
|
{bind(bt, "devices").as(devices => {
|
||||||
|
return devices
|
||||||
|
.filter(data => {
|
||||||
|
if (!data.get_connected()) {
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.map(device => {
|
||||||
|
return <BTDevice device={device}></BTDevice>;
|
||||||
|
});
|
||||||
|
})}
|
||||||
|
</box>
|
||||||
|
<label
|
||||||
|
visible={bind(bt, "devices").as(devices => {
|
||||||
|
return (
|
||||||
|
devices.filter(device => {
|
||||||
|
if (!device.get_connected()) {
|
||||||
|
return device;
|
||||||
|
}
|
||||||
|
}).length === 0
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
label={"No discovered devices"}
|
||||||
|
cssClasses={["bt-no-found"]}
|
||||||
|
></label>
|
||||||
|
</box>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
const BluetoothPicker = () => {
|
||||||
|
const popover = new Gtk.Popover();
|
||||||
|
|
||||||
|
popover.set_child(BluetoothPickerList());
|
||||||
|
popover.connect( 'closed', () => bt.adapter.stop_discovery() );
|
||||||
|
|
||||||
|
return popover;
|
||||||
|
};
|
||||||
|
|
||||||
|
const picker = BluetoothPicker();
|
||||||
|
|
||||||
|
export default {
|
||||||
|
BluetoothModule,
|
||||||
|
};
|
@ -0,0 +1,52 @@
|
|||||||
|
import { bind } from "astal";
|
||||||
|
import AstalBluetooth from "gi://AstalBluetooth";
|
||||||
|
|
||||||
|
|
||||||
|
const BTDevice = ({ device }: { device: AstalBluetooth.Device }) => {
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
visible={bind(device, "name").as(n => n !== null)}
|
||||||
|
child={
|
||||||
|
<centerbox
|
||||||
|
startWidget={
|
||||||
|
<box>
|
||||||
|
<image iconName={"chronometer-reset"} visible={bind( device, 'connecting' )}></image>
|
||||||
|
<image
|
||||||
|
iconName={bind(device, "icon")}
|
||||||
|
marginEnd={3}
|
||||||
|
></image>
|
||||||
|
</box>
|
||||||
|
}
|
||||||
|
centerWidget={
|
||||||
|
<label
|
||||||
|
label={bind(device, "name").as(n => n ?? "No name")}
|
||||||
|
marginEnd={5}
|
||||||
|
></label>
|
||||||
|
}
|
||||||
|
endWidget={
|
||||||
|
<box>
|
||||||
|
<label
|
||||||
|
label={bind(device, "batteryPercentage").as(
|
||||||
|
bat => (bat >= 0 ? bat + "%" : "?%"),
|
||||||
|
)}
|
||||||
|
marginEnd={3}
|
||||||
|
></label>
|
||||||
|
<image
|
||||||
|
iconName={bind(device, "trusted").as(v =>
|
||||||
|
v ? "checkbox" : "paint-unknown-symbolic",
|
||||||
|
)}
|
||||||
|
></image>
|
||||||
|
</box>
|
||||||
|
}
|
||||||
|
></centerbox>
|
||||||
|
}
|
||||||
|
onClicked={() => {
|
||||||
|
// TODO: Make sure to check if device was previously paired and otherwise do some pairing shenanigans
|
||||||
|
device.connect_device( () => {} );
|
||||||
|
}}
|
||||||
|
></button>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
export default BTDevice;
|
@ -73,7 +73,8 @@ const Network = () => {
|
|||||||
} else {
|
} else {
|
||||||
return 'Unavailable';
|
return 'Unavailable';
|
||||||
}
|
}
|
||||||
} )}></label>
|
} )} visible={bind(net.wifi, 'enabled').as( en => en )}></label>
|
||||||
|
<label label="Disabled" visible={bind(net.wifi, 'enabled').as( en => !en )}></label>
|
||||||
</box>}
|
</box>}
|
||||||
></button>
|
></button>
|
||||||
<button
|
<button
|
||||||
|
@ -17,9 +17,7 @@ const setNetworking = ( status: boolean ) => {
|
|||||||
|
|
||||||
|
|
||||||
const getIP = () => {
|
const getIP = () => {
|
||||||
print( 'Hello World' );
|
return exec( `/bin/bash -c "ip addr show | grep 'inet ' | awk '{print $2}' | grep -v '127'"` ).split( '/' )[ 0 ];
|
||||||
return 'Hello World';
|
|
||||||
// return exec( "ip addr show | grep 'inet ' | awk '{print $2}' | grep -v '127'" ).split( '/' )[ 0 ];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -2,30 +2,62 @@ import { exec } from "astal";
|
|||||||
import { Gtk } from "astal/gtk4";
|
import { Gtk } from "astal/gtk4";
|
||||||
|
|
||||||
const PowerMenu = (): Gtk.Popover => {
|
const PowerMenu = (): Gtk.Popover => {
|
||||||
const popover = new Gtk.Popover( { cssClasses: [ 'PowerMenu' ] } );
|
const popover = new Gtk.Popover({ cssClasses: ["PowerMenu"] });
|
||||||
|
|
||||||
const powerMenuBox = () => {
|
const powerMenuBox = () => {
|
||||||
return <box>
|
return (
|
||||||
<button cssClasses={['power-button']} child={<image iconName={"system-shutdown-symbolic"}></image>} onClicked={() => exec( 'shutdown now' )}></button>
|
<box>
|
||||||
<button cssClasses={['power-button']} child={<image iconName={"system-reboot-symbolic"}></image>} onClicked={() => exec( 'reboot' )}></button>
|
<button
|
||||||
<button cssClasses={['power-button']} child={<image iconName={"system-suspend-symbolic"}></image>} onClicked={() => exec( 'systemctl suspend' )}></button>
|
cssClasses={["power-button"]}
|
||||||
<button cssClasses={['power-button']} child={<image iconName={"system-lock-screen-symbolic"}></image>} onClicked={() => exec( 'hyprlock' )}></button>
|
child={
|
||||||
<button cssClasses={['power-button']} child={<image iconName={"system-log-out-symbolic"}></image>} onClicked={() => exec( 'hyprctl dispatch exit 0' )}></button>
|
<image iconName={"system-shutdown-symbolic"}></image>
|
||||||
</box>
|
}
|
||||||
}
|
onClicked={() => exec("shutdown now")}
|
||||||
|
></button>
|
||||||
|
<button
|
||||||
|
cssClasses={["power-button"]}
|
||||||
|
child={<image iconName={"system-reboot-symbolic"}></image>}
|
||||||
|
onClicked={() => exec("reboot")}
|
||||||
|
></button>
|
||||||
|
<button
|
||||||
|
cssClasses={["power-button"]}
|
||||||
|
child={<image iconName={"system-suspend-symbolic"}></image>}
|
||||||
|
onClicked={() => exec("systemctl suspend")}
|
||||||
|
></button>
|
||||||
|
<button
|
||||||
|
cssClasses={["power-button"]}
|
||||||
|
child={
|
||||||
|
<image iconName={"system-lock-screen-symbolic"}></image>
|
||||||
|
}
|
||||||
|
onClicked={() => exec("hyprlock")}
|
||||||
|
></button>
|
||||||
|
<button
|
||||||
|
cssClasses={["power-button"]}
|
||||||
|
child={<image iconName={"system-log-out-symbolic"}></image>}
|
||||||
|
onClicked={() => exec("hyprctl dispatch exit 0")}
|
||||||
|
></button>
|
||||||
|
</box>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
popover.set_child(powerMenuBox());
|
||||||
popover.set_child( powerMenuBox() );
|
|
||||||
return popover;
|
return popover;
|
||||||
}
|
};
|
||||||
|
|
||||||
const Power = () => {
|
const Power = () => {
|
||||||
const pm = PowerMenu();
|
const pm = PowerMenu();
|
||||||
return <box visible>
|
return (
|
||||||
<button cssClasses={['PowerMenuButton']} child={<image iconName={"system-shutdown-symbolic"}></image>} onClicked={() => pm.popup()}/>
|
<button
|
||||||
{ pm }
|
cssClasses={["PowerMenuButton"]}
|
||||||
</box>
|
child={
|
||||||
}
|
<box>
|
||||||
|
<image iconName={"system-shutdown-symbolic"}></image>
|
||||||
|
{pm}
|
||||||
|
</box>
|
||||||
|
}
|
||||||
|
onClicked={() => pm.popup()}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
export default Power;
|
export default Power;
|
||||||
|
@ -1,37 +1,13 @@
|
|||||||
@import "colors";
|
.title-2 {
|
||||||
|
font-size: 1.2rem;
|
||||||
.toggle-row {
|
font-weight: bold;
|
||||||
background-color: $bg;
|
}
|
||||||
border-radius: 12px;
|
|
||||||
margin: 6px 0;
|
.bt-conn-list {
|
||||||
overflow: hidden;
|
margin-bottom: 20px;
|
||||||
border: 1px solid $border;
|
}
|
||||||
|
|
||||||
button {
|
popover>box {
|
||||||
padding: 10px 16px;
|
margin: 10px;
|
||||||
font-size: 14px;
|
border-radius: 50px;
|
||||||
transition: background 0.2s ease;
|
|
||||||
border: none;
|
|
||||||
background: transparent;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background-color: $hover;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.toggle {
|
|
||||||
flex: 1;
|
|
||||||
background-color: transparent;
|
|
||||||
text-align: left;
|
|
||||||
&.active {
|
|
||||||
background-color: $accent;
|
|
||||||
color: white;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.arrow {
|
|
||||||
width: 40px;
|
|
||||||
background-color: transparent;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
19
config/astal/components/bar/ui/bar.scss
Normal file
19
config/astal/components/bar/ui/bar.scss
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
$fg-color: #{"@theme_fg_color"};
|
||||||
|
$bg-color: #{"@theme_bg_color"};
|
||||||
|
|
||||||
|
window.Bar {
|
||||||
|
background: transparent;
|
||||||
|
color: $fg-color;
|
||||||
|
font-weight: bold;
|
||||||
|
|
||||||
|
>centerbox {
|
||||||
|
background: $bg-color;
|
||||||
|
border-radius: 10px;
|
||||||
|
margin: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
button {
|
||||||
|
border-radius: 8px;
|
||||||
|
margin: 2px;
|
||||||
|
}
|
||||||
|
}
|
@ -13,11 +13,14 @@ const STATE = AstalNetwork.DeviceState;
|
|||||||
const QuickView = () => {
|
const QuickView = () => {
|
||||||
const quickActions = QuickActions.QuickActions();
|
const quickActions = QuickActions.QuickActions();
|
||||||
|
|
||||||
|
|
||||||
return <button onClicked={() => quickActions.popup()} child={
|
return <button onClicked={() => quickActions.popup()} child={
|
||||||
<box>
|
<box>
|
||||||
|
<BatteryWidget></BatteryWidget>
|
||||||
<Audio></Audio>
|
<Audio></Audio>
|
||||||
|
<BluetoothWidget></BluetoothWidget>
|
||||||
<NetworkWidget></NetworkWidget>
|
<NetworkWidget></NetworkWidget>
|
||||||
|
<BrightnessWidget></BrightnessWidget>
|
||||||
|
<image iconName={"system-shutdown-symbolic"}></image>
|
||||||
{ quickActions }
|
{ quickActions }
|
||||||
</box>
|
</box>
|
||||||
}></button>
|
}></button>
|
||||||
@ -32,20 +35,18 @@ const NetworkWidget = () => {
|
|||||||
if ( state === AstalNetwork.State.CONNECTING ) {
|
if ( state === AstalNetwork.State.CONNECTING ) {
|
||||||
return 'chronometer-reset-symbolic';
|
return 'chronometer-reset-symbolic';
|
||||||
} else if ( state === AstalNetwork.State.CONNECTED_LOCAL || state === AstalNetwork.State.CONNECTED_SITE || state === AstalNetwork.State.CONNECTED_GLOBAL ) {
|
} else if ( state === AstalNetwork.State.CONNECTED_LOCAL || state === AstalNetwork.State.CONNECTED_SITE || state === AstalNetwork.State.CONNECTED_GLOBAL ) {
|
||||||
print( 'Wired connected' );
|
|
||||||
return 'network-wired-activated-symbolic';
|
return 'network-wired-activated-symbolic';
|
||||||
} else {
|
} else {
|
||||||
print( 'Unknown state' );
|
|
||||||
return 'paint-unknown-symbolic';
|
return 'paint-unknown-symbolic';
|
||||||
}
|
}
|
||||||
} )} cssClasses={[ 'network-widget' ]} visible={bind( network.wifi, 'state' ).as( state => state !== STATE.ACTIVATED )}></image>
|
} )} cssClasses={[ 'network-widget', 'quick-view-symbol' ]} visible={bind( network.wifi, 'state' ).as( state => state !== STATE.ACTIVATED )}></image>
|
||||||
<image iconName={bind( network.wifi, 'state' ).as( state => {
|
<image iconName={bind( network.wifi, 'state' ).as( state => {
|
||||||
if ( state === STATE.ACTIVATED ) {
|
if ( state === STATE.ACTIVATED ) {
|
||||||
return network.wifi.iconName
|
return network.wifi.iconName
|
||||||
} else {
|
} else {
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
} )} cssClasses={[ 'network-widget' ]} visible={bind( network.wifi, 'state' ).as( state => state === STATE.ACTIVATED )}></image>
|
} )} cssClasses={[ 'network-widget', 'quick-view-symbol' ]} visible={bind( network.wifi, 'state' ).as( state => state === STATE.ACTIVATED )}></image>
|
||||||
</box>
|
</box>
|
||||||
|
|
||||||
|
|
||||||
@ -53,15 +54,34 @@ const NetworkWidget = () => {
|
|||||||
|
|
||||||
const BluetoothWidget = () => {
|
const BluetoothWidget = () => {
|
||||||
const bluetooth = AstalBluetooth.get_default();
|
const bluetooth = AstalBluetooth.get_default();
|
||||||
const enabled = bind( bluetooth, "isPowered" );
|
const enabled = bind( bluetooth.adapter, "powered" );
|
||||||
const connected = bind( bluetooth, "isConnected" );
|
const connected = bind( bluetooth, "isConnected" );
|
||||||
|
|
||||||
|
// For each connected BT device, render status
|
||||||
|
return <box>
|
||||||
|
<box visible={enabled.as( e => e )}>
|
||||||
|
<image iconName={"bluetooth-active-symbolic"} visible={connected.as( c => c )}></image>
|
||||||
|
<image iconName={"bluetooth-disconnected-symbolic"} visible={connected.as( c => !c )}></image>
|
||||||
|
</box>
|
||||||
|
<image iconName={"bluetooth-disabled-symbolic"} visible={enabled.as( e => !e )}></image>
|
||||||
|
<box>
|
||||||
|
{bind( bluetooth, 'devices' ).as( devices => {
|
||||||
|
return devices.map( device => {
|
||||||
|
return <box visible={bind( device, 'connected' ).as( c => c )}>
|
||||||
|
<image iconName={bind( device, 'icon' ).as( icon => icon )}></image>
|
||||||
|
<label label={bind( device, 'batteryPercentage' ).as( n => { return n + '%' } ) }></label>
|
||||||
|
</box>
|
||||||
|
} );
|
||||||
|
} )}
|
||||||
|
</box>
|
||||||
|
</box>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
const BatteryWidget = () => {
|
const BatteryWidget = () => {
|
||||||
const battery = AstalBattery.get_default();
|
const battery = AstalBattery.get_default();
|
||||||
if ( battery.get_is_present() ) {
|
if ( battery.get_is_present() ) {
|
||||||
return <image iconName={battery.iconName}></image>
|
return <image iconName={bind( battery, 'iconName' ).as( icon => icon )} cssClasses={[ 'quick-view-symbol' ]}></image>
|
||||||
} else {
|
} else {
|
||||||
return <box></box>
|
return <box></box>
|
||||||
}
|
}
|
||||||
@ -70,31 +90,27 @@ const BatteryWidget = () => {
|
|||||||
|
|
||||||
|
|
||||||
const BrightnessWidget = () => {
|
const BrightnessWidget = () => {
|
||||||
// TODO: Finish (detect if there is a controllable screen)
|
|
||||||
const brightness = Brightness.get_default();
|
const brightness = Brightness.get_default();
|
||||||
const screen_brightness = bind( brightness, "screen" );
|
const screen_brightness = bind( brightness, "screen" );
|
||||||
|
|
||||||
return <label label={"🌣" + screen_brightness}></label>
|
return <label label={"🌣" + screen_brightness} visible={bind( brightness, 'screenAvailable' )} cssClasses={[ 'quick-view-symbol' ]}></label>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
const Audio = () => {
|
const Audio = () => {
|
||||||
const wireplumber = AstalWp.get_default();
|
const wireplumber = AstalWp.get_default();
|
||||||
if ( wireplumber ) {
|
if ( wireplumber ) {
|
||||||
const volume_speakers = bind( wireplumber.defaultSpeaker, 'volume' );
|
|
||||||
|
|
||||||
return <box orientation={Gtk.Orientation.HORIZONTAL}>
|
return <box orientation={Gtk.Orientation.HORIZONTAL}>
|
||||||
<image iconName={wireplumber.defaultSpeaker.volumeIcon}></image>
|
<image iconName={bind(wireplumber.defaultSpeaker, 'volumeIcon').as( icon => icon )} cssClasses={[ 'quick-view-symbol' ]}></image>
|
||||||
<image iconName={wireplumber.defaultMicrophone.volumeIcon}></image>
|
<image iconName={bind(wireplumber.defaultMicrophone, 'volumeIcon').as( icon => icon )} cssClasses={[ 'quick-view-symbol' ]}></image>
|
||||||
<label label={volume_speakers.as( v => { return "" + v } ) }></label>
|
|
||||||
<label label={wireplumber.defaultSpeaker.get_name()}></label>
|
|
||||||
</box>
|
</box>
|
||||||
} else {
|
} else {
|
||||||
print( '[ WirePlumber ] Could not connect, Audio support in bar will be missing' );
|
print( '[ WirePlumber ] Could not connect, Audio support in bar will be missing' );
|
||||||
|
return <image iconName={"action-unavailable-symbolic"}></image>;
|
||||||
}
|
}
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// cssClasses={[ 'quick-view-symbol' ]}
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
QuickView
|
QuickView
|
||||||
|
@ -1,7 +1,46 @@
|
|||||||
import { exec, GLib } from "astal"
|
import { exec, GLib, interval, Variable } from "astal"
|
||||||
|
import { Gtk } from "astal/gtk4";
|
||||||
|
import AstalBattery from "gi://AstalBattery?version=0.1";
|
||||||
|
|
||||||
|
|
||||||
|
const FETCH_INTERVAL = 2000;
|
||||||
|
|
||||||
|
|
||||||
|
const cpuUtil = Variable( '0%' );
|
||||||
|
const ramUtil = Variable( '0%' );
|
||||||
|
const ramUsed = Variable( '0MiB' );
|
||||||
|
const gpuUtil = Variable( '0%' );
|
||||||
|
let gpuName = 'card1';
|
||||||
|
let enabled = false;
|
||||||
|
|
||||||
|
const refreshStats = (): Stats => {
|
||||||
|
gpuName = exec( `/bin/bash -c "ls /sys/class/drm/ | grep '^card[0-9]*$'"` );
|
||||||
|
const cpuNameInSensors = 'CPUTIN'
|
||||||
|
const stats = {
|
||||||
|
kernel: exec( 'uname -sr' ),
|
||||||
|
netSpeed: exec( `/bin/bash -c "interface=$(ip route get 8.8.8.8 | awk '{print $5; exit}') && cat \"/sys/class/net/$interface/speed\""` ),
|
||||||
|
cpuTemp: exec( `/bin/bash -c "sensors | grep -m1 ${cpuNameInSensors} | awk '{print $2}'"` ),
|
||||||
|
cpuClk: exec( `awk '/cpu MHz/ {sum+=$4; ++n} END {print sum/n " MHz"}' /proc/cpuinfo` ),
|
||||||
|
gpuTemp: exec( `/bin/bash -c "sensors | grep -E 'edge' | awk '{print $2}'"` ),
|
||||||
|
gpuClk: exec( `/bin/bash -c "cat /sys/class/drm/${gpuName}/device/pp_dpm_sclk | grep '\\*' | awk '{print $2 $3}'"` ),
|
||||||
|
vram: Math.round( parseInt( exec( `cat /sys/class/drm/${gpuName}/device/mem_info_vram_used` ) ) / 1024 / 1024 ) + 'MiB',
|
||||||
|
availableVRAM: Math.round( parseInt( exec( `cat /sys/class/drm/${gpuName}/device/mem_info_vram_total` ) ) / 1024 / 1024 ) + 'MiB',
|
||||||
|
}
|
||||||
|
|
||||||
|
return stats;
|
||||||
|
}
|
||||||
|
|
||||||
|
const systemStats: Variable<Stats> = Variable( refreshStats() );
|
||||||
|
|
||||||
|
|
||||||
|
const availableFeatures = {
|
||||||
|
cpu: true,
|
||||||
|
ram: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
const featureTest = () => {
|
const featureTest = () => {
|
||||||
// Check if awk is available
|
// Check if awk & sed are available
|
||||||
try {
|
try {
|
||||||
exec( 'awk -V' );
|
exec( 'awk -V' );
|
||||||
exec( 'sed --version' );
|
exec( 'sed --version' );
|
||||||
@ -11,44 +50,86 @@ const featureTest = () => {
|
|||||||
enabled = false;
|
enabled = false;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check if mpstat is available
|
||||||
try {
|
try {
|
||||||
exec( 'mpstat' );
|
exec( 'mpstat -V' );
|
||||||
} catch ( e ) {
|
} catch ( e ) {
|
||||||
availableFeatures.cpu = false;
|
availableFeatures.cpu = false;
|
||||||
printerr( '[ SysInfo ] Feature Test for CPU info failed. mpstat from the sysstat package missing!' );
|
printerr( '[ SysInfo ] Feature Test for CPU info failed. mpstat from the sysstat package missing!' );
|
||||||
}
|
}
|
||||||
|
|
||||||
// Battery... acpi might be present, but potentially no bat
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let enabled = false;
|
const info = () => {
|
||||||
|
return <box vertical valign={Gtk.Align.START}>
|
||||||
const availableFeatures = {
|
<label label={ramUsed( used => {
|
||||||
cpu: true,
|
return used + `(${ ramUtil.get() }%)`;
|
||||||
ram: true,
|
} )}></label>
|
||||||
bat: true,
|
<label label={systemStats( stats => {
|
||||||
|
return `CPU: ${stats.cpuTemp}, ${stats.cpuClk}
|
||||||
|
GPU: ${stats.gpuTemp}, ${stats.gpuClk} (${stats.vram} / ${stats.availableVRAM})
|
||||||
|
Network: ${stats.netSpeed}
|
||||||
|
Kernel: ${stats.kernel}` } ) }></label>
|
||||||
|
</box>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const SystemInformationPanel = () => {
|
||||||
|
const popover = new Gtk.Popover();
|
||||||
|
|
||||||
|
popover.set_child( info() );
|
||||||
|
|
||||||
|
return popover;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
const sysInfoFetcher = () => {
|
const sysInfoFetcher = () => {
|
||||||
if ( enabled ) {
|
if ( enabled ) {
|
||||||
if ( availableFeatures.cpu ) {
|
if ( availableFeatures.cpu ) {
|
||||||
const cpuUtil = exec( 'mpstat | awk "/all/ {print(100 - $NF)"}' );
|
cpuUtil.set( '' + Math.round( parseFloat( exec( `/bin/fish -c cpu-utilization` ) ) ) );
|
||||||
}
|
}
|
||||||
if ( availableFeatures.ram ) {
|
if ( availableFeatures.ram ) {
|
||||||
const ramUtil = exec( `free | awk '/Mem/ { printf("%.2f\\n", ($3/$2)*100) }'` );
|
ramUtil.set( '' + Math.round( parseFloat( exec( `/bin/bash -c "free | awk '/Mem/ { printf(\\"%.2f\\\\n\\", ($3/$2)*100) }'"` ) ) ) );
|
||||||
}
|
ramUsed.set( exec( `/bin/bash -c \"free -h | awk '/^Mem:/ {print $3 \\" used of \\" $2}'\"` ).replaceAll( 'Gi', 'GiB' ).replaceAll( 'Mi', 'MiB' ) );
|
||||||
if ( availableFeatures.bat ) {
|
|
||||||
const acpi = exec( `acpi -i | grep 'Battery'` );
|
|
||||||
// TODO: Parse acpi output
|
|
||||||
}
|
}
|
||||||
|
gpuUtil.set( exec( 'cat /sys/class/drm/card1/device/gpu_busy_percent' ) );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const panel = SystemInformationPanel();
|
||||||
|
|
||||||
|
|
||||||
const SystemInfo = () => {
|
const SystemInfo = () => {
|
||||||
return <box></box>
|
featureTest();
|
||||||
|
|
||||||
|
const openSysInfo = async () => {
|
||||||
|
panel.popup();
|
||||||
|
systemStats.set( refreshStats() );
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( enabled ) {
|
||||||
|
sysInfoFetcher();
|
||||||
|
interval( FETCH_INTERVAL, sysInfoFetcher );
|
||||||
|
|
||||||
|
return <button onClicked={() => openSysInfo() } child={
|
||||||
|
<box tooltipText={ ramUsed( v => v ) }>
|
||||||
|
<image iconName={"power-profile-performance-symbolic"} marginEnd={1}></image>
|
||||||
|
<label label={ cpuUtil( util => util ) } marginEnd={5}></label>
|
||||||
|
<image iconName={"histogram-symbolic"}></image>
|
||||||
|
<label label={ ramUtil( util => util ) }></label>
|
||||||
|
<image iconName={"show-gpu-effects-symbolic"}></image>
|
||||||
|
<label label={ gpuUtil( util => util ) }></label>
|
||||||
|
{ panel }
|
||||||
|
</box>
|
||||||
|
}></button>
|
||||||
|
} else {
|
||||||
|
return <image iconName={"action-unavailable-symbolic"}></image>
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
SystemInfo
|
SystemInfo,
|
||||||
|
panel
|
||||||
}
|
}
|
||||||
|
10
config/astal/components/bar/ui/modules/stats.d.ts
vendored
Normal file
10
config/astal/components/bar/ui/modules/stats.d.ts
vendored
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
interface Stats {
|
||||||
|
kernel: string;
|
||||||
|
netSpeed: string;
|
||||||
|
cpuTemp: string;
|
||||||
|
cpuClk: string;
|
||||||
|
gpuTemp: string;
|
||||||
|
gpuClk: string;
|
||||||
|
vram: string;
|
||||||
|
availableVRAM: string;
|
||||||
|
}
|
@ -12,7 +12,7 @@ export default class Brightness extends GObject.Object {
|
|||||||
static get_default() {
|
static get_default() {
|
||||||
if (!this.instance)
|
if (!this.instance)
|
||||||
this.instance = new Brightness()
|
this.instance = new Brightness()
|
||||||
|
|
||||||
return this.instance
|
return this.instance
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -20,6 +20,10 @@ export default class Brightness extends GObject.Object {
|
|||||||
#kbd = get(`--device ${kbd} get`)
|
#kbd = get(`--device ${kbd} get`)
|
||||||
#screenMax = get("max")
|
#screenMax = get("max")
|
||||||
#screen = get("get") / (get("max") || 1)
|
#screen = get("get") / (get("max") || 1)
|
||||||
|
#screenAvailable = false
|
||||||
|
|
||||||
|
@property(Boolean)
|
||||||
|
get screenAvailable() { return this.#screenAvailable }
|
||||||
|
|
||||||
@property(Number)
|
@property(Number)
|
||||||
get kbd() { return this.#kbd }
|
get kbd() { return this.#kbd }
|
||||||
@ -67,5 +71,12 @@ export default class Brightness extends GObject.Object {
|
|||||||
this.#kbd = Number(v) / this.#kbdMax
|
this.#kbd = Number(v) / this.#kbdMax
|
||||||
this.notify("kbd")
|
this.notify("kbd")
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// Check if there is a screen available
|
||||||
|
try {
|
||||||
|
get( 'g -c backlight' );
|
||||||
|
} catch ( _ ) {
|
||||||
|
this.#screenAvailable = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
0
config/astal/components/launcher/Launcher.tsx
Normal file
0
config/astal/components/launcher/Launcher.tsx
Normal file
@ -91,7 +91,6 @@ const findNotificationByNotificationID = ( id: number ): number => {
|
|||||||
* @param id The notifd ID of the notification
|
* @param id The notifd ID of the notification
|
||||||
*/
|
*/
|
||||||
const addNotification = ( id: number ): void => {
|
const addNotification = ( id: number ): void => {
|
||||||
print( '[ Notifications ] Notification with id ' + id + ' added.' );
|
|
||||||
const currIndex = Notifications.length;
|
const currIndex = Notifications.length;
|
||||||
Notifications.push( {
|
Notifications.push( {
|
||||||
notifdID: id,
|
notifdID: id,
|
||||||
|
12
config/astal/components/notifications/helper.ts
Normal file
12
config/astal/components/notifications/helper.ts
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
import { Gtk, Gdk } from "astal/gtk4";
|
||||||
|
import { GLib } from "astal";
|
||||||
|
|
||||||
|
export const isIcon = (icon: string) => {
|
||||||
|
const display = Gdk.Display.get_default();
|
||||||
|
if (!display) return false;
|
||||||
|
const iconTheme = Gtk.IconTheme.get_for_display(display);
|
||||||
|
return iconTheme.has_icon(icon);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const fileExists = (path: string) =>
|
||||||
|
GLib.file_test(path, GLib.FileTest.EXISTS);
|
24
config/astal/components/notifications/icon.tsx
Normal file
24
config/astal/components/notifications/icon.tsx
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
import { Gtk } from "astal/gtk4";
|
||||||
|
import Notifd from "gi://AstalNotifd";
|
||||||
|
import { fileExists, isIcon } from "./helper";
|
||||||
|
|
||||||
|
|
||||||
|
export function NotificationIcon(notification: Notifd.Notification) {
|
||||||
|
if ( notification.image || notification.appIcon || notification.desktopEntry) {
|
||||||
|
const icon = notification.image || notification.appIcon || notification.desktopEntry;
|
||||||
|
if (fileExists(icon)) {
|
||||||
|
return (
|
||||||
|
<box expand={false} valign={Gtk.Align.CENTER}>
|
||||||
|
<image file={icon} />
|
||||||
|
</box>
|
||||||
|
);
|
||||||
|
} else if (isIcon(icon)) {
|
||||||
|
return (
|
||||||
|
<box expand={false} valign={Gtk.Align.CENTER}>
|
||||||
|
<image iconName={icon} />
|
||||||
|
</box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
@ -13,7 +13,7 @@ window.NotificationHandler {
|
|||||||
all: unset;
|
all: unset;
|
||||||
}
|
}
|
||||||
|
|
||||||
box.Notification {
|
box.notification {
|
||||||
|
|
||||||
&:first-child {
|
&:first-child {
|
||||||
margin-top: 1rem;
|
margin-top: 1rem;
|
||||||
|
@ -1,103 +1,113 @@
|
|||||||
// From astal examples
|
// From astal examples
|
||||||
|
|
||||||
import { GLib } from "astal"
|
import { bind, GLib } from "astal";
|
||||||
import { Gtk } from "astal/gtk4"
|
import { Gtk } from "astal/gtk4";
|
||||||
import Notifd from "gi://AstalNotifd"
|
import Notifd from "gi://AstalNotifd";
|
||||||
|
import { NotificationIcon } from "./icon";
|
||||||
// import Pango from "gi://Pango?version=1.0"
|
// import Pango from "gi://Pango?version=1.0"
|
||||||
|
|
||||||
const fileExists = (path: string) =>
|
const fileExists = (path: string) => GLib.file_test(path, GLib.FileTest.EXISTS);
|
||||||
GLib.file_test(path, GLib.FileTest.EXISTS)
|
|
||||||
|
|
||||||
const time = (time: number, format = "%H:%M") => GLib.DateTime
|
const time = (time: number, format = "%H:%M") =>
|
||||||
.new_from_unix_local(time)
|
GLib.DateTime.new_from_unix_local(time).format(format)!;
|
||||||
.format(format)!
|
|
||||||
|
|
||||||
const urgency = (n: Notifd.Notification) => {
|
const urgency = (n: Notifd.Notification) => {
|
||||||
const { LOW, NORMAL, CRITICAL } = Notifd.Urgency
|
const { LOW, NORMAL, CRITICAL } = Notifd.Urgency;
|
||||||
// match operator when?
|
// match operator when?
|
||||||
switch (n.urgency) {
|
switch (n.urgency) {
|
||||||
case LOW: return "low"
|
case LOW:
|
||||||
case CRITICAL: return "critical"
|
return "low";
|
||||||
|
case CRITICAL:
|
||||||
|
return "critical";
|
||||||
case NORMAL:
|
case NORMAL:
|
||||||
default: return "normal"
|
default:
|
||||||
|
return "normal";
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
delete: ( id: number ) => void;
|
delete: (id: number) => void;
|
||||||
notification: Notifd.Notification
|
notification: Notifd.Notification;
|
||||||
id: number,
|
id: number;
|
||||||
}
|
};
|
||||||
|
|
||||||
export default function Notification(props: Props) {
|
export default function Notification(props: Props) {
|
||||||
const { notification: n, id: id, delete: del } = props
|
const { notification: n, id: id, delete: del } = props;
|
||||||
const { START, CENTER, END } = Gtk.Align
|
const { START, CENTER, END } = Gtk.Align;
|
||||||
|
|
||||||
return <box vertical
|
return (
|
||||||
cssClasses={['Notification', `${urgency(n)}`]}>
|
<box vertical cssClasses={["notification", `${urgency(n)}`]}>
|
||||||
<box cssClasses={["header"]}>
|
<box cssClasses={["header"]}>
|
||||||
{(n.appIcon || n.desktopEntry) ? <Gtk.Image
|
{n.appIcon || n.desktopEntry ? (
|
||||||
cssClasses={["app-icon"]}
|
<Gtk.Image
|
||||||
visible={Boolean(n.appIcon || n.desktopEntry)}
|
cssClasses={["app-icon"]}
|
||||||
iconName={n.appIcon || n.desktopEntry}
|
visible={Boolean(n.appIcon || n.desktopEntry)}
|
||||||
/> : <image iconName={'window-close-symbolic'}></image>}
|
iconName={n.appIcon || n.desktopEntry}
|
||||||
<label
|
/>
|
||||||
cssClasses={["app-name"]}
|
) : (
|
||||||
halign={START}
|
<image iconName={"window-close-symbolic"}></image>
|
||||||
// ellipsize={Pango.EllipsizeMode.END}
|
)}
|
||||||
label={n.appName || "Unknown"}
|
|
||||||
/>
|
|
||||||
<label
|
|
||||||
cssClasses={["time"]}
|
|
||||||
hexpand
|
|
||||||
halign={END}
|
|
||||||
label={time(n.time)}
|
|
||||||
/>
|
|
||||||
<button onClicked={() => { del( id ) }}>
|
|
||||||
<image iconName="window-close-symbolic" />
|
|
||||||
</button>
|
|
||||||
</box>
|
|
||||||
<Gtk.Separator visible />
|
|
||||||
<box cssClasses={["content"]}>
|
|
||||||
{n.image && fileExists(n.image) ? <box
|
|
||||||
valign={START}
|
|
||||||
cssClasses={["image"]}>
|
|
||||||
<image file={n.image}></image>
|
|
||||||
</box>
|
|
||||||
: <box></box>}
|
|
||||||
{n.image ? <box
|
|
||||||
expand={false}
|
|
||||||
valign={START}
|
|
||||||
cssClasses={["icon-image"]}>
|
|
||||||
<image iconName={n.image} expand halign={CENTER} valign={CENTER} />
|
|
||||||
</box>
|
|
||||||
: <box></box>}
|
|
||||||
<box vertical>
|
|
||||||
<label
|
<label
|
||||||
cssClasses={["summary"]}
|
cssClasses={["app-name"]}
|
||||||
halign={START}
|
halign={START}
|
||||||
xalign={0}
|
|
||||||
label={n.summary}
|
|
||||||
// ellipsize={Pango.EllipsizeMode.END}
|
// ellipsize={Pango.EllipsizeMode.END}
|
||||||
|
label={n.appName || "Unknown"}
|
||||||
/>
|
/>
|
||||||
{n.body ? <label
|
<label
|
||||||
cssClasses={["body"]}
|
cssClasses={["time"]}
|
||||||
wrap
|
|
||||||
useMarkup
|
|
||||||
halign={START}
|
|
||||||
xalign={0}
|
|
||||||
label={n.body}
|
|
||||||
/> : <label></label>}
|
|
||||||
</box>
|
|
||||||
</box>
|
|
||||||
{n.get_actions().length > 0 ? <box cssClasses={["actions"]}>
|
|
||||||
{n.get_actions().map(({ label, id }) => (
|
|
||||||
<button
|
|
||||||
hexpand
|
hexpand
|
||||||
onClicked={() => n.invoke(id)}>
|
halign={END}
|
||||||
<label label={label} halign={CENTER} hexpand />
|
label={time(n.time)}
|
||||||
</button>
|
/>
|
||||||
))}
|
<button
|
||||||
</box> : <box></box>}
|
onClicked={() => {
|
||||||
</box>
|
del(id);
|
||||||
|
}}
|
||||||
|
child={<image iconName="window-close-symbolic" />}
|
||||||
|
></button>
|
||||||
|
</box>
|
||||||
|
<Gtk.Separator visible />
|
||||||
|
<box cssClasses={["content"]}>
|
||||||
|
<box
|
||||||
|
cssClasses={["image"]}
|
||||||
|
visible={Boolean(NotificationIcon(n))}
|
||||||
|
halign={CENTER}
|
||||||
|
valign={CENTER}
|
||||||
|
vexpand={true}
|
||||||
|
>
|
||||||
|
{NotificationIcon(n)}
|
||||||
|
</box>
|
||||||
|
<box vertical>
|
||||||
|
<label
|
||||||
|
cssClasses={["summary"]}
|
||||||
|
halign={START}
|
||||||
|
xalign={0}
|
||||||
|
useMarkup
|
||||||
|
label={bind(n, "summary")}
|
||||||
|
// ellipsize={Pango.EllipsizeMode.END}
|
||||||
|
/>
|
||||||
|
{n.body && (
|
||||||
|
<label
|
||||||
|
cssClasses={["body"]}
|
||||||
|
valign={CENTER}
|
||||||
|
wrap={true}
|
||||||
|
maxWidthChars={50}
|
||||||
|
label={bind(n, "body")}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</box>
|
||||||
|
</box>
|
||||||
|
{n.get_actions().length > 0 ? (
|
||||||
|
<box cssClasses={["actions"]}>
|
||||||
|
{n.get_actions().map(({ label, id }) => (
|
||||||
|
<button hexpand onClicked={() => n.invoke(id)}>
|
||||||
|
<label label={label} halign={CENTER} hexpand />
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
</box>
|
||||||
|
) : (
|
||||||
|
<box></box>
|
||||||
|
)}
|
||||||
|
</box>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
@ -1,21 +1,12 @@
|
|||||||
// https://gitlab.gnome.org/GNOME/gtk/-/blob/gtk-3-24/gtk/theme/Adwaita/_colors-public.scss
|
|
||||||
@use './components/notifications/notifications.scss';
|
@use './components/notifications/notifications.scss';
|
||||||
$fg-color: #{"@theme_fg_color"};
|
@use './components/bar/ui/bar.scss';
|
||||||
$bg-color: #{"@theme_bg_color"};
|
@use './components/bar/ui/QuickActions/quickactions.scss';
|
||||||
|
|
||||||
window.Bar {
|
* {
|
||||||
background: transparent;
|
font-size: 1rem;
|
||||||
color: $fg-color;
|
}
|
||||||
font-weight: bold;
|
|
||||||
|
empty {
|
||||||
>centerbox {
|
min-width: 0;
|
||||||
background: $bg-color;
|
background-color: transparent;
|
||||||
border-radius: 10px;
|
|
||||||
margin: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
button {
|
|
||||||
border-radius: 8px;
|
|
||||||
margin: 2px;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
2
install
2
install
@ -11,7 +11,7 @@ read -p "Choose the configs to install, Laptop or Desktop (l/D): " platform
|
|||||||
yay -S hyprland hypridle hyprfreeze hyprlock plymouth aylurs-gtk-shell-git brightnessctl pulsemixer xdg-desktop-portal-hyprland
|
yay -S hyprland hypridle hyprfreeze hyprlock plymouth aylurs-gtk-shell-git brightnessctl pulsemixer xdg-desktop-portal-hyprland
|
||||||
|
|
||||||
# Audio, drivers, tools
|
# Audio, drivers, tools
|
||||||
yay -S pipewire pipewire-alsa pipewire-pulse pipewire-jack mesa fish thunar yazi wireplumber grimblast wl-clipboard wget vimiv zoxide trash-cli fzf ouch zathura
|
yay -S pipewire pipewire-alsa pipewire-pulse pipewire-jack mesa fish thunar yazi wireplumber grimblast wl-clipboard wget vimiv zoxide trash-cli fzf ouch zathura sensors radeontop lm-sensors
|
||||||
|
|
||||||
# Set up yazi
|
# Set up yazi
|
||||||
ya pack -a ndtoan96/ouch
|
ya pack -a ndtoan96/ouch
|
||||||
|
20
scripts/cpu-utilization
Executable file
20
scripts/cpu-utilization
Executable file
@ -0,0 +1,20 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Get first snapshot
|
||||||
|
read cpu user nice system idle iowait irq softirq steal guest < /proc/stat
|
||||||
|
total1=$((user+nice+system+idle+iowait+irq+softirq+steal))
|
||||||
|
idle1=$idle
|
||||||
|
|
||||||
|
sleep 0.5
|
||||||
|
|
||||||
|
# Get second snapshot
|
||||||
|
read cpu user nice system idle iowait irq softirq steal guest < /proc/stat
|
||||||
|
total2=$((user+nice+system+idle+iowait+irq+softirq+steal))
|
||||||
|
idle2=$idle
|
||||||
|
|
||||||
|
# Calculate usage
|
||||||
|
total_diff=$((total2 - total1))
|
||||||
|
idle_diff=$((idle2 - idle1))
|
||||||
|
cpu_usage=$((100 * (total_diff - idle_diff) / total_diff))
|
||||||
|
|
||||||
|
echo "$cpu_usage"
|
Loading…
x
Reference in New Issue
Block a user