Compare commits

...

10 Commits

13 changed files with 483 additions and 211 deletions

8
.prettierrc.json Normal file
View File

@@ -0,0 +1,8 @@
{
"tabWidth": 2,
"printWidth": 90,
"useTabs": false,
"proseWrap": "always",
"endOfLine": "lf",
"embeddedLanguageFormatting": "auto"
}

20
Bar.qml
View File

@@ -1,9 +1,12 @@
pragma ComponentBehavior: Bound
import QtQuick import QtQuick
import QtQuick.Effects import QtQuick.Effects
import QtQuick.Layouts import QtQuick.Layouts
import Quickshell import Quickshell
import Quickshell.Hyprland import Quickshell.Hyprland
import Quickshell.Wayland
import "./Bar" import "./Bar"
import "./Services/"
import "Color.js" as Colors import "Color.js" as Colors
Scope { Scope {
@@ -25,6 +28,8 @@ Scope {
required property var modelData required property var modelData
property HyprlandMonitor monitor: Hyprland.monitorFor(modelData) property HyprlandMonitor monitor: Hyprland.monitorFor(modelData)
WlrLayershell.keyboardFocus: monitor.focused == true ? IpcManager.focusState :
WlrKeyboardFocus.None
color: "transparent" color: "transparent"
exclusionMode: ExclusionMode.Normal exclusionMode: ExclusionMode.Normal
exclusiveZone: barExclusionZone exclusiveZone: barExclusionZone
@@ -32,6 +37,21 @@ Scope {
mask: maskRegion mask: maskRegion
screen: modelData screen: modelData
Rectangle {
id: blurOverlay
color: Colors.base
implicitHeight: win.modelData.height
implicitWidth: win.modelData.width
opacity: IpcManager.logoutMenuOpen == true ? 0.8 : 0
Behavior on opacity {
NumberAnimation {
duration: 200
}
}
}
Region { Region {
id: maskRegion id: maskRegion

View File

@@ -18,61 +18,16 @@ Container {
animOffset: 150 animOffset: 150
boxColor: Colors.green boxColor: Colors.green
boxHeight: 25
boxHeightOpened: root.batteryDisplayHeight
boxRadius: radius boxRadius: radius
boxWidth: 60
boxWidthOpened: 410
defaultItem: icon defaultItem: icon
exclusiveMonitor: root.monitor exclusiveMonitor: root.monitor
exclusiveToScreen: true exclusiveToScreen: true
forceHidden: !BatteryManager.hasLaptopBattery forceHidden: !BatteryManager.hasLaptopBattery
openedItem: batteryDisplay
states: [
State {
name: "closed"
when: root.hovered == false && root.opened == false
PropertyChanges {
root.boxHeight: 25
root.boxRadius: root.radius
root.boxWidth: 60
}
},
State {
name: "hovered"
when: root.hovered == true && root.opened == false
PropertyChanges {
root.boxHeight: 28
root.boxWidth: 64
}
},
State {
name: "opened"
when: root.opened == true && root.hovered == true
PropertyChanges {
root.boxHeight: root.batteryDisplayHeight
root.boxRadius: 12
root.boxWidth: 450
}
StateChangeScript {
script: {
stack.replace(batteryDisplay);
}
}
}
]
hover.onHoveredChanged: {
if (hover.hovered == false) {
root.opened = false;
if (root.state != "icon") {
root.stack.replace(icon);
}
}
}
tap.onTapped: {
root.opened = true;
}
Component { Component {
id: icon id: icon

View File

@@ -14,67 +14,19 @@ Container {
property double devicePlaqueHeight: 60 property double devicePlaqueHeight: 60
property double devicePlaqueSpacing: 4 property double devicePlaqueSpacing: 4
required property HyprlandMonitor monitor required property HyprlandMonitor monitor
property bool opened: false
animOffset: 200 animOffset: 200
boxColor: Colors.peach boxColor: Colors.peach
boxHeight: 25 boxHeight: 25
boxHeightOpened: (root.devicePlaqueHeight * BluetoothManager.getConnectedDevicesList().length) + (
root.devicePlaqueSpacing * (BluetoothManager.getConnectedDevicesList().length + 1)) + 2
boxWidth: 33 boxWidth: 33
boxWidthOpened: 350
defaultItem: icon defaultItem: icon
exclusiveMonitor: root.monitor exclusiveMonitor: root.monitor
exclusiveToScreen: true exclusiveToScreen: true
forceHidden: !BluetoothManager.anyConnected forceHidden: !BluetoothManager.anyConnected
openedItem: deviceList
states: [
State {
name: "closed"
when: root.hovered == false && root.opened == false
PropertyChanges {
root.boxHeight: 25
root.boxRadius: 9
root.boxWidth: 33
}
},
State {
name: "hovered"
when: root.hovered == true && root.opened == false
PropertyChanges {
root.boxHeight: 28
root.boxWidth: 35
}
},
State {
name: "opened"
when: root.opened == true && root.hovered == true
PropertyChanges {
root.boxHeight: (root.devicePlaqueHeight * BluetoothManager.getConnectedDevicesList().length) + (
root.devicePlaqueSpacing * (BluetoothManager.getConnectedDevicesList().length + 1)) + 2
root.boxRadius: 12
root.boxWidth: 350
}
StateChangeScript {
script: {
stack.replace(deviceList);
}
}
}
]
hover.onHoveredChanged: {
if (hover.hovered == false) {
root.opened = false;
if (root.state != "icon") {
root.stack.replace(icon);
}
}
}
tap.onTapped: {
root.opened = true;
}
Component { Component {
id: icon id: icon

View File

@@ -13,66 +13,18 @@ Container {
required property HyprlandMonitor monitor required property HyprlandMonitor monitor
property double networkDisplayHeight: 80 property double networkDisplayHeight: 80
property bool opened: false
animOffset: 250 animOffset: 250
boxColor: Colors.red boxColor: Colors.red
boxHeight: 25 boxHeight: 25
boxHeightOpened: root.networkDisplayHeight
boxWidth: 33 boxWidth: 33
boxWidthOpened: 350
defaultItem: icon defaultItem: icon
exclusiveMonitor: root.monitor exclusiveMonitor: root.monitor
exclusiveToScreen: true exclusiveToScreen: true
forceHidden: !NetworkManager.connectedWifi forceHidden: !NetworkManager.connectedWifi
openedItem: networkDisplay
states: [
State {
name: "closed"
when: root.hovered == false && root.opened == false
PropertyChanges {
root.boxHeight: 25
root.boxRadius: 9
root.boxWidth: 33
}
},
State {
name: "hovered"
when: root.hovered == true && root.opened == false
PropertyChanges {
root.boxHeight: 28
root.boxWidth: 35
}
},
State {
name: "opened"
when: root.opened == true && root.hovered == true
PropertyChanges {
root.boxHeight: root.networkDisplayHeight
root.boxRadius: 12
root.boxWidth: 350
}
StateChangeScript {
script: {
stack.replace(wifiInfo);
}
}
}
]
hover.onHoveredChanged: {
if (hover.hovered == false) {
root.opened = false;
if (root.state != "icon") {
root.stack.replace(icon);
}
}
}
tap.onTapped: {
root.opened = true;
}
Component { Component {
id: icon id: icon
@@ -86,7 +38,7 @@ Container {
} }
Component { Component {
id: wifiInfo id: networkDisplay
Item { Item {
id: networkDisplayRoot id: networkDisplayRoot
@@ -142,7 +94,8 @@ Container {
color: Colors.subtext1 color: Colors.subtext1
fontSize: 12 fontSize: 12
fontWeight: 5 fontWeight: 5
text: "Connected on: " + networkColumn.networkInfo["adapterName"] text: networkColumn.networkInfo["ipAddress"] + " on "
+ networkColumn.networkInfo["adapterName"]
} }
StyledText { StyledText {

View File

@@ -15,6 +15,10 @@ Container {
id: root id: root
property Notification latestNotif property Notification latestNotif
property bool logout: false
property double logoutHeight: 100
property double logoutMargin: 7
property double logoutSpacing: 5
required property HyprlandMonitor monitor required property HyprlandMonitor monitor
property bool mpris: false property bool mpris: false
property double mprisHeight: 60 property double mprisHeight: 60
@@ -22,7 +26,6 @@ Container {
property double notificationHeight: 100 property double notificationHeight: 100
property double notificationWidth: 350 property double notificationWidth: 350
property bool notified: false property bool notified: false
property bool overriden: false
boxColor: Colors.mauve boxColor: Colors.mauve
defaultItem: time defaultItem: time
@@ -30,43 +33,6 @@ Container {
exclusiveToScreen: true exclusiveToScreen: true
states: [ states: [
State {
name: "closed"
when: root.hovered == false && root.overriden == false
PropertyChanges {
root.boxHeight: 28
root.boxRadius: 9
root.boxWidth: 100
}
StateChangeScript {
script: {
if (!root.stack.currentItem) {
return;
}
if (root.stack.currentItem.component != "time") {
root.stack.replace(time);
}
}
}
},
State {
name: "hovered"
when: root.hovered == true && root.overriden == false
PropertyChanges {
root.boxHeight: MprisManager.getPlaying() == false ? 32 : root.mprisHeight
root.boxRadius: 12
root.boxWidth: MprisManager.getPlaying() == false ? 107 : root.mprisWidth
}
StateChangeScript {
script: {
root.stack.replace(MprisManager.getPlaying() == true ? mprisToast : time);
}
}
},
State { State {
name: "notified" name: "notified"
when: root.notified == true when: root.notified == true
@@ -93,7 +59,7 @@ Container {
root.boxHeight: root.mprisHeight root.boxHeight: root.mprisHeight
root.boxRadius: 12 root.boxRadius: 12
root.boxWidth: root.mprisWidth root.boxWidth: root.mprisWidth
root.visibleTopMargin: 5 root.visibleTopMargin: root.hovered == true ? 0 : 5
} }
StateChangeScript { StateChangeScript {
@@ -102,9 +68,51 @@ Container {
mprisTimer.restart(); mprisTimer.restart();
} }
} }
},
State {
name: "logout"
when: root.logout == true
PropertyChanges {
root.boxHeight: root.logoutHeight
root.boxRadius: 13
root.boxWidth: ((root.logoutHeight - (root.logoutMargin * 2)) * 5) + (root.logoutMargin * 2) + (
root.logoutSpacing * 4)
root.visibleTopMargin: 10
}
StateChangeScript {
script: {
root.stack.replace(logoutMenu);
}
}
} }
] ]
onHoveredChanged: {
if (root.hovered == true && MprisManager.getPlaying() == true) {
root.overriden = true;
root.mpris = true;
} else {
root.overriden = false;
root.mpris = true;
}
}
Connections {
function onCloseLogoutMenu() {
root.overriden = false;
root.logout = false;
}
function onLogoutMenu() {
root.overriden = true;
root.logout = true;
}
target: IpcManager
}
Connections { Connections {
function onNewNotification() { function onNewNotification() {
notification => { notification => {
@@ -155,6 +163,98 @@ Container {
} }
} }
Component {
id: logoutMenu
Item {
id: logoutMenuRoot
property var logoutModel: [
{
color: Colors.red,
text: "s",
key: Qt.Key_S
},
{
color: Colors.peach,
text: "e",
key: Qt.Key_E
},
{
color: Colors.yellow,
text: "r",
key: Qt.Key_R
},
{
color: Colors.green,
text: "l",
key: Qt.Key_L
},
{
color: Colors.sky,
text: "u",
key: Qt.Key_U
},
]
Component.onCompleted: {
forceActiveFocus();
}
Keys.onPressed: event => {
if (event.key == Qt.Key_Escape) {
IpcManager.closeLogoutMenu();
} else {
for (let button of logoutMenuRoot.logoutModel) {
if (button.key == event.key) {
event.accepted = true;
}
}
}
}
RowLayout {
spacing: root.logoutSpacing
anchors {
fill: parent
margins: root.logoutMargin
}
Repeater {
delegate: logoutButton
model: logoutMenuRoot.logoutModel
}
Component {
id: logoutButton
Item {
id: logoutButtonRoot
// Key yen isn't on my keyboard, acts as an errorless analog for no bind
required property var modelData
Layout.fillHeight: true
Layout.fillWidth: true
Rectangle {
anchors.fill: parent
color: logoutButtonRoot.modelData.color
radius: 7
StyledText {
anchors.fill: parent
horizontalAlignment: Qt.AlignHCenter
text: logoutButtonRoot.modelData.text
verticalAlignment: Qt.AlignVCenter
}
}
}
}
}
}
}
Component { Component {
id: time id: time
@@ -162,7 +262,7 @@ Container {
property string component: "time" property string component: "time"
horizontalAlignment: Qt.AlignCenter horizontalAlignment: Qt.AlignCenter
text: Time.time text: TimeManager.time
verticalAlignment: Qt.AlignVCenter verticalAlignment: Qt.AlignVCenter
} }
} }

View File

@@ -25,18 +25,6 @@ Container {
defaultItem: placeholder defaultItem: placeholder
exclusiveMonitor: monitor exclusiveMonitor: monitor
states: [
State {
name: "hovered"
when: root.hovered
PropertyChanges {
root.barHeight: 30
root.workspaceWidth: 20
}
}
]
Component { Component {
id: placeholder id: placeholder

View File

@@ -14,8 +14,12 @@ Item {
property double animOffset: 0 property double animOffset: 0
property color boxColor: Colors.surface0 property color boxColor: Colors.surface0
property double boxHeight: 28 property double boxHeight: 28
property double boxHeightOpened: 150
property bool boxOpened: false
property double boxRadius: 9 property double boxRadius: 9
property double boxRadiusOpened: 12
property double boxWidth: 100 property double boxWidth: 100
property double boxWidthOpened: 200
readonly property double calculatedTopMargin: { readonly property double calculatedTopMargin: {
if (!root.exclusiveToScreen) { if (!root.exclusiveToScreen) {
return visibleTopMargin; return visibleTopMargin;
@@ -38,6 +42,8 @@ Item {
property alias hover: hoverHandler property alias hover: hoverHandler
property bool hoverableWhenHidden: false property bool hoverableWhenHidden: false
property bool hovered: false property bool hovered: false
property Component openedItem
property bool overriden: false
property alias rect: container property alias rect: container
property alias stack: containerContent property alias stack: containerContent
property alias tap: tapHandler property alias tap: tapHandler
@@ -50,13 +56,13 @@ Item {
Behavior on anchors.topMargin { Behavior on anchors.topMargin {
animation: defaultCurve animation: defaultCurve
} }
Behavior on boxHeight { Behavior on boxRadius {
SpringAnimation { SpringAnimation {
damping: 0.3 damping: 0.3
spring: 4 spring: 4
} }
} }
Behavior on boxRadius { Behavior on implicitHeight {
SpringAnimation { SpringAnimation {
damping: 0.3 damping: 0.3
spring: 4 spring: 4
@@ -68,6 +74,56 @@ Item {
spring: 4 spring: 4
} }
} }
states: [
State {
name: "closed"
when: root.hovered == false && root.boxOpened == false && root.overriden == false
PropertyChanges {
container.radius: root.boxRadius
root.implicitHeight: root.boxHeight
root.implicitWidth: root.boxWidth
}
StateChangeScript {
script: {
stack.replace(root.defaultItem);
}
}
},
State {
name: "hovered"
when: root.hovered == true && root.boxOpened == false && root.overriden == false
PropertyChanges {
container.radius: root.boxRadius + 3
root.implicitHeight: root.boxHeight + 2
root.implicitWidth: root.boxWidth + 2
}
StateChangeScript {
script: {
stack.replace(root.defaultItem);
}
}
},
State {
name: "opened"
when: root.boxOpened == true && root.hovered == true && root.overriden == false
PropertyChanges {
container.radius: root.boxRadiusOpened
root.implicitHeight: root.boxHeightOpened
root.implicitWidth: root.boxWidthOpened
}
StateChangeScript {
script: {
stack.replace(root.openedItem);
}
}
}
]
onForceHiddenChanged: { onForceHiddenChanged: {
if (root.forceHidden == true) { if (root.forceHidden == true) {
@@ -125,12 +181,22 @@ Item {
root.hovered = true; root.hovered = true;
} else { } else {
root.hovered = false; root.hovered = false;
root.boxOpened = false;
if (root.state == "closed") {
root.stack.replace(root.defaultItem);
}
} }
} }
} }
TapHandler { TapHandler {
id: tapHandler id: tapHandler
onTapped: {
if (root.openedItem) {
root.boxOpened = true;
}
}
} }
Rectangle { Rectangle {
@@ -141,6 +207,12 @@ Item {
color: root.boxColor color: root.boxColor
radius: root.boxRadius radius: root.boxRadius
Behavior on radius {
NumberAnimation {
duration: 300
}
}
StackView { StackView {
id: containerContent id: containerContent

169
README.md Normal file
View File

@@ -0,0 +1,169 @@
# VoidBar
_Minimal, Verbose, Clean_
## Ethos
This bar is a retort to all of the "dynamic island" style bars that have been popular
recently. I'm sure you know the ones, with a cramped, forced "minimalistic" aesthetic,
that, even though marketed toward window manager users such as myself, usually end up
being mouse driven spring animation slop.
Nobody wants that.
If you use something like Caelestia or Noctalia, sure, whatever. I'm not going to be some
elitist and say they aren't without their uses. If you want a cohesive shell with theme
switching and everything working perfectly out of the box, then they're good enough
solutions for new users, or even experienced ones that just claim they don't want to waste
their time writing from scratch.
However, everything outside of those shells is bullshit. Lately, all the shells I've seen
have been "dynamic islands", bars that only show the time when dormant and have a shitload
of features that you're never going to use effectively. They have no concept of workspace
identifiers, instead opting for nondescript nerd icon blobs rather than the tried and true
numbers that seem to always be more useful. Not that it matters, they're hidden anyway,
until you change workspaces and they get shown for a second or two after the point that
you actually needed to see them.
Then come the theme switchers, a sorry excuse for a feature that just ends up being a
party trick at the end of the day. Changing wallpapers every so often I can get behind,
but the whole theme? Really? The prophecy is borderline self fulfilling, you're going to
pick a favorite eventually and end up never touching the switcher again, no matter how
many variations of gruvbox you have configured. On top of that, are you really going to be
switching wallpapers at runtime that often? I for one never even considered implementing a
feature so distracting, opting instead to actually getting shit done, to the behest of
everyone on r/unixporn.
The final gripe I have with these so called "minimal bars" is, ironically, the
over-abundance of features. No, you don't need an android-style pull down menu to adjust
your brightness, no, you don't need a button to change your screen scaling on the fly, and
no, you don't need to be able to authenticate with your fucking desktop shell. Just use
sudo like the rest of us. Many such features are jammed into bars like these, it's as if
they need some standout feature in order to actually get used (oh wait). Bars shouldn't
need a whole custom settings app. Bars shouldn't need tutorials for what keybinds to use
to access some obscure feature. Bars shouldn't implement entire bluetooth management
interfaces. Bars should show the time, what workspace you're on, and maybe, at a stretch,
how much battery you have left. However, none of the geniuses over in the quickshell
discussions tab can figure out how to put numbers where the workspace blobs are.
So, I had no choice to fix the problem.
### The problem
I like minimalistic apps, and minimalism in general. But there comes a point where being
"minimal" either wraps around to being over the top and useless, or practically unusable.
Theoretically, the most minimal bar is no bar at all, but that suffers from not existing
by definition, so I did my best to come up with something that looks good, has utility,
and, most of the time, stays out of your way when you don't care about what it says.
The first thing I did was to expand from a magical single pill to the more classical
layout, a left, center and right section, ripped straight from waybar. If you only have a
left or right section, then your screen looks lopsided, and if you only have a center
section, then your bar either looks weirdly wide, or becomes just another useless capsule.
As I have ordained, the left section is solely for the workspace switcher, the center is
for the clock, and the right is for optional information, like bluetooth connection and
battery level, which you wouldn't need on a desktop machine.
The second innovation I came up with (fr fr no cap) was removing useless information from
the bar entirely. When wifi is disconnected, what good is it to have a "no wifi" icon
taking up space on the screen? None at all, in fact. Instead, I have opted to hide unused
modules entirely. For example, if no bluetooth devices are connected, instead of leaving a
useless logo in the top right, the module slides up off of the screen, telling you that
there is nothing connected simply through its absence. Plus, it looks better. That means
that on a system with no battery, no wifi connection (ethernet hides the module), and no
bluetooth devices connected, like my main pc most of the time, there are no needless icons
cluttering the top right of the screen.
The third and final main feature that I implemented is monitor-exclusive modules. If you
have multiple monitors, you most likely won't be looking at the second if you're doing
work on the first. If that's the case, then why would you need a ticking clock on your
second monitor? Why would you need an entire shell over there taking up space and being a
potential distraction (yes I know I'm being melodramatic but stfu)? This was one of my
main gripes with waybar, the fact that the same bar with the same information was
plastered across all of your screens, regardless of whether the information was needed or
not. A simple check can be done within an instance of the bar in quickshell, as to whether
that instance of the bar is on the currently focused monitor. If it is? Display the
modules. If not? Hide them away to keep the desktop clean. It's as simple as that.
No repeated information, no unnecesarry "disconnected" icons, and, most importantly, no
useless features. Speaking of...
## Features
### Tl;Dr
- _Workspace switcher:_ Displaying only the workspaces on that monitor. When on a
different monitor, highlights the focused workspace as to distinguish it from unfocused
workspaces
- _Clock:_ It's a clock. When a notification comes in, the clock is substituted for a
notification popup. If music is playing (any playerctld compatible player), when the
track changes a toast will appear, and when hovered over, the clock will be replaced
with a music information screen.
- _Network:_ Displays whether the machine is connected to wifi or not. Ethernet is assumed
a permanent feature, and the module is thus hidden even though a network is connected.
When clicked, shows the network name, local ip address, interface that is being used to
connect, and whether the network has saved login information.
- _Bluetooth:_ Displays whether there are any connected bluetooth devices. Does NOT
function as a full bluetooth interface because I have bluetui for that. When clicked,
displays connected devices, appropriate icons, and their mac addresses. The box
containing the icon doubles as a battery indicator, and when the icon is hovered over it
is replaced with a numerical percentage referring to the device's battery level
- _Battery:_ Displays battery level, and state through coloring and icons. When unplugged,
battery icon is reactive to every 10%. When plugged in but not recieving charge, the
module is highlighted orange. when plugged in and recieving charge, the module is
highlighted purple (the best color). When plugged in and full, the module is highlighted
blue. Icons also change to reflect the battery state, using the `nf-md-battery` class of
icons, as labeled on the nerd font website. When clicked on, The module shows the
battery's UPower reference (eg `BAT0`), reported charge in Wh, change rate in watts for
both charging and discharging, the time remaining until the battery is either fully
charged or empty, and a numerical percentage alongside another vertical battery meter
(same component as the bluetooth devices)
- Planned features (when I get around to it)
- Logout menu (wlogout is still broken lol)
- Notification menu/management
- Lock screen
- Volume control? (probably not, way-edges clears)
- Interactible mpris player (skip and rewind)
- Music indicator on the left of the clock when music is playing
- Hover to show mpris thing when music is paused
- Mpris firefox exclusion (I had to disable it in about:config lol)
### More rambling
If you read the above section, you will notice there is no mention of a theme switcher or
launcher functionality. That is because I made a bar, not a theme switcher or a launcher.
Other, more knowledgeable people have made those things already, and they are almost
definately more feature rich and polished than anything you or I could make in qml. Once
again, this bar has NO BULLSHIT!!! NONE!!!
One thing I found cool while making this bar is all the interesting information that most
other bars will hide from you. Instead of what my cpu temperature is, I can see how fast
my battery is charging down to 8 decimal places of precision. On top of this, because qml
is just fancy javascript under the hood, you can do surprisingly complex data manipulation
if needs be. Of course, you can pull out an old trusty `toFixed(2)` so that there aren't
so many decimals, but beyond that, for example, the mpris api gives you track info in
miliseconds, which means you have to make a utility function to convert from that format
into a readable one, and then manipulate those values again, because they don't have
leading zeros by default.
Because of how qml works, I worried less about how inefficient my polling code was, and
how things were going to react with eachother, and update order, and instead focused on
how I wanted the bar to act. I'll tell you a little secret, qml is a really really easy
language to learn. That's how it's supposed to be, and why there's a quick in the name.
Qml is to desktop apps what tailwind is to html, almost. You can throw together something
that looks cohesive in a few minutes, and because of how state management works, adding
reactivity like a hover animation is stupidly easy. Everything is predictable, and because
you don't have to worry about how to update components, you can just throw in a few js
functions here and there regardless of whether you need them. I have to tell you, having a
bunch of heights and margins and then using a bunch of maths in a function to dynamically
calculate the height of a window or a textbox is the coolest shit ever.
I will actually use this bar on my main machine. There might be some weird shit going on
with the battery module, because the only machine I had while making this was a laptop,
but once I get home everything will be resolved. This was not made to post on reddit. This
wasn't made on a whim. I hate the state of places like r/unixporn, where, for every decent
and original rice, there are 300 copycats or people just running a prefab shell with a
custom wallpaper. And don't even get me going on the damn islands again. If I can make
something like this in a few days, then so can you. Go make something, and thanks for
reading <3

31
Services/IpcManager.qml Normal file
View File

@@ -0,0 +1,31 @@
pragma Singleton
import QtQuick
import Quickshell
import Quickshell.Io
import Quickshell.Wayland
Singleton {
id: root
property var focusState: WlrKeyboardFocus.None
property bool logoutMenuOpen: false
signal closeLogoutMenu
signal logoutMenu
onCloseLogoutMenu: {
root.focusState = WlrKeyboardFocus.None;
root.logoutMenuOpen = false;
}
IpcHandler {
function openLogoutMenu(): void {
root.logoutMenu();
root.focusState = WlrKeyboardFocus.Exclusive;
root.logoutMenuOpen = true;
}
target: "root"
}
}

View File

@@ -1,6 +1,7 @@
pragma Singleton pragma Singleton
import QtQuick import QtQuick
import Quickshell import Quickshell
import Quickshell.Io
import Quickshell.Networking import Quickshell.Networking
Singleton { Singleton {
@@ -9,6 +10,7 @@ Singleton {
property bool connectedWifi: root.defaultAdapter != null && root.defaultAdapter.connected property bool connectedWifi: root.defaultAdapter != null && root.defaultAdapter.connected
&& root.defaultAdapter.type == DeviceType.Wifi && root.defaultAdapter.type == DeviceType.Wifi
property NetworkDevice defaultAdapter: Networking.devices.values[0] || null property NetworkDevice defaultAdapter: Networking.devices.values[0] || null
property string ipAddress: "0.0.0.0"
function getConnectedNetworks(networksArr) { function getConnectedNetworks(networksArr) {
let connectedNetworks = []; let connectedNetworks = [];
@@ -61,7 +63,8 @@ Singleton {
"networkName": networkName, "networkName": networkName,
"networkStrength": networkStrength, "networkStrength": networkStrength,
"icon": networkIcon, "icon": networkIcon,
"saved": primaryNetwork.known "saved": primaryNetwork.known,
"ipAddress": root.ipAddress
}; };
return outputDict; return outputDict;
} }
@@ -96,4 +99,24 @@ Singleton {
return false; return false;
} }
} }
function refreshIpAddress() {
ipProcess.running = true;
}
onDefaultAdapterChanged: {
refreshIpAddress();
}
Process {
id: ipProcess
command: ["sh", "-c", "ip -4 -o addr show dev " + root.defaultAdapter.name
+ " scope global | awk '{sub(\"/.*\", \"\", $4); printf $4}'"]
running: true
stdout: StdioCollector {
onStreamFinished: root.ipAddress = this.text
}
}
} }

View File

@@ -1,8 +1,9 @@
module Services module Services
singleton Time 1.0 Time.qml singleton TimeManager 1.0 TimeManager.qml
singleton NotificationManager 1.0 NotificationManager.qml singleton NotificationManager 1.0 NotificationManager.qml
singleton WorkspaceManager 1.0 WorkspaceManager.qml singleton WorkspaceManager 1.0 WorkspaceManager.qml
singleton BluetoothManager 1.0 BluetoothManager.qml singleton BluetoothManager 1.0 BluetoothManager.qml
singleton MprisManager 1.0 MprisManager.qml singleton MprisManager 1.0 MprisManager.qml
singleton BatteryManager 1.0 BatteryManager.qml singleton BatteryManager 1.0 BatteryManager.qml
singleton NetworkManager 1.0 NetworkManager.qml singleton NetworkManager 1.0 NetworkManager.qml
singleton IpcManager 1.0 IpcManager.qml